重构+修复: ARC-06 composables/ai 循环依赖破环 + B-34 多选审批 select_type 回归
- ARC-06: 提 aiShared.ts 下沉 nextMsgId+_msgCounter(叶子模块),useAiEvents/useAiStream/useAiSend/useAiWindow import 源改 aiShared,useAiStream 不再 import useAiEvents 环消除。watchdog 三函数留 useAiStream(持计时器状态非纯函数)。行为零变化。 - B-34(另一会话走查发现 P0): stores/project.ts:273 selectType→select_type(Tauri 2 IPC 不转 camelCase),对齐后端 workflow.rs:211。F-260615-01 多选审批回归修复——前端传 selectType 后端收 None 归一化 Single 致多选被拒。删错误注释。 批1 wwllb4ith(ARC-06)+主代理(B-34),vue-tsc 0 err
This commit is contained in:
18
src/composables/ai/aiShared.ts
Normal file
18
src/composables/ai/aiShared.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
//! AI composables 共享层 — 多 composable 共用的模块级状态与纯函数
|
||||
//!
|
||||
//! 存在意义:打破 useAiEvents ↔ useAiStream 之间的循环依赖。
|
||||
//! - useAiStream.onStreamTimeout 调用 nextMsgId
|
||||
//! - useAiEvents.handleEvent 调用 useAiStream 的 resetStreamWatchdog/clearStreamWatchdog
|
||||
//! 把两者互相依赖的 nextMsgId(全局消息 id 自增计数器,无其他依赖的纯函数 + 模块级状态)
|
||||
//! 下沉到本模块,使 useAiStream 改为依赖 aiShared 而非 useAiEvents,环即消除。
|
||||
//!
|
||||
//! 下沉范围(严格限定为「无依赖、被多个 composable 共用」的成员):
|
||||
//! - nextMsgId / _msgCounter:events/stream/send/window 四个 composable 共用同一计数器
|
||||
|
||||
/** 客户端消息自增 id(全局唯一,多个 composable 共用 nextMsgId) */
|
||||
let _msgCounter = 0
|
||||
|
||||
/** 全局消息 id 自增(供 events/stream/send/window 各 composable 共享同一计数器) */
|
||||
export function nextMsgId(): number {
|
||||
return ++_msgCounter
|
||||
}
|
||||
@@ -3,18 +3,18 @@
|
||||
//! 模块级私有状态(不进 reactive):
|
||||
//! - _unlistenAiEvent / _unlistenConvChanged: 已注册的 unlistener
|
||||
//! - _startPromise: startListener 并发去重(防 onMounted 与 sendMessage 首发竞态重复注册)
|
||||
//! - _msgCounter: 客户端消息自增 id(全局唯一,多个 composable 共用 nextMsgId)
|
||||
//!
|
||||
//! 耦合:
|
||||
//! - handleEvent 调 useAiConversations.loadConversations、useAiSend.drainQueue、
|
||||
//! useAiStream.{resetStreamWatchdog,clearStreamWatchdog}、本模块 flushCurrentText/findToolCall/notifyConversationChanged
|
||||
//! - onStreamTimeout(useAiStream) 调本模块 nextMsgId
|
||||
//! - nextMsgId 已下沉到 aiShared(原为本模块导出,useAiStream 亦依赖之构成循环依赖,故抽出)
|
||||
|
||||
import { listen, emit } from '@tauri-apps/api/event'
|
||||
import { aiApi } from '../../api'
|
||||
import { useAppSettingsStore } from '../../stores/appSettings'
|
||||
import { state } from '../../stores/ai'
|
||||
import i18n from '../../i18n'
|
||||
import { nextMsgId } from './aiShared'
|
||||
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||
import { drainQueue } from './useAiSend'
|
||||
import { loadConversations } from './useAiConversations'
|
||||
@@ -29,15 +29,9 @@ let _unlistenConvChanged: (() => void) | null = null
|
||||
// startListener 并发去重:防 onMounted 与 sendMessage 首发竞态下重复注册 listener
|
||||
// (两回调写同一 state.currentText → 流式文字双倍)
|
||||
let _startPromise: Promise<void> | null = null
|
||||
let _msgCounter = 0
|
||||
|
||||
const appSettings = useAppSettingsStore()
|
||||
|
||||
/** 全局消息 id 自增(供 events/stream/send/window 各 composable 共享同一计数器) */
|
||||
export function nextMsgId(): number {
|
||||
return ++_msgCounter
|
||||
}
|
||||
|
||||
/** 通知会话列表发生变化(供 newConversation/deleteConversation/rename/archive 等触发刷新侧栏) */
|
||||
export function notifyConversationChanged() {
|
||||
emit('ai-conversation-changed', {})
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useAppSettingsStore } from '../../stores/appSettings'
|
||||
import { state } from '../../stores/ai'
|
||||
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||
import { startListener } from './useAiEvents'
|
||||
import { nextMsgId } from './useAiEvents'
|
||||
import { nextMsgId } from './aiShared'
|
||||
|
||||
const appSettings = useAppSettingsStore()
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
//! - sendMessage 启动生成时 resetStreamWatchdog() 启动计时
|
||||
//! - handleEvent 每个活跃事件(delta/工具/新轮/审批结果)重置;审批等待/完成/错误 clear
|
||||
//! - 超时回调 onStreamTimeout 直接改 state 并补一条错误消息
|
||||
//!
|
||||
//! 依赖:nextMsgId 取自 aiShared(原从 useAiEvents 取,构成 useAiEvents ↔ useAiStream
|
||||
//! 循环依赖;下沉到 aiShared 后本模块不再 import useAiEvents,环消除)
|
||||
|
||||
import { state } from '../../stores/ai'
|
||||
import { nextMsgId } from './useAiEvents'
|
||||
import { nextMsgId } from './aiShared'
|
||||
|
||||
/// ≥ 后端 STREAM_IDLE_TIMEOUT(120s, ai.rs:848)+余量;
|
||||
/// 前端若短于后端,慢首 token 会被前端先误杀
|
||||
@@ -26,7 +29,8 @@ export function onStreamTimeout() {
|
||||
// 文案区分:若已有 completed 工具调用,说明工具执行完后续回复被中断(常见为审批后
|
||||
// 续生成卡住),引导用户继续;否则按纯流式中断处理。
|
||||
// 数据源:state.messages[].toolCalls[].status,反向扫尾部提前退出(O(1) 均,同 findToolCall);
|
||||
// 不复用 useAiEvents.findToolCall 避免反向 import 循环依赖(useAiEvents 已 import 本模块)
|
||||
// 不复用 useAiEvents.findToolCall:本模块不依赖 useAiEvents(已下沉 nextMsgId 到 aiShared 破环),
|
||||
// 且此处需遍历查找任一 completed 卡片而非按 id 定位,语义不同
|
||||
let hasCompletedTool = false
|
||||
for (let i = state.messages.length - 1; i >= 0; i--) {
|
||||
const toolCalls = state.messages[i].toolCalls
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
//!
|
||||
//! 耦合:
|
||||
//! - detachPanel/resumeInDetached 调 useAiConversations.switchConversation
|
||||
//! - resumeInDetached 调 useAiEvents.nextMsgId 占位 ai 气泡
|
||||
//! - resumeInDetached 调 aiShared.nextMsgId 占位 ai 气泡
|
||||
//! - reattachPanel/detachPanel 调 useAiPanel.persistUiState
|
||||
|
||||
import { state } from '../../stores/ai'
|
||||
import { nextMsgId } from './useAiEvents'
|
||||
import { nextMsgId } from './aiShared'
|
||||
import { switchConversation } from './useAiConversations'
|
||||
import { persistUiState } from './useAiPanel'
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ function createStore() {
|
||||
comment,
|
||||
options: state.pendingApproval.options ?? [],
|
||||
decisions: decisions ?? [],
|
||||
selectType: state.pendingApproval.select_type ?? 'single', // Tauri camelCase→snake_case 自动转 select_type
|
||||
select_type: state.pendingApproval.select_type ?? 'single', // B-260615-34:Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211(同 invoke 其余字段 execution_id/node_id/decisions 均已 snake_case)
|
||||
})
|
||||
|
||||
// 清除待审批状态
|
||||
|
||||
Reference in New Issue
Block a user