优化: aichat 工具体验补强(切生成会话占位气泡治丢首响应/read_symbol符号级提示/失败引导/审批失败toast) + 修复 WorkflowDagDisplay 崩页与 aiChat i18n 缺key

This commit is contained in:
lxy
2026-08-09 21:34:53 +08:00
parent cad76ffdf2
commit 37040616bd
10 changed files with 335 additions and 21 deletions
+7 -1
View File
@@ -13,6 +13,7 @@
//! - _pendingApprovalIds:审批按钮防抖守卫(同 useAiSend 原实现)
import { ref } from 'vue'
import { emit } from '@tauri-apps/api/event'
import { aiApi } from '@/api'
import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers'
@@ -99,9 +100,14 @@ async function approveToolCall(toolCallId: string, approved: boolean, decision?:
// 仅 IPC 真失败(审批已处理/网络断)走到这里:后端工具失败已改走 emit completed,不进此分支
// 不回滚 pending_approval(会卡死按钮),改设 completed + 错误提示,让用户知晓失败
console.error('[AI] 审批操作未送达后端:', e)
// P1-e-d:审批失败给用户可见反馈——composable 无组件上下文,经 Tauri 事件总线
// emit('ai-toast'),由 AiChat.vue listen 弹本地 toast(对齐 ai-tool-slow-toast 先例)。
// 文案复用 ai.approvalNotDelivered(与下方 tc.result 同源)。
const errMsg = e instanceof Error ? e.message : String(e)
void emit('ai-toast', { msg: t('ai.approvalNotDelivered', { error: errMsg }), type: 'error' })
if (tc) {
tc.status = 'completed'
tc.result = t('ai.approvalNotDelivered', { error: e instanceof Error ? e.message : String(e) })
tc.result = t('ai.approvalNotDelivered', { error: errMsg })
}
throw e
}
+23 -1
View File
@@ -11,7 +11,7 @@ import { persistUiState } from './useAiPanel'
import { setStreaming } from './streamingGuard'
import { nextMsgId, getConvState, clearConvStreamState, clearLastDelta, startApprovalTimer, clearAllApprovalTimers, switchingConvs, getConvStreamState, setConvCurrentText, notifyConversationChanged } from './aiShared'
import { t } from '@/i18n/i18n-helpers'
import type { AiConversationDetail, AiMessage, AiToolCallInfo, ConvId } from '@/api/types'
import type { AiConversationDetail, AiMessage, AiToolCallInfo, ConvId, MessageId } from '@/api/types'
const appSettings = useAppSettingsStore()
@@ -333,6 +333,28 @@ export async function switchConversation(id: string, force = false) {
state.messages = parseConvMessages(rawMsgs, 'loaded')
// parse 成功才置 active(保证 active 与 messages 一致)
state.activeConversationId = id
// CSW-P1-4: 切到 round0 进行中的会话时,后端该轮 per_conv 只有 user 消息(assistant 占位
// 轮末才入),末条是 user → 流式 currentText 无 assistant 承载(MessageList isLastAi 不命中,
// flushCurrentText 遇 user break)整轮回复丢失。补前端占位 assistant 气泡承接 currentText,
// 生成完成由 AiCompleted/AiAgentRound 收尾 flushCurrentText 回填 content。
// targetGen(非终止三态 generating/stopping/compressed)在上文已算,直接复用;
// round0-pending- 前缀防御判重(force 重刷同会话时 parseConvMessages 已整体重建,不叠加)。
// 字段满足 AiMessage 必填(id/role/content/timestamp),仅 id 因 branded MessageId 逐字段 cast。
const lastMsg = state.messages[state.messages.length - 1]
if (
targetGen
&& lastMsg
&& lastMsg.role === 'user'
&& !state.messages.some((m) => m.role === 'assistant' && m.id.startsWith('round0-pending-'))
) {
state.messages.push({
id: `round0-pending-${id}` as MessageId,
role: 'assistant',
content: '',
isError: false,
timestamp: Date.now(),
})
}
} catch (e) {
// 历史消息解析/映射失败原仅 state.messages=[] → 切换后空白用户不知原因。
// 改为推错误气泡提示 + 控制台日志(保留 state.messages 不再清空,避免空白无反馈)。