diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 45f52c9..1954d4c 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -746,6 +746,10 @@ watch(() => store.state.streaming, (s) => { }) onBeforeUnmount(() => { if (rafId !== null) cancelAnimationFrame(rafId) + // CR-260615-24: 释放后端 listener(ai-chat-event/ai-conversation-changed)+清流式看门狗。 + // 之前 stopListener 定义但全仓零调用 → listener+watchdog 永久泄漏;分离窗口主窗口 AiChat + // 卸载(走 App.vue v-if detach)时 listener 仍挂后端,watchdog 130s 后写已卸载 state。 + store.stopListener() }) // ── 工具卡片自动收起:新内容追加(新消息/toolCall 状态变化/文本增长)时, diff --git a/src/composables/ai/useAiEvents.ts b/src/composables/ai/useAiEvents.ts index 753eec4..88abe9c 100644 --- a/src/composables/ai/useAiEvents.ts +++ b/src/composables/ai/useAiEvents.ts @@ -254,12 +254,15 @@ export async function startListener() { } } -/** 停止事件监听(卸载时调用,释放后端 listener) */ +/** 停止事件监听(卸载时调用,释放后端 listener + 清看门狗) + * 清看门狗:卸载时若仍在生成,_streamWatchdog 计时器未释放,130s 后 onStreamTimeout + * 仍写 state(messages.push/置 streaming)——已卸载组件不应再被触发。故同步清除。 */ function stopListener() { _unlistenAiEvent?.() _unlistenConvChanged?.() _unlistenAiEvent = null _unlistenConvChanged = null + clearStreamWatchdog() } export function useAiEvents() {