diff --git a/sundynix-admin/src/components/TaskDetailDrawer.tsx b/sundynix-admin/src/components/TaskDetailDrawer.tsx index 30603c4..9411f17 100644 --- a/sundynix-admin/src/components/TaskDetailDrawer.tsx +++ b/sundynix-admin/src/components/TaskDetailDrawer.tsx @@ -9,7 +9,38 @@ import { adminTaskDetail, type AdminTask, type AdminTaskDetail } from "../api"; // // 不含审批操作:审批是客户端用户的行为(桌面端 ApprovalBar),管理端只做观测。 -type ExecEvent = { node?: string; type?: string; msg?: string; at?: string; [k: string]: unknown }; +// 字段名以库里真实轨迹为准(不是猜的):seq/ts/node/kind/phase/label/detail。 +// ts 是毫秒时间戳;kind ∈ system|model|tool|agent|plan|section|render|approval; +// phase ∈ info|start|end|await|error。 +type ExecEvent = { + seq?: number; + ts?: number; + node?: string; + kind?: string; + phase?: string; + label?: string; + detail?: string; + [k: string]: unknown; +}; + +const KIND_TONE: Record = { + system: "bg-gray-100 text-gray-600", + model: "bg-violet-50 text-violet-600", + tool: "bg-sky-50 text-sky-600", + agent: "bg-indigo-50 text-indigo-600", + plan: "bg-amber-50 text-amber-700", + section: "bg-emerald-50 text-emerald-600", + render: "bg-teal-50 text-teal-600", + approval: "bg-orange-50 text-orange-600", +}; + +// 只有 error/await 值得跳出来:前者是失败点,后者是卡在人工审批。 +const PHASE_TONE: Record = { + error: "text-rose-600 font-medium", + await: "text-orange-600", +}; + +const hhmmss = (ms?: number) => (ms ? new Date(ms).toLocaleTimeString("zh-CN", { hour12: false }) : ""); export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose: () => void }) { const [d, setD] = useState(null); @@ -31,9 +62,12 @@ export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose: }; }, [task.task_id]); + // flags 是库里存的 JSON 字符串,实测可能是 "null"(不是 "[]")——JSON.parse 出来是 null, + // 不校验就会在 flags.length 上抛 TypeError 把整个控制台白屏掉。必须确认真是数组。 const flags: string[] = (() => { try { - return d?.eval?.flags ? (JSON.parse(d.eval.flags) as string[]) : []; + const v: unknown = d?.eval?.flags ? JSON.parse(d.eval.flags) : null; + return Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : []; } catch { return []; } @@ -104,15 +138,29 @@ export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose: ) : (
    {exec.map((e, i) => ( -
  1. +
  2. - #{i + 1} - {e.node && {e.node}} - {e.type && {e.type}} - {e.at && {e.at}} + #{e.seq ?? i + 1} + {e.label && {e.label}} + {e.kind && ( + + {e.kind} + + )} + {e.phase && e.phase !== "info" && ( + {e.phase} + )} + {hhmmss(e.ts)}
    - {e.msg &&

    {e.msg}

    } - {!e.msg && !e.node && ( + {e.node &&
    {e.node}
    } + {e.detail &&

    {e.detail}

    } + {/* 兜底:字段形状变了也别把线索吞掉(上一版就是猜错字段名,白渲染了一列空行) */} + {!e.label && !e.detail && (
    {JSON.stringify(e)}
    )}