import { useState } from "react"; import { ShieldCheck, Check, X } from "lucide-react"; import { pendingApproval, type RunState } from "../lib/run"; import { approveTask } from "../lib/api"; import { Badge } from "../ui"; // ApprovalBar:HITL 人工审批中断条 —— 全局常驻(任务停在审批节点时,不管在哪个页面都要能看见+操作)。 // 触发以「后端状态 waiting」为准(落 PG,可靠);exec 的 await 事件仅用于丰富摘要(可能抢跑丢失)。 // 决定经 approveTask 发回;dispatcher 续跑后状态轮询/SSE 更新,本条随 waiting 解除自动消失。 export function ApprovalBar({ run }: { run: RunState }) { const approval = run.taskId && run.lifecycle === "waiting" ? pendingApproval(run.exec) ?? { node: "", title: run.detail || "人工审批", summary: "" } : null; if (!approval || !run.taskId) return null; // key=taskId:换任务时重新挂载,重置 submitted 等本地状态,避免上一个审批的「已提交」残留。 return ; } function Bar({ taskId, node, title, summary }: { taskId: string; node: string; title: string; summary: string }) { const [note, setNote] = useState(""); const [busy, setBusy] = useState<"approve" | "reject" | null>(null); const [submitted, setSubmitted] = useState(false); // 决定已发出;正常情况下任务续跑后本条随 waiting 解除自动消失 const [err, setErr] = useState(""); const decide = async (approved: boolean) => { setBusy(approved ? "approve" : "reject"); setErr(""); try { await approveTask(taskId, approved, { node, note }); setSubmitted(true); // 关键:成功后置「已提交」而非停在 spinner —— 否则任务若恢复不了会永远卡「提交中…」 } catch (e) { setErr((e as Error).message); } finally { setBusy(null); } }; return (
人工审批 {title} {submitted ? "已提交决定" : "等待决定"}
{summary &&
{summary}
} {submitted ? (

决定已发出,等待任务续跑…(若长时间无响应,该任务可能已失效)

) : (
setNote(e.target.value)} placeholder="备注(可选,拒绝原因等)" className="min-w-0 flex-1 rounded border border-line bg-ink-950/60 px-2 py-1 text-[11px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500/50 focus:outline-none" />
)} {err &&

提交失败:{err}

}
); }