重构: 应用状态拆分+事件分发统一+路径授权治本

三项治本性架构重构,解决长期遗留的技术债:

一、应用状态文件拆分(原 1416 行单文件 → 按职责分模块)
- 文件授权白名单(AllowedDirs/路径校验/黑名单)独立成模块
- 知识库行为配置(ExtractTrigger/KnowledgeConfig/KV 常量)独立成模块
- LLM 并发控制(LlmConcurrency 双层信号量)独立成模块
- 主文件仅保留 AppState 结构体与初始化逻辑

二、事件分发器统一(消除双监听器架构隐患)
- 压缩/清空等 5 类生命周期事件纳入主事件联合类型
- 删除上下文管理模块的独立第二监听器
- 改为由主事件分发器统一处理,经回调注入副作用
- 架构保证:一个事件源 → 一个监听器 → 一个分发器

三、路径授权去除编译期硬编码(分发适配治本)
- 启动时不再硬塞编译机路径到授权白名单
- 工程根授权完全靠项目绑定目录自动授权 + 设置页手动配置
- 分发到其他机器后不再有脏白名单数据
This commit is contained in:
2026-06-28 23:35:38 +08:00
parent 696e34407c
commit 996f1d9e5f
8 changed files with 967 additions and 873 deletions

View File

@@ -141,6 +141,29 @@ export interface PendingHelp {
}
export const pendingHelp = ref<PendingHelp | null>(null)
// F-15 上下文管理生命周期事件回调(原 useAiContext 独立监听器,现合入统一分发器)。
// useAiContext.initContextListener 注入回调,useAiEvents.handleLifecycleEvent 统一分发。
// 设计:一个事件源(ai-chat-event)→ 一个监听器(useAiEvents)→ 一个分发器(handleEvent),
// 架构保证无双重处理风险(原 useAiContext 第二监听器已删除)。
export interface ContextCallbacks {
/** 压缩开始(AiCompressing):置 loading=true */
onCompressing?: () => void
/** 手动压缩成功(AiManualCompressed):置 loading=false + toast + 回填摘要 */
onManualCompressed?: (conversationId: string, summary: string) => void
/** 自动压缩成功(AiAutoCompressed):静默置 loading=false(不弹 toast) */
onAutoCompressed?: () => void
/** 上下文已清空(AiContextCleared):toast + 刷新消息列表 */
onCleared?: (conversationId: string) => void
/** 错误(AiError,含压缩失败):仅在 loading 时置 false + toast */
onError?: (conversationId: string, message: string) => void
}
let _contextCallbacks: ContextCallbacks = {}
/** 注入上下文生命周期回调(useAiContext.initContextListener 调用) */
export function setContextCallbacks(callbacks: ContextCallbacks): void {
_contextCallbacks = callbacks
}
// L2 统一状态机 convStates/getConvState/setConvState 已下沉到 aiShared.ts(批4 双轨收口破环:
// stores/ai.ts、useAiStream.ts、useAiConversations.ts 等读点 import getConvState 会与
// useAiEvents 现有依赖构成环,故下沉到本模块既有的破环共享层)。本模块从 aiShared re-import。
@@ -664,11 +687,14 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
errorType: event.error_type,
timestamp: Date.now(),
} as AiMessage)
// F-15 上下文管理:AiError 也用于压缩 IPC 失败场景。通知上下文回调复位 loading +
// 弹错误 toast(若处于压缩中)。与上方流式错误处理不冲突——上下文回调内部判 isCompressing 守卫。
_contextCallbacks.onError?.(event.conversation_id || '', event.error)
return true
}
case 'AiHelpRequired': {
// L1 求助协议(§2.3,2026-06-21):后端断路器断已 guard.reset(generating=false,loop 终止)。
// L1 求助协议(§2.3,2026-06-21):后端断路器断已 guard.reset(generating=false,loop 终止)。
// 前端按终止态收尾(对齐 AiError 分支:清看门狗/流式态/快照/队尾文本 flushCurrentText),
// 但不创建错误气泡(求助非错误,是 AI 主动求助)——改为翻 pendingHelp 驱动求助卡(HelpRequiredCard)
// 显 reason + options 按钮供用户选。
@@ -710,6 +736,28 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
return true
}
// F-15 上下文管理生命周期事件(原 useAiContext 独立监听器,现合入统一分发器)
case 'AiCompressing':
_contextCallbacks.onCompressing?.()
return true
case 'AiManualCompressed': {
// 手动 IPC 压缩(用户点压缩按钮):复位 loading + 弹 toast + 刷新视图回填摘要。
_contextCallbacks.onManualCompressed?.(event.conversation_id || '', event.summary || '')
return true
}
case 'AiAutoCompressed': {
// loop 自动压缩对桌面静默——仅复位 loading(冗余兜底,Auto 路径前端 loading 本就为 false),
// 不调 onManualCompressed(防每次发送误弹 toast + 误 switchConversation 打断 LLM 流)。
_contextCallbacks.onAutoCompressed?.()
return true
}
case 'AiContextCleared':
_contextCallbacks.onCleared?.(event.conversation_id || '')
return true
default:
return false
}