From 3c889caacf18f845ada2f8508b27975e0f75a0c8 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Fri, 12 Jun 2026 16:22:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E4=BB=A5=E6=8C=81=E4=B9=85=E5=8C=96=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E4=BA=8B=E9=A1=B9=E6=97=B6=E7=9A=84=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/command/handlers/list_todos.rs | 4 +- src/gateway/session.rs | 5 + web/src/components/Chat/ToolDetailModal.tsx | 146 ++++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 web/src/components/Chat/ToolDetailModal.tsx diff --git a/src/command/handlers/list_todos.rs b/src/command/handlers/list_todos.rs index 94c797f..621ec05 100644 --- a/src/command/handlers/list_todos.rs +++ b/src/command/handlers/list_todos.rs @@ -69,6 +69,8 @@ impl CommandHandler for ListTodosCommandHandler { let todos_json = serde_json::to_string(&summaries) .map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?; - Ok(CommandResponse::success(ctx.request_id).with_metadata("todos", &todos_json)) + Ok(CommandResponse::success(ctx.request_id) + .with_metadata("todos", &todos_json) + .with_metadata("todos_scope_key", scope_key)) } } diff --git a/src/gateway/session.rs b/src/gateway/session.rs index 6968d4f..133ab79 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -437,6 +437,11 @@ impl Session { .collect(); // 持久化到 SQLite + tracing::info!( + scope_key = %scope_key, + todo_count = records.len(), + "intercept_todo_write_results: persisting todos" + ); if let Err(e) = self.store.replace_todos(&scope_key, &records) { tracing::warn!(error = %e, scope_key = %scope_key, "Failed to persist todo list"); } diff --git a/web/src/components/Chat/ToolDetailModal.tsx b/web/src/components/Chat/ToolDetailModal.tsx new file mode 100644 index 0000000..c9e6a43 --- /dev/null +++ b/web/src/components/Chat/ToolDetailModal.tsx @@ -0,0 +1,146 @@ +import { useEffect } from 'react' +import { X, Terminal, Clock, Maximize2 } from 'lucide-react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +interface ToolDetailModalProps { + toolName: string + status: string + statusLabel: string + arguments?: unknown + resultContent: string + callContent: string + durationMs?: number + onClose: () => void +} + +function formatDuration(ms: number): string { + if (ms < 1000) return `${ms}ms` + if (ms < 60000) return `${(ms / 1000).toFixed(1)}s` + const minutes = Math.floor(ms / 60000) + const seconds = Math.floor((ms % 60000) / 1000) + return `${minutes}m ${seconds}s` +} + +function formatJSON(text: string): string { + if (!text) return '' + try { + const parsed = JSON.parse(text) + return JSON.stringify(parsed, null, 2) + } catch { + return text + } +} + +export function ToolDetailModal({ + toolName, + status, + statusLabel, + arguments: args, + resultContent, + callContent, + durationMs, + onClose, +}: ToolDetailModalProps) { + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + + const displayContent = resultContent || callContent + const formattedContent = formatJSON(displayContent) + + const statusColor = + status === 'calling' ? 'var(--accent-amber)' : + status === 'result' ? 'var(--accent-green)' : + status === 'pending' ? '#f59e0b' : + 'var(--text-muted)' + + return ( +
+ {JSON.stringify(args, null, 2)}
+
+