import { Cpu, Bookmark, Wrench, MessageSquareText, Sparkles, ListTree, FileText, FileOutput, CheckCircle2, XCircle, Loader2, Circle, type LucideIcon, } from "lucide-react"; import { deriveNodes, type NodeTrace, type RunPhase } from "../lib/run"; import type { ExecEvent } from "../lib/api"; import { cn } from "../ui"; // 各节点类别的图标与配色(与后端 ExecEvent.kind 对应)。 const KIND: Record = { system: { icon: Cpu, cls: "text-slate-400", name: "系统" }, memory: { icon: Bookmark, cls: "text-brand-400", name: "记忆召回" }, tool: { icon: Wrench, cls: "text-warn", name: "工具调用" }, prompt: { icon: MessageSquareText, cls: "text-sky-300", name: "提示词" }, model: { icon: Sparkles, cls: "text-success", name: "模型推理" }, plan: { icon: ListTree, cls: "text-accent-400", name: "规划" }, section: { icon: FileText, cls: "text-indigo-300", name: "章节" }, render: { icon: FileOutput, cls: "text-danger", name: "渲染" }, }; function meta(kind: string) { return KIND[kind] ?? { icon: Circle, cls: "text-slate-400", name: kind }; } function StatusDot({ status }: { status: NodeTrace["status"] }) { if (status === "running") return ; if (status === "done") return ; if (status === "error") return ; if (status === "waiting") return ; // 待审批 return ; } // ExecTrace 把执行事件流渲染为竖向轨道:每个节点一颗灯,实时点亮 + 耗时 + 入参/产出。 export function ExecTrace({ events, phase, compact }: { events: ExecEvent[]; phase?: RunPhase; compact?: boolean }) { const raw = deriveNodes(events); // 运行整体已完成,但轨迹可能缺了中断后(resume)续录的事件(如审批通过、续跑节点), // 导致审批节点停在 waiting 转圈。既然已完成,把悬挂的 waiting/running 收敛为 done,不再假装进行中。 const nodes = phase === "done" ? raw.map((n) => (n.status === "waiting" || n.status === "running" ? { ...n, status: "done" as const } : n)) : raw; if (nodes.length === 0) { return (
运行后逐节点点亮执行轨迹 记忆召回 → 工具调用 → 提示词 → 模型推理 / 报告:规划 → 各章并行 → 渲染
); } const total = nodes.reduce((s, n) => s + (n.ms ?? 0), 0); return (
{!compact && (
{nodes.length} 个节点 · 累计 {total} ms {phase === "streaming" && ● 执行中} {phase === "done" && ✓ 完成} {phase === "error" && ✗ 出错}
)}
    {nodes.map((n) => { const m = meta(n.kind); const Icon = m.icon; return (
  1. {n.label} {m.name} {n.ms != null && n.ms > 0 && {n.ms} ms} {n.status === "running" && 运行中…}
    {n.detail &&

    {n.detail}

    } {n.notes.map((note, i) => (

    {note}

    ))}
  2. ); })}
); }