优化: aichat 体验收尾(useAiEvents拆分4子域 + AIC-FIX P1队列/mutex/缓存 + i18n残留抽取)

This commit is contained in:
lxy
2026-08-08 20:38:33 +08:00
parent 4092a8d5bb
commit 65c0f6b2b6
21 changed files with 882 additions and 913 deletions
+108
View File
@@ -0,0 +1,108 @@
//! 工具事件域 — handleToolEvent
//!
//! 处理:AiToolCallStarted / AiToolCallCompleted / AiToolAutoApproved / AiApprovalRequired / AiApprovalResult。
//! 依赖方向:本模块 → aiShared / useAiPendingState(叶子)/ useAiStream,不导入任何同级子 composable。
import { emit } from '@tauri-apps/api/event'
import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers'
import { findToolCall, clearApprovalTimer, startApprovalTimer, startToolSlowTimer, clearToolSlowTimer } from './aiShared'
import { removePendingDirAuth } from './useAiPendingState'
import { clearStreamWatchdog } from './useAiStream'
import type { AiChatEvent, AiToolCallInfo } from '@/api/types'
/** tool 域:工具卡片状态流转/会话级信任/审批。返回 true=已处理。 */
export function handleToolEvent(event: AiChatEvent): boolean {
switch (event.type) {
case 'AiToolCallStarted': {
const info: AiToolCallInfo = {
id: event.id,
name: event.name,
args: event.args,
status: 'running',
}
// 同 id 重复 Started 不重复 push(provider id 不稳,防残留 running 空卡)
if (!findToolCall(event.id)) {
const lastMsg = state.messages[state.messages.length - 1]
if (lastMsg && lastMsg.role === 'assistant') {
lastMsg.toolCalls = lastMsg.toolCalls || []
lastMsg.toolCalls.push(info)
}
}
// 工具开始执行即挂慢执行计时器(超时仅 toast,不动 running 态)
startToolSlowTimer(event.id, event.name)
return true
}
case 'AiToolCallCompleted': {
const tc = findToolCall(event.id)
if (tc) {
tc.status = 'completed'
tc.result = event.result
}
state.completedTools++ // 进度条计数器
state.pendingApprovals = state.pendingApprovals.filter(p => p.id !== event.id)
clearToolSlowTimer(event.id)
// 工具结束(无论审批通过后执行还是被拒),清审批超时计时器
clearApprovalTimer(event.id)
return true
}
case 'AiToolAutoApproved': {
// 会话级信任:经事件总线桥接弹本地 toast(composable 无组件上下文)
void emit('ai-tool-auto-approved-toast', { tool: event.tool, dir: event.dir })
return true
}
case 'AiApprovalRequired': {
clearStreamWatchdog(event.conversation_id || undefined) // 等用户审批,不计整流超时
const info: AiToolCallInfo = {
id: event.id,
name: event.name,
args: event.args,
status: 'pending_approval',
kind: event.kind ?? 'risk',
// reason 必须写入 info,否则浮窗 ApprovalPopup 的 tc.reason 永远空(审批说明丢失)
reason: event.reason,
conversationId: event.conversation_id ?? state.activeConversationId ?? undefined,
}
state.pendingApprovals.push(info)
const tc = findToolCall(event.id)
if (tc) {
tc.status = 'pending_approval'
tc.reason = event.reason
// write_file 审批注入行级 diff(旧文件 vs 新内容),前端审批卡预览
if (event.diff) tc.diff = event.diff
}
// 进入审批等待 → 取消该工具慢执行计时器(审批耗时由用户主导,非执行慢),再启动审批超时计时器
clearToolSlowTimer(event.id)
startApprovalTimer(event.id, event.name, 'risk')
// 通知 ToolCardList 展开折叠组 + scroll(pending 卡落折叠分组不可见时)
void emit('ai-pending-arrived', { toolCallId: event.id })
return true
}
case 'AiApprovalResult': {
if (!event.approved) {
const tc = findToolCall(event.id)
if (tc) {
tc.status = 'rejected'
tc.result = t('ai.aiTool.rejectedHint')
}
state.pendingApprovals = state.pendingApprovals.filter(p => p.id !== event.id)
clearToolSlowTimer(event.id)
clearApprovalTimer(event.id)
} else {
// 审批通过 → 工具重回执行态,重启慢执行计时器;对称清审批超时计时器防到点误拒
startToolSlowTimer(event.id, findToolCall(event.id)?.name || '')
clearApprovalTimer(event.id)
}
// 路径授权挂起的工具收到 ApprovalResult → 按 id 移除该条(数组化后只清这一条)
removePendingDirAuth(event.id)
return true
}
default:
return false
}
}