From 5823fbfa7297ee8505b448547da1f97b39debbc3 Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Sat, 11 Apr 2026 14:39:38 +0800 Subject: [PATCH] =?UTF-8?q?fix(threads):=20=E5=BF=BD=E7=95=A5=E6=B5=81?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E5=AF=BC=E8=87=B4=E7=9A=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 识别 cancelled/canceled/abort 等取消信号。\n在流式请求被主动停止或中断时不再弹出错误 toast,减少误报。 --- .../components/workspace/workspace-header.tsx | 2 +- frontend/src/core/threads/hooks.ts | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/workspace/workspace-header.tsx b/frontend/src/components/workspace/workspace-header.tsx index 036f9927..87a7272c 100644 --- a/frontend/src/components/workspace/workspace-header.tsx +++ b/frontend/src/components/workspace/workspace-header.tsx @@ -43,7 +43,7 @@ export function WorkspaceHeader({ className }: { className?: string }) { ) : (
{/* TODO: 测试标识 */} - XClaw v3.2.3 + XClaw v3.2.4
)} diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index c2bb5abe..08e712fe 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -49,6 +49,12 @@ export type LegacyThreadStreamOptions = { const STREAM_ERROR_FALLBACK_MESSAGE = "Request failed."; const STREAM_ERROR_TOAST_MESSAGE = "出现了某些错误。"; const STREAM_ERROR_TOAST_DEDUPE_WINDOW_MS = 2000; +const STREAM_CANCEL_PATTERNS = [ + /\bcancellederror\b/i, + /\bcancelled\b/i, + /\bcanceled\b/i, + /\babort(?:ed|error)?\b/i, +]; function readMessageCandidate(value: unknown): string | null { if (typeof value === "string" && value.trim()) { @@ -116,6 +122,21 @@ function getStreamErrorMessage(error: unknown): string { return STREAM_ERROR_FALLBACK_MESSAGE; } +function isStreamCancellation(error: unknown, message: string): boolean { + const direct = + typeof error === "object" && + error !== null && + "name" in error && + typeof Reflect.get(error, "name") === "string" + ? String(Reflect.get(error, "name")) + : ""; + + const candidates = [message, direct]; + return candidates.some((value) => + STREAM_CANCEL_PATTERNS.some((pattern) => pattern.test(value)), + ); +} + function normalizeThreadId( value: string | null | undefined, ): string | undefined { @@ -234,6 +255,11 @@ export function useThreadStream({ const showStreamErrorToast = useCallback((error: unknown) => { const message = getStreamErrorMessage(error); + if (isStreamCancellation(error, message)) { + // Cancellation is expected when user presses "Stop" or stream disconnects. + console.info("[useThreadStream] stream cancelled:", message); + return; + } const now = Date.now(); const lastToast = lastErrorToastRef.current; if (