From 93bf4572786fa1fbfc70158f603d24b30fd00056 Mon Sep 17 00:00:00 2001 From: Plateau Nguyen Date: Thu, 7 May 2026 17:49:54 +0700 Subject: [PATCH] feat(ui): add Compact button to session detail page Backend method sessions.compact (Issue 958) was not exposed in the web UI. Users only had Reset (wipes both messages and summary) and Delete. Compact truncates messages to last N (default 4) while preserving the summary, letting agents keep context after recovering from full-context situations. Adds: - SESSIONS_COMPACT method constant. - compactSession callback in useSessions hook. - "Compact" button between Reset and Delete in session detail header, with confirm dialog explaining the difference vs Reset. - i18n keys for en/vi/zh: detail.compact, detail.compactTitle, detail.compactDescription, detail.confirmCompact, toast.compacted, toast.compactFailed. Backend unchanged. --- ui/web/src/api/protocol.ts | 1 + ui/web/src/i18n/locales/en/sessions.json | 6 +++++ ui/web/src/i18n/locales/vi/sessions.json | 6 +++++ ui/web/src/i18n/locales/zh/sessions.json | 6 +++++ .../src/pages/sessions/hooks/use-sessions.ts | 17 +++++++++++- .../pages/sessions/session-detail-page.tsx | 27 ++++++++++++++++++- ui/web/src/pages/sessions/sessions-page.tsx | 3 ++- 7 files changed, 63 insertions(+), 3 deletions(-) diff --git a/ui/web/src/api/protocol.ts b/ui/web/src/api/protocol.ts index da541fe975..4d0acd2571 100644 --- a/ui/web/src/api/protocol.ts +++ b/ui/web/src/api/protocol.ts @@ -78,6 +78,7 @@ export const Methods = { SESSIONS_PATCH: "sessions.patch", SESSIONS_DELETE: "sessions.delete", SESSIONS_RESET: "sessions.reset", + SESSIONS_COMPACT: "sessions.compact", // Phase 2 - NEEDED SKILLS_LIST: "skills.list", diff --git a/ui/web/src/i18n/locales/en/sessions.json b/ui/web/src/i18n/locales/en/sessions.json index a2935f645e..bd69001686 100644 --- a/ui/web/src/i18n/locales/en/sessions.json +++ b/ui/web/src/i18n/locales/en/sessions.json @@ -18,13 +18,17 @@ }, "detail": { "reset": "Reset", + "compact": "Compact", "delete": "Delete", "deleteTitle": "Delete Session", "deleteDescription": "This will permanently delete all messages in this session.", "resetTitle": "Reset Session", "resetDescription": "This will clear all messages but keep the session.", + "compactTitle": "Compact Session", + "compactDescription": "Truncate message history to the last 4 messages while keeping the summary. Unlike Reset, the summary is preserved so the agent retains context.", "confirmDelete": "Delete", "confirmReset": "Reset", + "confirmCompact": "Compact", "noMessages": "No messages in this session", "summary": "Summary", "showMore": "Show more", @@ -39,6 +43,8 @@ "deleteFailed": "Failed to delete session", "reset": "Session reset", "resetFailed": "Failed to reset session", + "compacted": "Session compacted", + "compactFailed": "Failed to compact session", "updated": "Session updated", "updateFailed": "Failed to update session" } diff --git a/ui/web/src/i18n/locales/vi/sessions.json b/ui/web/src/i18n/locales/vi/sessions.json index 66a484669f..144e118279 100644 --- a/ui/web/src/i18n/locales/vi/sessions.json +++ b/ui/web/src/i18n/locales/vi/sessions.json @@ -18,13 +18,17 @@ }, "detail": { "reset": "Đặt lại", + "compact": "Nén", "delete": "Xóa", "deleteTitle": "Xóa session", "deleteDescription": "Thao tác này sẽ xóa vĩnh viễn tất cả tin nhắn trong session này.", "resetTitle": "Đặt lại phiên", "resetDescription": "Thao tác này sẽ xóa tất cả tin nhắn nhưng giữ lại phiên.", + "compactTitle": "Nén phiên", + "compactDescription": "Cắt ngắn lịch sử tin nhắn xuống 4 tin gần nhất, giữ lại tóm tắt. Khác với Đặt lại - không xóa tóm tắt, agent vẫn nhớ ngữ cảnh.", "confirmDelete": "Xóa", "confirmReset": "Đặt lại", + "confirmCompact": "Nén", "noMessages": "Không có tin nhắn trong session này", "summary": "Tóm tắt", "showMore": "Xem thêm", @@ -39,6 +43,8 @@ "deleteFailed": "Không thể xóa session", "reset": "Đã đặt lại session", "resetFailed": "Không thể đặt lại session", + "compacted": "Đã nén session", + "compactFailed": "Không thể nén session", "updated": "Đã cập nhật session", "updateFailed": "Không thể cập nhật session" } diff --git a/ui/web/src/i18n/locales/zh/sessions.json b/ui/web/src/i18n/locales/zh/sessions.json index 6ea76314ae..79d5e74e6d 100644 --- a/ui/web/src/i18n/locales/zh/sessions.json +++ b/ui/web/src/i18n/locales/zh/sessions.json @@ -18,13 +18,17 @@ }, "detail": { "reset": "重置", + "compact": "压缩", "delete": "删除", "deleteTitle": "删除Session", "deleteDescription": "这将永久删除此Session中的所有消息。", "resetTitle": "重置Session", "resetDescription": "这将清除所有消息但保留Session。", + "compactTitle": "压缩Session", + "compactDescription": "将消息历史截断为最近 4 条消息,但保留摘要。与重置不同,摘要会保留,以便Agent保留上下文。", "confirmDelete": "删除", "confirmReset": "重置", + "confirmCompact": "压缩", "noMessages": "此Session中没有消息", "summary": "摘要", "showMore": "显示更多", @@ -39,6 +43,8 @@ "deleteFailed": "删除Session失败", "reset": "Session已重置", "resetFailed": "重置Session失败", + "compacted": "Session已压缩", + "compactFailed": "压缩Session失败", "updated": "Session已更新", "updateFailed": "更新Session失败" } diff --git a/ui/web/src/pages/sessions/hooks/use-sessions.ts b/ui/web/src/pages/sessions/hooks/use-sessions.ts index e63a1bd8ff..6493b7e732 100644 --- a/ui/web/src/pages/sessions/hooks/use-sessions.ts +++ b/ui/web/src/pages/sessions/hooks/use-sessions.ts @@ -89,6 +89,21 @@ export function useSessions(opts: UseSessionsOptions = {}) { [ws, invalidate], ); + const compactSession = useCallback( + async (key: string, keepLast?: number) => { + if (!ws.isConnected) return; + try { + await ws.call(Methods.SESSIONS_COMPACT, { key, keepLast }); + await invalidate(); + toast.success(i18next.t("sessions:toast.compacted")); + } catch (err) { + toast.error(i18next.t("sessions:toast.compactFailed"), userFriendlyError(err)); + throw err; + } + }, + [ws, invalidate], + ); + const patchSession = useCallback( async (key: string, updates: { label?: string; model?: string; metadata?: Record }) => { if (!ws.isConnected) return; @@ -104,5 +119,5 @@ export function useSessions(opts: UseSessionsOptions = {}) { [ws, invalidate], ); - return { sessions, total, loading, refresh: invalidate, preview, deleteSession, resetSession, patchSession }; + return { sessions, total, loading, refresh: invalidate, preview, deleteSession, resetSession, compactSession, patchSession }; } diff --git a/ui/web/src/pages/sessions/session-detail-page.tsx b/ui/web/src/pages/sessions/session-detail-page.tsx index 04eee12f60..46144945f0 100644 --- a/ui/web/src/pages/sessions/session-detail-page.tsx +++ b/ui/web/src/pages/sessions/session-detail-page.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; -import { ArrowLeft, Trash2, RotateCcw, Eye, Pencil, Check, X } from "lucide-react"; +import { ArrowLeft, Trash2, RotateCcw, Shrink, Eye, Pencil, Check, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { MessageBubble } from "@/components/chat/message-bubble"; @@ -40,6 +40,7 @@ interface SessionDetailPageProps { onPreview: (key: string) => Promise; onDelete: (key: string) => Promise; onReset: (key: string) => Promise; + onCompact?: (key: string, keepLast?: number) => Promise; onPatch?: (key: string, updates: { label?: string }) => Promise; } @@ -49,6 +50,7 @@ export function SessionDetailPage({ onPreview, onDelete, onReset, + onCompact, onPatch, }: SessionDetailPageProps) { const { t } = useTranslation("sessions"); @@ -57,6 +59,7 @@ export function SessionDetailPage({ const [loading, setLoading] = useState(true); const [confirmDelete, setConfirmDelete] = useState(false); const [confirmReset, setConfirmReset] = useState(false); + const [confirmCompact, setConfirmCompact] = useState(false); const [editingTitle, setEditingTitle] = useState(false); const [titleDraft, setTitleDraft] = useState(""); @@ -206,6 +209,11 @@ export function SessionDetailPage({
+ {onCompact && ( + + )} @@ -268,6 +276,23 @@ export function SessionDetailPage({ setMessages([]); }} /> + + {onCompact && ( + { + await onCompact(session.key); + setConfirmCompact(false); + // Reload preview to reflect truncated history. + const p = await onPreview(session.key); + if (p) setMessages(p.messages); + }} + /> + )}
); } diff --git a/ui/web/src/pages/sessions/sessions-page.tsx b/ui/web/src/pages/sessions/sessions-page.tsx index 3d0dc851c2..9e6771418c 100644 --- a/ui/web/src/pages/sessions/sessions-page.tsx +++ b/ui/web/src/pages/sessions/sessions-page.tsx @@ -27,7 +27,7 @@ export function SessionsPage() { const [pageSize, setPageSizeRaw] = useState(globalPageSize); const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); }; - const { sessions, total, loading, preview, deleteSession, resetSession, patchSession } = useSessions({ + const { sessions, total, loading, preview, deleteSession, resetSession, compactSession, patchSession } = useSessions({ limit: pageSize, offset: (page - 1) * pageSize, }); @@ -50,6 +50,7 @@ export function SessionsPage() { navigate("/sessions"); }} onReset={resetSession} + onCompact={compactSession} onPatch={patchSession} /> );