Show cancel button for backend active runs

This commit is contained in:
wuyang6
2026-05-14 16:43:06 +08:00
parent e48c0ff981
commit e10cb6909b
@@ -58,6 +58,7 @@ import {
import { MarkdownText } from "@/components/assistant-ui/markdown-text"; import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { Reasoning } from "@/components/assistant-ui/reasoning"; import { Reasoning } from "@/components/assistant-ui/reasoning";
import { import {
type ClawRunStatus,
fetchLatestRunStatus, fetchLatestRunStatus,
toReplayRepository, toReplayRepository,
} from "@/components/assistant-ui/thread-list"; } from "@/components/assistant-ui/thread-list";
@@ -925,6 +926,8 @@ const ComposerAction: FC = () => {
}); });
const [runtimeCancelling, setRuntimeCancelling] = useState(false); const [runtimeCancelling, setRuntimeCancelling] = useState(false);
const cancelSessionId = replayedRun.sessionId ?? currentSessionId; const cancelSessionId = replayedRun.sessionId ?? currentSessionId;
const backendRunStatus = useBackendActiveRunStatus(cancelSessionId);
const cancelRunId = replayedRun.runId ?? backendRunStatus?.run_id ?? null;
useEffect(() => { useEffect(() => {
if (!runtimeRunning) setRuntimeCancelling(false); if (!runtimeRunning) setRuntimeCancelling(false);
}, [runtimeRunning]); }, [runtimeRunning]);
@@ -935,7 +938,7 @@ const ComposerAction: FC = () => {
<ComposerAssistButtons /> <ComposerAssistButtons />
</div> </div>
<ComposerContextStatus /> <ComposerContextStatus />
{!runtimeRunning && !replayedRun.active ? ( {!runtimeRunning && !replayedRun.active && !backendRunStatus ? (
<ComposerPrimitive.Send asChild> <ComposerPrimitive.Send asChild>
<TooltipIconButton <TooltipIconButton
tooltip="Send message" tooltip="Send message"
@@ -961,11 +964,12 @@ const ComposerAction: FC = () => {
onClick={() => { onClick={() => {
if (!cancelSessionId) return; if (!cancelSessionId) return;
setRuntimeCancelling(true); setRuntimeCancelling(true);
void cancelLatestRun(cancelSessionId, replayedRun.runId) void cancelLatestRun(cancelSessionId, cancelRunId)
.catch(() => undefined) .catch(() => undefined)
.finally(() => { .finally(() => {
// 后端可能已经 idle;无论取消接口是否真的命中 run,都清理本地残留运行态。 // 后端可能已经 idle;无论取消接口是否真的命中 run,都清理本地残留运行态。
aui.thread().cancelRun(); aui.thread().cancelRun();
window.dispatchEvent(new Event("claw-sessions-changed"));
window.setTimeout(() => setRuntimeCancelling(false), 1200); window.setTimeout(() => setRuntimeCancelling(false), 1200);
}); });
}} }}
@@ -973,16 +977,48 @@ const ComposerAction: FC = () => {
<SquareIcon className="aui-composer-cancel-icon size-3 fill-current" /> <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
</Button> </Button>
) : null} ) : null}
{!runtimeRunning && replayedRun.active ? ( {!runtimeRunning && (replayedRun.active || backendRunStatus) ? (
<ReplayedRunCancelButton <ReplayedRunCancelButton
sessionId={replayedRun.sessionId} sessionId={cancelSessionId}
runId={replayedRun.runId} runId={cancelRunId}
/> />
) : null} ) : null}
</div> </div>
); );
}; };
function useBackendActiveRunStatus(sessionId: string | null) {
const [status, setStatus] = useState<ClawRunStatus | null>(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<{ const ReplayedRunCancelButton: FC<{
sessionId: string | null; sessionId: string | null;
runId: string | null; runId: string | null;