重构: aichat 双轨状态机收口 + AiCompressed 事件拆分
- generating bool + CONV_STATE_ENABLED 开关双轨退役,ConvState enum 单一真相源 - can_accept_request 接入 chat 域入口(覆盖 Stopping 竞态,严谨于 is_active) - AiCompressed 拆 AiManualCompressed/AiAutoCompressed(治自动压缩误触桌面toast+刷新) - convStates/getConvState 下沉 aiShared.ts 破循环依赖
This commit is contained in:
@@ -7,14 +7,15 @@
|
||||
//! - 模块级 listener:消费后端 emit 的 ai-chat-event 生命周期事件(serde 默认 PascalCase,
|
||||
//! 对齐 useAiEvents.ts 既有 9 事件):
|
||||
//! AiCompressing → isCompressing=true
|
||||
//! AiCompressed → isCompressing=false + (成功 toast 由调用方弹,摘要已在事件里)
|
||||
//! AiManualCompressed → isCompressing=false + (手动 IPC,成功 toast 由调用方弹,摘要已在事件里)
|
||||
//! AiAutoCompressed → isCompressing=false + (loop 自动压缩,静默仅复位,治 Task#1 不弹 toast)
|
||||
//! AiContextCleared → (刷新提示由调用方弹)
|
||||
//! AiError → isCompressing=false + (错误 toast 由调用方弹)
|
||||
//!
|
||||
//! 设计:
|
||||
//! - 与 useAiEvents.startListener 共存:后者监听 AiChatEvent 联合类型(不含
|
||||
//! AiCompressing/AiCompressed/AiContextCleared,因 types.ts 不在本任务白名单),
|
||||
//! 本模块注册第二个 listen('ai-chat-event') 监听器,专门按 type 字符串分发上述 4 类事件。
|
||||
//! AiCompressing/AiManualCompressed|AiAutoCompressed/AiContextCleared,因 types.ts 不在本任务白名单),
|
||||
//! 本模块注册第二个 listen('ai-chat-event') 监听器,专门按 type 字符串分发上述 5 类事件。
|
||||
//! 两个监听器都触发同一后端事件,但各取所需类型,Tauri 事件多 listener 是合法的。
|
||||
//! - toast/刷新副作用经回调注入(composable 无组件上下文,无 toast ref),
|
||||
//! AiChat.vue 调 initContextListener(onCleared/onCompressed/onError) 注入副作用,
|
||||
@@ -32,7 +33,7 @@ import { aiApi } from '@/api'
|
||||
import { state } from '@/stores/ai'
|
||||
import { resolveAiLang } from './aiShared'
|
||||
|
||||
/// 压缩中 loading(防重复点击;AiCompressing→true / AiCompressed|AiError→false)
|
||||
/// 压缩中 loading(防重复点击;AiCompressing→true / AiManualCompressed|AiAutoCompressed|AiError→false)
|
||||
const isCompressing = ref(false)
|
||||
|
||||
/// 已注册的 ai-chat-event 第二监听器(生命周期事件);幂等
|
||||
@@ -43,7 +44,8 @@ let _unlistenContext: UnlistenFn | null = null
|
||||
interface ContextCallbacks {
|
||||
/** 清空完成(AiContextCleared):toast + 刷新消息列表 */
|
||||
onCleared?: (conversationId: string) => void
|
||||
/** 压缩成功(AiCompressed):toast + 渲染摘要 system 消息 */
|
||||
/** 手动压缩成功(AiManualCompressed):toast + 渲染摘要 system 消息。
|
||||
* 注:仅 AiManualCompressed 触发;AiAutoCompressed(loop 自动)静默不进此回调(治 Task#1)。 */
|
||||
onCompressed?: (conversationId: string, summary: string) => void
|
||||
/** 压缩失败 / 错误(AiError with conversation_id):toast */
|
||||
onError?: (conversationId: string, message: string) => void
|
||||
@@ -76,10 +78,18 @@ async function initContextListener(callbacks: ContextCallbacks = {}): Promise<vo
|
||||
case 'AiCompressing':
|
||||
isCompressing.value = true
|
||||
break
|
||||
case 'AiCompressed':
|
||||
case 'AiManualCompressed':
|
||||
// 手动 IPC 压缩(用户点压缩按钮):复位 isCompressing + 弹 toast + 刷新视图回填摘要。
|
||||
isCompressing.value = false
|
||||
_callbacks.onCompressed?.(convId || '', payload.summary || '')
|
||||
break
|
||||
case 'AiAutoCompressed':
|
||||
// 治 Task#1:loop 自动压缩对桌面静默——仅复位 isCompressing(冗余兜底,Auto 路径
|
||||
// 前端 isCompressing 本就为 false,只有手动 compressContext 才置 true),不调
|
||||
// onCompressed(防每次发送误弹 toast + 误 switchConversation 打断 LLM 流)。
|
||||
// 摘要 system 消息已由后端 insert_at,后续 stream 自然带出,无需整会话刷新。
|
||||
isCompressing.value = false
|
||||
break
|
||||
case 'AiContextCleared':
|
||||
_callbacks.onCleared?.(convId || '')
|
||||
break
|
||||
@@ -122,7 +132,7 @@ async function compressContext(): Promise<void> {
|
||||
try {
|
||||
await aiApi.compressContext(convId, resolveAiLang())
|
||||
// 后端 IPC 同步等到 LLM 压缩完成才 resolve(非 spawn 异步),成功即复位 isCompressing;
|
||||
// case 'AiCompressed' 事件也复位(双保险,防事件丢失致按钮永久禁用)
|
||||
// case 'AiManualCompressed' 事件也复位(双保险,防事件丢失致按钮永久禁用)
|
||||
isCompressing.value = false
|
||||
} catch (e) {
|
||||
isCompressing.value = false
|
||||
|
||||
Reference in New Issue
Block a user