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)}
+
+