diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index a26fe77..9cdeeb4 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -58,6 +58,7 @@ import { import { MarkdownText } from "@/components/assistant-ui/markdown-text"; import { Reasoning } from "@/components/assistant-ui/reasoning"; import { + type ClawRunStatus, fetchLatestRunStatus, toReplayRepository, } from "@/components/assistant-ui/thread-list"; @@ -925,6 +926,8 @@ const ComposerAction: FC = () => { }); const [runtimeCancelling, setRuntimeCancelling] = useState(false); const cancelSessionId = replayedRun.sessionId ?? currentSessionId; + const backendRunStatus = useBackendActiveRunStatus(cancelSessionId); + const cancelRunId = replayedRun.runId ?? backendRunStatus?.run_id ?? null; useEffect(() => { if (!runtimeRunning) setRuntimeCancelling(false); }, [runtimeRunning]); @@ -935,7 +938,7 @@ const ComposerAction: FC = () => { - {!runtimeRunning && !replayedRun.active ? ( + {!runtimeRunning && !replayedRun.active && !backendRunStatus ? ( { onClick={() => { if (!cancelSessionId) return; setRuntimeCancelling(true); - void cancelLatestRun(cancelSessionId, replayedRun.runId) + void cancelLatestRun(cancelSessionId, cancelRunId) .catch(() => undefined) .finally(() => { // 后端可能已经 idle;无论取消接口是否真的命中 run,都清理本地残留运行态。 aui.thread().cancelRun(); + window.dispatchEvent(new Event("claw-sessions-changed")); window.setTimeout(() => setRuntimeCancelling(false), 1200); }); }} @@ -973,16 +977,48 @@ const ComposerAction: FC = () => { ) : null} - {!runtimeRunning && replayedRun.active ? ( + {!runtimeRunning && (replayedRun.active || backendRunStatus) ? ( ) : null} ); }; +function useBackendActiveRunStatus(sessionId: string | null) { + const [status, setStatus] = useState(null); + + useEffect(() => { + if (!sessionId) { + setStatus(null); + return; + } + const safeSessionId = sessionId; + let cancelled = false; + + async function refresh() { + const nextStatus = await fetchLatestRunStatus(safeSessionId); + if (cancelled) return; + setStatus(isActiveRunStatus(nextStatus) ? nextStatus : null); + } + + void refresh(); + const interval = window.setInterval(refresh, 1000); + return () => { + cancelled = true; + window.clearInterval(interval); + }; + }, [sessionId]); + + return status; +} + +function isActiveRunStatus(status?: ClawRunStatus | null) { + return status?.status === "queued" || status?.status === "running"; +} + const ReplayedRunCancelButton: FC<{ sessionId: string | null; runId: string | null;