重构+修复: 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):
|
//! 模块级私有状态(不进 reactive):
|
||||||
//! - _unlistenAiEvent / _unlistenConvChanged: 已注册的 unlistener
|
//! - _unlistenAiEvent / _unlistenConvChanged: 已注册的 unlistener
|
||||||
//! - _startPromise: startListener 并发去重(防 onMounted 与 sendMessage 首发竞态重复注册)
|
//! - _startPromise: startListener 并发去重(防 onMounted 与 sendMessage 首发竞态重复注册)
|
||||||
//! - _msgCounter: 客户端消息自增 id(全局唯一,多个 composable 共用 nextMsgId)
|
|
||||||
//!
|
//!
|
||||||
//! 耦合:
|
//! 耦合:
|
||||||
//! - handleEvent 调 useAiConversations.loadConversations、useAiSend.drainQueue、
|
//! - handleEvent 调 useAiConversations.loadConversations、useAiSend.drainQueue、
|
||||||
//! useAiStream.{resetStreamWatchdog,clearStreamWatchdog}、本模块 flushCurrentText/findToolCall/notifyConversationChanged
|
//! useAiStream.{resetStreamWatchdog,clearStreamWatchdog}、本模块 flushCurrentText/findToolCall/notifyConversationChanged
|
||||||
//! - onStreamTimeout(useAiStream) 调本模块 nextMsgId
|
//! - nextMsgId 已下沉到 aiShared(原为本模块导出,useAiStream 亦依赖之构成循环依赖,故抽出)
|
||||||
|
|
||||||
import { listen, emit } from '@tauri-apps/api/event'
|
import { listen, emit } from '@tauri-apps/api/event'
|
||||||
import { aiApi } from '../../api'
|
import { aiApi } from '../../api'
|
||||||
import { useAppSettingsStore } from '../../stores/appSettings'
|
import { useAppSettingsStore } from '../../stores/appSettings'
|
||||||
import { state } from '../../stores/ai'
|
import { state } from '../../stores/ai'
|
||||||
import i18n from '../../i18n'
|
import i18n from '../../i18n'
|
||||||
|
import { nextMsgId } from './aiShared'
|
||||||
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||||
import { drainQueue } from './useAiSend'
|
import { drainQueue } from './useAiSend'
|
||||||
import { loadConversations } from './useAiConversations'
|
import { loadConversations } from './useAiConversations'
|
||||||
@@ -29,15 +29,9 @@ let _unlistenConvChanged: (() => void) | null = null
|
|||||||
// startListener 并发去重:防 onMounted 与 sendMessage 首发竞态下重复注册 listener
|
// startListener 并发去重:防 onMounted 与 sendMessage 首发竞态下重复注册 listener
|
||||||
// (两回调写同一 state.currentText → 流式文字双倍)
|
// (两回调写同一 state.currentText → 流式文字双倍)
|
||||||
let _startPromise: Promise<void> | null = null
|
let _startPromise: Promise<void> | null = null
|
||||||
let _msgCounter = 0
|
|
||||||
|
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
|
|
||||||
/** 全局消息 id 自增(供 events/stream/send/window 各 composable 共享同一计数器) */
|
|
||||||
export function nextMsgId(): number {
|
|
||||||
return ++_msgCounter
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 通知会话列表发生变化(供 newConversation/deleteConversation/rename/archive 等触发刷新侧栏) */
|
/** 通知会话列表发生变化(供 newConversation/deleteConversation/rename/archive 等触发刷新侧栏) */
|
||||||
export function notifyConversationChanged() {
|
export function notifyConversationChanged() {
|
||||||
emit('ai-conversation-changed', {})
|
emit('ai-conversation-changed', {})
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useAppSettingsStore } from '../../stores/appSettings'
|
|||||||
import { state } from '../../stores/ai'
|
import { state } from '../../stores/ai'
|
||||||
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||||
import { startListener } from './useAiEvents'
|
import { startListener } from './useAiEvents'
|
||||||
import { nextMsgId } from './useAiEvents'
|
import { nextMsgId } from './aiShared'
|
||||||
|
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
//! - sendMessage 启动生成时 resetStreamWatchdog() 启动计时
|
//! - sendMessage 启动生成时 resetStreamWatchdog() 启动计时
|
||||||
//! - handleEvent 每个活跃事件(delta/工具/新轮/审批结果)重置;审批等待/完成/错误 clear
|
//! - handleEvent 每个活跃事件(delta/工具/新轮/审批结果)重置;审批等待/完成/错误 clear
|
||||||
//! - 超时回调 onStreamTimeout 直接改 state 并补一条错误消息
|
//! - 超时回调 onStreamTimeout 直接改 state 并补一条错误消息
|
||||||
|
//!
|
||||||
|
//! 依赖:nextMsgId 取自 aiShared(原从 useAiEvents 取,构成 useAiEvents ↔ useAiStream
|
||||||
|
//! 循环依赖;下沉到 aiShared 后本模块不再 import useAiEvents,环消除)
|
||||||
|
|
||||||
import { state } from '../../stores/ai'
|
import { state } from '../../stores/ai'
|
||||||
import { nextMsgId } from './useAiEvents'
|
import { nextMsgId } from './aiShared'
|
||||||
|
|
||||||
/// ≥ 后端 STREAM_IDLE_TIMEOUT(120s, ai.rs:848)+余量;
|
/// ≥ 后端 STREAM_IDLE_TIMEOUT(120s, ai.rs:848)+余量;
|
||||||
/// 前端若短于后端,慢首 token 会被前端先误杀
|
/// 前端若短于后端,慢首 token 会被前端先误杀
|
||||||
@@ -26,7 +29,8 @@ export function onStreamTimeout() {
|
|||||||
// 文案区分:若已有 completed 工具调用,说明工具执行完后续回复被中断(常见为审批后
|
// 文案区分:若已有 completed 工具调用,说明工具执行完后续回复被中断(常见为审批后
|
||||||
// 续生成卡住),引导用户继续;否则按纯流式中断处理。
|
// 续生成卡住),引导用户继续;否则按纯流式中断处理。
|
||||||
// 数据源:state.messages[].toolCalls[].status,反向扫尾部提前退出(O(1) 均,同 findToolCall);
|
// 数据源:state.messages[].toolCalls[].status,反向扫尾部提前退出(O(1) 均,同 findToolCall);
|
||||||
// 不复用 useAiEvents.findToolCall 避免反向 import 循环依赖(useAiEvents 已 import 本模块)
|
// 不复用 useAiEvents.findToolCall:本模块不依赖 useAiEvents(已下沉 nextMsgId 到 aiShared 破环),
|
||||||
|
// 且此处需遍历查找任一 completed 卡片而非按 id 定位,语义不同
|
||||||
let hasCompletedTool = false
|
let hasCompletedTool = false
|
||||||
for (let i = state.messages.length - 1; i >= 0; i--) {
|
for (let i = state.messages.length - 1; i >= 0; i--) {
|
||||||
const toolCalls = state.messages[i].toolCalls
|
const toolCalls = state.messages[i].toolCalls
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
//!
|
//!
|
||||||
//! 耦合:
|
//! 耦合:
|
||||||
//! - detachPanel/resumeInDetached 调 useAiConversations.switchConversation
|
//! - detachPanel/resumeInDetached 调 useAiConversations.switchConversation
|
||||||
//! - resumeInDetached 调 useAiEvents.nextMsgId 占位 ai 气泡
|
//! - resumeInDetached 调 aiShared.nextMsgId 占位 ai 气泡
|
||||||
//! - reattachPanel/detachPanel 调 useAiPanel.persistUiState
|
//! - reattachPanel/detachPanel 调 useAiPanel.persistUiState
|
||||||
|
|
||||||
import { state } from '../../stores/ai'
|
import { state } from '../../stores/ai'
|
||||||
import { nextMsgId } from './useAiEvents'
|
import { nextMsgId } from './aiShared'
|
||||||
import { switchConversation } from './useAiConversations'
|
import { switchConversation } from './useAiConversations'
|
||||||
import { persistUiState } from './useAiPanel'
|
import { persistUiState } from './useAiPanel'
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ function createStore() {
|
|||||||
comment,
|
comment,
|
||||||
options: state.pendingApproval.options ?? [],
|
options: state.pendingApproval.options ?? [],
|
||||||
decisions: decisions ?? [],
|
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