diff --git a/ui/web/src/components/chat/chat-top-bar.tsx b/ui/web/src/components/chat/chat-top-bar.tsx index 527977a5..ddac7a63 100644 --- a/ui/web/src/components/chat/chat-top-bar.tsx +++ b/ui/web/src/components/chat/chat-top-bar.tsx @@ -1,9 +1,11 @@ import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { Loader2, Bot, Users, PanelRightOpen, PanelRightClose } from "lucide-react"; import { useHttp } from "@/hooks/use-ws"; import { useAuthStore } from "@/stores/use-auth-store"; import type { RunActivity, ActiveTeamTask } from "@/types/chat"; import type { AgentData } from "@/types/agent"; +import type { SessionInfo } from "@/types/session"; interface ChatTopBarProps { agentId: string; @@ -13,6 +15,8 @@ interface ChatTopBarProps { teamTasks: ActiveTeamTask[]; onToggleTaskPanel?: () => void; taskPanelOpen?: boolean; + /** Current session — when provided, the bar renders a context-usage badge. */ + session?: SessionInfo | null; } const phaseLabels: Record = { @@ -24,8 +28,9 @@ const phaseLabels: Record = { leader_processing: "Processing team results…", }; -export function ChatTopBar({ agentId, isRunning, isBusy, activity, teamTasks, onToggleTaskPanel, taskPanelOpen }: ChatTopBarProps) { +export function ChatTopBar({ agentId, isRunning, isBusy, activity, teamTasks, onToggleTaskPanel, taskPanelOpen, session }: ChatTopBarProps) { const http = useHttp(); + const { t } = useTranslation("chat"); const connected = useAuthStore((s) => s.connected); const [agent, setAgent] = useState<{ name: string; emoji?: string } | null>(null); @@ -51,6 +56,31 @@ export function ChatTopBar({ agentId, isRunning, isBusy, activity, teamTasks, on const emoji = agent?.emoji; const PanelIcon = taskPanelOpen ? PanelRightClose : PanelRightOpen; + // Context-usage badge: only renders when the caller passes a session with + // both estimatedTokens (Phase 4 ContextStage output) and contextWindow. + // `percent` drives the color ramp so operators spot near-limit sessions. + const usage = (() => { + if (!session || !session.contextWindow || session.contextWindow <= 0) { + return null; + } + const used = session.estimatedTokens ?? 0; + const max = session.contextWindow; + const percent = Math.min(100, Math.round((used / max) * 100)); + const color = + percent >= 90 ? "text-destructive" : percent >= 75 ? "text-amber-600 dark:text-amber-400" : "text-muted-foreground"; + return { used, max, percent, color }; + })(); + + // Last compaction timestamp ships in sessions.metadata JSONB (Phase 5 follow-up, + // keyed "last_compaction_at"). Parsed lazily so bad data doesn't crash the bar. + const lastCompaction = (() => { + const raw = session?.metadata?.last_compaction_at; + if (!raw) return null; + const d = new Date(raw); + if (isNaN(d.getTime())) return null; + return d; + })(); + return (
@@ -63,6 +93,23 @@ export function ChatTopBar({ agentId, isRunning, isBusy, activity, teamTasks, on
+ {usage && ( +
+ + {usage.used.toLocaleString()}/{usage.max.toLocaleString()} + + ({usage.percent}%) +
+ )} {isRunning ? (
{activity ? phaseLabels[activity.phase] : "Running…"} diff --git a/ui/web/src/i18n/locales/en/agents.json b/ui/web/src/i18n/locales/en/agents.json index 2e25fd34..19048c7f 100644 --- a/ui/web/src/i18n/locales/en/agents.json +++ b/ui/web/src/i18n/locales/en/agents.json @@ -826,6 +826,18 @@ "textWeight": "Text Weight", "textWeightTip": "Weight for keyword similarity in hybrid search." }, + "dreaming": { + "title": "Dreaming (Memory Consolidation)", + "description": "Background worker that promotes session summaries into long-term memory. Tune when the agent is too chatty or not consolidating often enough.", + "enabled": "Enabled", + "enabledTip": "Disable if you want the agent to rely only on raw session summaries without long-term consolidation.", + "threshold": "Threshold", + "thresholdTip": "Minimum unpromoted summaries before a consolidation run fires. Default 5.", + "debounceMs": "Debounce (ms)", + "debounceMsTip": "Minimum interval between runs per agent/user. Default 600000 (10 minutes).", + "verboseLog": "Verbose Logging", + "verboseLogTip": "Emit debounce and below-threshold skips at info level for operator debugging." + }, "thinking": { "title": "Extended Thinking", "description": "Allow the model to reason before responding. Higher levels use more tokens but produce better results on complex tasks.", diff --git a/ui/web/src/i18n/locales/en/chat.json b/ui/web/src/i18n/locales/en/chat.json index 5db2ba2d..c0a347e8 100644 --- a/ui/web/src/i18n/locales/en/chat.json +++ b/ui/web/src/i18n/locales/en/chat.json @@ -4,6 +4,10 @@ "newChat": "New Chat", "openSessions": "Open sessions", "readOnly": "Read-only — this session belongs to another user", + "contextUsage": { + "tooltip": "{{used}} / {{max}} tokens ({{percent}}%) · {{compactions}} compactions · last: {{lastCompact}}", + "never": "never" + }, "empty": { "title": "Start a conversation", "description": "Send a message to begin chatting with the agent." diff --git a/ui/web/src/i18n/locales/vi/agents.json b/ui/web/src/i18n/locales/vi/agents.json index c52dbf7f..7da5e728 100644 --- a/ui/web/src/i18n/locales/vi/agents.json +++ b/ui/web/src/i18n/locales/vi/agents.json @@ -783,6 +783,18 @@ "textWeight": "Trọng số văn bản", "textWeightTip": "Trọng số từ khóa trong tìm kiếm hỗn hợp." }, + "dreaming": { + "title": "Dreaming (hợp nhất bộ nhớ)", + "description": "Worker nền ghi các tóm tắt phiên vào bộ nhớ dài hạn. Tinh chỉnh nếu agent hợp nhất quá thường xuyên hoặc quá ít.", + "enabled": "Đã bật", + "enabledTip": "Tắt nếu bạn chỉ muốn dùng tóm tắt phiên thô, không cần hợp nhất dài hạn.", + "threshold": "Ngưỡng", + "thresholdTip": "Số tóm tắt chưa promote tối thiểu trước khi chạy hợp nhất. Mặc định 5.", + "debounceMs": "Debounce (ms)", + "debounceMsTip": "Khoảng thời gian tối thiểu giữa hai lần chạy cho mỗi agent/user. Mặc định 600000 (10 phút).", + "verboseLog": "Log chi tiết", + "verboseLogTip": "Ghi các lần skip (debounce, below-threshold) ở mức info để debug." + }, "thinking": { "title": "Suy nghĩ mở rộng", "description": "Cho phép model suy luận trước khi phản hồi. Cấp độ cao hơn dùng nhiều token hơn nhưng cho kết quả tốt hơn với tác vụ phức tạp.", diff --git a/ui/web/src/i18n/locales/vi/chat.json b/ui/web/src/i18n/locales/vi/chat.json index fbbbb2c0..c5c5ac90 100644 --- a/ui/web/src/i18n/locales/vi/chat.json +++ b/ui/web/src/i18n/locales/vi/chat.json @@ -1,4 +1,8 @@ { + "contextUsage": { + "tooltip": "{{used}} / {{max}} token ({{percent}}%) · {{compactions}} lần compact · gần nhất: {{lastCompact}}", + "never": "chưa có" + }, "empty": { "description": "Gửi tin nhắn để bắt đầu trò chuyện với agent.", "title": "Bắt đầu cuộc trò chuyện" diff --git a/ui/web/src/i18n/locales/zh/agents.json b/ui/web/src/i18n/locales/zh/agents.json index 09303374..124b8612 100644 --- a/ui/web/src/i18n/locales/zh/agents.json +++ b/ui/web/src/i18n/locales/zh/agents.json @@ -783,6 +783,18 @@ "textWeight": "文本权重", "textWeightTip": "混合搜索中关键词相似度的权重。" }, + "dreaming": { + "title": "Dreaming(记忆整合)", + "description": "后台工作器将会话摘要提升为长期记忆。根据Agent整合频率调整参数。", + "enabled": "已启用", + "enabledTip": "关闭后Agent将仅使用原始会话摘要,不进行长期整合。", + "threshold": "阈值", + "thresholdTip": "触发整合运行所需的最少未提升摘要数。默认5。", + "debounceMs": "防抖间隔 (ms)", + "debounceMsTip": "每个Agent/用户两次运行之间的最小间隔。默认600000(10分钟)。", + "verboseLog": "详细日志", + "verboseLogTip": "在info级别输出防抖和低于阈值跳过,便于运维调试。" + }, "thinking": { "title": "扩展思考", "description": "允许模型在响应前进行推理。更高级别使用更多令牌,但在复杂任务上产生更好的结果。", diff --git a/ui/web/src/i18n/locales/zh/chat.json b/ui/web/src/i18n/locales/zh/chat.json index b50c25da..c16aa14c 100644 --- a/ui/web/src/i18n/locales/zh/chat.json +++ b/ui/web/src/i18n/locales/zh/chat.json @@ -1,4 +1,8 @@ { + "contextUsage": { + "tooltip": "{{used}} / {{max}} tokens ({{percent}}%) · 压缩 {{compactions}} 次 · 最近:{{lastCompact}}", + "never": "无" + }, "empty": { "description": "发送消息开始与Agent对话。", "title": "开始对话" diff --git a/ui/web/src/pages/agents/agent-detail/config-sections/memory-section.tsx b/ui/web/src/pages/agents/agent-detail/config-sections/memory-section.tsx index c8487f72..42d2ff61 100644 --- a/ui/web/src/pages/agents/agent-detail/config-sections/memory-section.tsx +++ b/ui/web/src/pages/agents/agent-detail/config-sections/memory-section.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; -import type { MemoryConfig } from "@/types/agent"; +import type { MemoryConfig, DreamingConfig } from "@/types/agent"; import { InfoLabel, numOrUndef } from "./config-section"; interface MemorySectionProps { @@ -12,6 +12,10 @@ interface MemorySectionProps { export function MemorySection({ value, onChange }: MemorySectionProps) { const { t } = useTranslation("agents"); const s = "configSections.memory"; + const ds = "configSections.dreaming"; + const dreaming: DreamingConfig = value.dreaming ?? {}; + const setDreaming = (patch: Partial) => + onChange({ ...value, dreaming: { ...dreaming, ...patch } }); return (
@@ -95,6 +99,51 @@ export function MemorySection({ value, onChange }: MemorySectionProps) { />
+ {/* Phase 8 — Dreaming worker per-agent overrides. + Nested inside memory since dreaming is memory consolidation. + All fields optional; nil = use backend defaults. */} +
+
+

{t(`${ds}.title`)}

+

{t(`${ds}.description`)}

+
+
+ setDreaming({ enabled: v })} + /> + {t(`${ds}.enabled`)} +
+
+
+ {t(`${ds}.threshold`)} + setDreaming({ threshold: numOrUndef(e.target.value) })} + className="text-base md:text-sm" + /> +
+
+ {t(`${ds}.debounceMs`)} + setDreaming({ debounce_ms: numOrUndef(e.target.value) })} + className="text-base md:text-sm" + /> +
+
+
+ setDreaming({ verbose_log: v })} + /> + {t(`${ds}.verboseLog`)} +
+
); diff --git a/ui/web/src/pages/chat/chat-page.tsx b/ui/web/src/pages/chat/chat-page.tsx index 5df229f8..5cf0ab5c 100644 --- a/ui/web/src/pages/chat/chat-page.tsx +++ b/ui/web/src/pages/chat/chat-page.tsx @@ -234,7 +234,16 @@ export function ChatPage() { )}
- setTaskPanelOpen((v) => !v)} taskPanelOpen={taskPanelOpen} /> + setTaskPanelOpen((v) => !v)} + taskPanelOpen={taskPanelOpen} + session={sessions.find((s) => s.key === sessionKey) ?? null} + />
{sendError && ( diff --git a/ui/web/src/types/agent.ts b/ui/web/src/types/agent.ts index 6dd89160..038f491f 100644 --- a/ui/web/src/types/agent.ts +++ b/ui/web/src/types/agent.ts @@ -67,6 +67,20 @@ export interface MemoryConfig { vector_weight?: number; text_weight?: number; min_score?: number; + dreaming?: DreamingConfig | null; +} + +/** + * DreamingConfig mirrors Go internal/config.DreamingConfig — per-agent + * overrides for the consolidation dreaming worker. Pointer fields stay + * undefined when the operator hasn't set them so partial overrides merge + * cleanly with backend defaults. + */ +export interface DreamingConfig { + enabled?: boolean; + debounce_ms?: number; + threshold?: number; + verbose_log?: boolean; } export interface WorkspaceSharingConfig {