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} /> );