From e8dc2e02c3fc9cd515c72c7bce82ffd3e90e4661 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 29 Jun 2026 16:40:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E5=AE=A1=E6=89=B9=E6=9D=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=90=8E=E4=B8=8D=E5=86=8D=E6=B0=B8=E5=8D=A1?= =?UTF-8?q?=E3=80=8C=E6=8F=90=E4=BA=A4=E4=B8=AD=E3=80=8D+=20=E6=8C=89=20ta?= =?UTF-8?q?skId=20=E9=87=8D=E6=8C=82=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复现:登录恢复待审任务时若捞到一个无法续跑的任务(如早期 KV 冒号 bug 留下的无 resume 记录的孤儿),点批准 → 决定发出但消费者找不到记录 → 任务永远 waiting → 审批条 busy 状态成功后从不复位 → 永卡「提交中…」spinner,看似整个应用卡死。 - decide 成功后置 submitted 并在 finally 复位 busy(此前只在 catch 复位)。 - submitted 时显示「决定已发出,等待续跑…(若长时间无响应,该任务可能已失效)」 而非停在 spinner —— 诚实反映状态,不误导成 hang。 - Bar 加 key={taskId}:换审批任务时重挂载,避免上一个的 submitted 残留。 (孤儿任务本身已在 PG 标记 failed 清理;冒号 bug 早已修复,不再产生新孤儿。) Co-Authored-By: Claude Opus 4.8 --- .../frontend/src/shell/ApprovalBar.tsx | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/sundynix-desktop/frontend/src/shell/ApprovalBar.tsx b/sundynix-desktop/frontend/src/shell/ApprovalBar.tsx index 7d62eb0..8a3e8a8 100644 --- a/sundynix-desktop/frontend/src/shell/ApprovalBar.tsx +++ b/sundynix-desktop/frontend/src/shell/ApprovalBar.tsx @@ -13,12 +13,14 @@ export function ApprovalBar({ run }: { run: RunState }) { ? pendingApproval(run.exec) ?? { node: "", title: run.detail || "人工审批", summary: "" } : null; if (!approval || !run.taskId) return null; - return ; + // 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) => { @@ -26,8 +28,10 @@ function Bar({ taskId, node, title, summary }: { taskId: string; node: string; t setErr(""); try { await approveTask(taskId, approved, { node, note }); + setSubmitted(true); // 关键:成功后置「已提交」而非停在 spinner —— 否则任务若恢复不了会永远卡「提交中…」 } catch (e) { setErr((e as Error).message); + } finally { setBusy(null); } }; @@ -38,25 +42,29 @@ function Bar({ taskId, node, title, summary }: { taskId: string; node: string; t 人工审批 {title} - 等待决定 + {submitted ? "已提交决定" : "等待决定"} {summary &&
{summary}
} -
- 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" - /> - - -
+ {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}

} );