From 7a2a2e13a29acadba9936adc905d279e862933b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 29 Jun 2026 12:33:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=B0=B8=E8=BF=9C=E6=98=BE=E7=A4=BA=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:aiShared 的消息引用在会话切换时失效。 switchConversation 和 newConversation 会整体替换 state.messages (赋值新数组),但 aiShared 缓存的是旧数组引用。切换后 findToolCall 在旧数组中找不到新消息的工具卡片,AiToolCallCompleted 事件无法更新卡片状态,卡片永远停在 running。 修复:缓存数组引用改为 getter 函数,每次调用实时从 state 读取 最新数组,确保切换会话后仍能找到工具卡片。 --- src/composables/ai/aiShared.ts | 26 ++++++++++++++++++-------- src/stores/ai.ts | 6 ++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/composables/ai/aiShared.ts b/src/composables/ai/aiShared.ts index 8be95ea..467787a 100644 --- a/src/composables/ai/aiShared.ts +++ b/src/composables/ai/aiShared.ts @@ -34,25 +34,35 @@ export function resolveAiLang(): string { : raw } -// ── Lazy messages ref(破 stores/ai ↔ aiShared 循环依赖) ── +// ── Lazy messages getter(破 stores/ai ↔ aiShared 循环依赖) ── // // stores/ai.ts import aiShared.ts,而 aiShared.ts 若在模块顶层 import // stores/ai.ts 的 `state`,Vite 打包后可能因循环引用导致 TDZ 崩溃 // (ReferenceError: Cannot access 'u' before initialization)。 -// 策略:stores/ai.ts 在 state 就绪后主动注入 messages 数组引用, -// aiShared 函数在运行时通过 getMessages() 惰性获取,不再模块顶 import。 -let _messages: AiMessage[] | null = null +// 策略:stores/ai.ts 在 state 就绪后注入 getter 函数, +// aiShared 函数在运行时通过 getMessages() 惰性调用 getter 获取最新数组。 +// +// 重要:getter 而非缓存引用 —— state.messages 在 switchConversation/newConversation +// 时被整体替换(state.messages = []),缓存引用会指向陈旧数组(findToolCall 找不到 +// 新数组中的工具卡片 → AiToolCallCompleted 无法更新 status → 卡片永远 running)。 +let _messagesGetter: (() => AiMessage[]) | null = null /** - * @internal 由 stores/ai.ts 在 state 创建后调用,注入响应式 messages 引用。 + * @internal 由 stores/ai.ts 在 state 创建后调用,注入 getter 函数。 * 本模块内 findToolCall / startApprovalTimer 通过 getMessages() 访问。 + * 用 getter 而非缓存数组引用,确保 switchConversation 等整体替换 messages 后仍可读到最新数组。 */ -export function __bindMessages(m: AiMessage[]): void { - _messages = m +export function __bindMessages(getter: (() => AiMessage[]) | AiMessage[]): void { + // 兼容旧 API:传数组则包装为 getter(避免破坏既有调用方) + if (typeof getter === 'function') { + _messagesGetter = getter + } else { + _messagesGetter = () => getter + } } function getMessages(): AiMessage[] { - return _messages ?? [] + return _messagesGetter?.() ?? [] } /** 客户端消息自增 id(全局唯一,多个 composable 共用 nextMsgId) */ diff --git a/src/stores/ai.ts b/src/stores/ai.ts index a675e22..cdd8d32 100644 --- a/src/stores/ai.ts +++ b/src/stores/ai.ts @@ -190,8 +190,10 @@ export const state = reactive(_stateBase) as typeof _stateBase & { } // 回填 stateRef:accessor 闭包读此引用(经 reactive 包装后的代理对象,activeConversationId 变化可追踪)。 stateRef = state -// 注入 messages 引用到 aiShared 共享层(破循环依赖,避免 Vite 打包 TDZ)。 -__bindMessages(state.messages) +// 注入 messages getter 到 aiShared 共享层(破循环依赖,避免 Vite 打包 TDZ)。 +// 用 getter 而非数组引用:switchConversation/newConversation 会整体替换 state.messages, +// 缓存引用会指向陈旧数组(findToolCall 找不到新数组的工具卡片 → Completed 事件无法更新 → 卡片卡 running)。 +__bindMessages(() => state.messages) // 旁注:此处不再保留 type-only 导出(AiChatEvent 等),因组件直接从 api/types import。 // 若有外部模块仍从本文件 import 这些类型,下方 re-export 兜底: