From f93b7584ff315965edf924d38bd13d66fdd4b397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 15 Jun 2026 05:30:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20B-32+33=20=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E6=94=B6=E5=B0=BE=E6=B8=85=E9=98=9F=E5=88=97+?= =?UTF-8?q?=E5=9B=9E=E6=BB=9A=20running=20toolCall(=E8=B5=B0=E6=9F=A5=20P0?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - B-32:四路径(onStreamTimeout in useAiStream / AiError case in useAiEvents / stopChat+approveToolCall catch in useAiSend)补 state.queue=[] 清队列——drainQueue 仅 AiCompleted 触发,此前三路径不清致生成中入队消息静默丢失。 - B-33:onStreamTimeout 合并单遍扫描回滚 status===running toolCall→rejected(B-260615-07 残留)——approveToolCall 乐观置 running,后端 hang 时看门狗只复位 streaming 不碰 toolCalls 致卡片永久骨架屏无重审入口,现回滚恢复。 批2 wjg2t6j8d,vue-tsc 0 err --- src/composables/ai/useAiEvents.ts | 1 + src/composables/ai/useAiSend.ts | 2 ++ src/composables/ai/useAiStream.ts | 22 +++++++++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/composables/ai/useAiEvents.ts b/src/composables/ai/useAiEvents.ts index cd32cd9..91d298c 100644 --- a/src/composables/ai/useAiEvents.ts +++ b/src/composables/ai/useAiEvents.ts @@ -218,6 +218,7 @@ export function handleEvent(event: AiChatEvent) { state.streaming = false state.generatingConvId = null state.currentText = '' + state.queue = [] // B-32:错误收尾清队列,防生成中入队的消息被静默丢失(drainQueue 仅 AiCompleted 触发) localStorage.removeItem('df-ai-gen') localStorage.removeItem('df-ai-text') state.messages.push({ diff --git a/src/composables/ai/useAiSend.ts b/src/composables/ai/useAiSend.ts index 691d9f3..6d3507f 100644 --- a/src/composables/ai/useAiSend.ts +++ b/src/composables/ai/useAiSend.ts @@ -95,6 +95,7 @@ async function approveToolCall(toolCallId: string, approved: boolean) { } catch (e) { // IPC 未送达:用户已点过按钮,清看门狗(避免 130s 后误触发假错误)再回滚到失败态 clearStreamWatchdog() + state.queue = [] // B-32:IPC 失败即对话结束,清队列防生成中入队消息静默丢失 // 仅 IPC 真失败(审批已处理/网络断)走到这里:后端工具失败已改走 emit completed,不进此分支 // 不回滚 pending_approval(会卡死按钮),改设 completed + 错误提示,让用户知晓失败 console.error('[AI] 审批操作未送达后端:', e) @@ -122,6 +123,7 @@ function clearQueue() { async function stopChat() { state.streaming = false // 本地先复位,不依赖后端 AiCompleted(审批态看门狗已 clear,竞态丢失则卡死) clearStreamWatchdog() // 清残留看门狗 + state.queue = [] // B-32:用户主动停止,清队列防生成中入队的消息被静默丢弃 await aiApi.stopChat() } diff --git a/src/composables/ai/useAiStream.ts b/src/composables/ai/useAiStream.ts index 0af4916..0b302d5 100644 --- a/src/composables/ai/useAiStream.ts +++ b/src/composables/ai/useAiStream.ts @@ -25,20 +25,28 @@ export function onStreamTimeout() { state.streaming = false state.generatingConvId = null state.currentText = '' + state.queue = [] // B-32:超时收尾同步清队列,防生成中入队的消息静默丢失 clearStreamWatchdog() - // 文案区分:若已有 completed 工具调用,说明工具执行完后续回复被中断(常见为审批后 - // 续生成卡住),引导用户继续;否则按纯流式中断处理。 - // 数据源:state.messages[].toolCalls[].status,反向扫尾部提前退出(O(1) 均,同 findToolCall); - // 不复用 useAiEvents.findToolCall:本模块不依赖 useAiEvents(已下沉 nextMsgId 到 aiShared 破环), - // 且此处需遍历查找任一 completed 卡片而非按 id 定位,语义不同 + // 一次性遍历同时做两件事(单次 O(n),不再分两遍扫): + // 1) 回滚 running 态工具调用 → rejected:approveToolCall 乐观置 running,后端 hang 时 + // 看门狗触发本回调,若不回滚则卡片永久骨架屏(ToolCard:24 渲染 running 无审批按钮,无重审入口)。 + // B-260615-07 残留。 + // 2) 探测任一 completed 卡片以区分错误文案(工具已执行完后续回复中断 vs 纯流式中断)。 + // 反向扫尾部提前退出(同 findToolCall 策略);不复用 useAiEvents.findToolCall:本模块 + // 不依赖 useAiEvents(已下沉 nextMsgId 到 aiShared 破环),且此处需全量回滚 running 而非按 id 定位。 let hasCompletedTool = false for (let i = state.messages.length - 1; i >= 0; i--) { const toolCalls = state.messages[i].toolCalls if (!toolCalls) continue for (let j = 0; j < toolCalls.length; j++) { - if (toolCalls[j].status === 'completed') { hasCompletedTool = true; break } + const tc = toolCalls[j] + if (tc.status === 'running') { + // B-33:running 态无后续事件回流,置 rejected 释放骨架屏,恢复重审入口 + tc.status = 'rejected' + } else if (tc.status === 'completed') { + hasCompletedTool = true + } } - if (hasCompletedTool) break } const content = hasCompletedTool ? '⚠ 工具已执行完成,后续回复中断(长时间无数据流)。可点继续重试。'