feat: 添加日志记录以持久化待办事项时的调试信息
This commit is contained in:
parent
0b190b717c
commit
3c889caacf
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
146
web/src/components/Chat/ToolDetailModal.tsx
Normal file
146
web/src/components/Chat/ToolDetailModal.tsx
Normal file
@ -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 (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm animate-fade-in"
|
||||
onClick={onClose}
|
||||
>
|
||||
{/* Modal container */}
|
||||
<div
|
||||
className="relative w-[90vw] max-w-4xl max-h-[90vh] rounded-2xl border border-[var(--border-color)] bg-[var(--bg-secondary)] shadow-2xl flex flex-col overflow-hidden animate-scale-in"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 shrink-0 px-6 py-4 border-b border-[var(--border-color)] bg-[var(--bg-tertiary)]/50">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Terminal className="h-5 w-5 text-[var(--accent-cyan)]" />
|
||||
<span className="text-lg font-semibold text-[var(--text-primary)]">{toolName}</span>
|
||||
</div>
|
||||
<span
|
||||
className="px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style={{
|
||||
backgroundColor: `${statusColor}15`,
|
||||
color: statusColor,
|
||||
border: `1px solid ${statusColor}30`,
|
||||
}}
|
||||
>
|
||||
{statusLabel}
|
||||
</span>
|
||||
{durationMs != null && (
|
||||
<span className="flex items-center gap-1.5 text-sm text-[var(--text-muted)]">
|
||||
<Clock className="h-4 w-4" />
|
||||
{formatDuration(durationMs)}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto p-2 rounded-lg text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
|
||||
aria-label="关闭"
|
||||
title="关闭 (Esc)"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body — scrollable */}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-5">
|
||||
{/* Arguments */}
|
||||
{args !== undefined && args !== null && (
|
||||
<div>
|
||||
<div className="text-base font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
|
||||
<span className="w-1 h-5 rounded-full bg-[var(--accent-cyan)] inline-block" />
|
||||
参数
|
||||
</div>
|
||||
<pre className="text-base leading-relaxed text-[var(--text-secondary)] font-mono whitespace-pre-wrap bg-[var(--overlay-dim)] rounded-xl p-4 overflow-x-auto border border-[var(--border-color)]">
|
||||
{JSON.stringify(args, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result / Output */}
|
||||
{displayContent && (
|
||||
<div>
|
||||
<div className="text-base font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
|
||||
<span className="w-1 h-5 rounded-full bg-[var(--accent-green)] inline-block" />
|
||||
{resultContent ? '结果' : '输出'}
|
||||
</div>
|
||||
<div className="text-base leading-relaxed text-[var(--text-secondary)] font-mono whitespace-pre-wrap bg-[var(--overlay-dim)] rounded-xl p-4 overflow-x-auto border border-[var(--border-color)] markdown-content">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{formattedContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="shrink-0 px-6 py-3 border-t border-[var(--border-color)] bg-[var(--bg-tertiary)]/30 flex items-center justify-between text-sm text-[var(--text-muted)]">
|
||||
<span>按 Esc 关闭</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Maximize2 className="h-3.5 w-3.5" />
|
||||
放大视图
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user