优化: 会话状态 per-conv 化与交互守卫

- 待审批跨会话 per-conv 合并,审批类事件全局放行
- 看门狗/重试计数/流式残段按会话键控迁移
- 输入草稿与子代理完成卡数据按会话隔离
- 切换兜底 activeId,收敛守卫防幽灵气泡
- 发送错误按限流/额度/网络分类文案
This commit is contained in:
lxy
2026-10-03 15:35:15 +08:00
parent d406a31722
commit b4fafa0e9a
13 changed files with 460 additions and 96 deletions
+15 -4
View File
@@ -9,9 +9,9 @@ import { emit } from '@tauri-apps/api/event'
import { useAppSettingsStore } from '@/stores/appSettings'
import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers'
import { nextMsgId, convStates, clearApprovalTimer, flushCurrentText, clearAllToolSlowTimers, friendlyError, notifyConversationChanged } from './aiShared'
import { nextMsgId, convStates, clearApprovalTimer, flushCurrentText, clearAllToolSlowTimers, friendlyError, notifyConversationChanged, clearLastDelta, discardConvPendingApprovals } from './aiShared'
import { clearTextIdleTimer, pendingMaxRounds, pendingHelp, pendingDirAuths } from './useAiPendingState'
import { clearStreamWatchdog } from './useAiStream'
import { clearStreamWatchdog, clearResumeAttempts } from './useAiStream'
import { setStreaming } from './streamingGuard'
import type { AiChatEvent, AiMessage, MessageId } from '@/api/types'
@@ -52,6 +52,12 @@ function isShowTokenUsage(): boolean {
*/
function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | 'AiError' | 'AiHelpRequired') {
clearStreamWatchdog(convId || undefined)
// 正常收尾路径清该会话的 delta 去重记录:flushCurrentText 只清活跃会话的,这里按 convId
// 显式清一遍兜住 convId 与 active 不一致或事件缺 convId 的时序,防残留记录误丢下轮同内容 delta
clearLastDelta(convId || undefined)
// 正常收尾清自动续跑计数:计数只服务于单轮内连续超时的风暴防护,终态后必须归零,
// 否则陈旧计数会让下一轮的正常超时提前触发强制收敛(清零只放收尾路径,不在发消息时清)
clearResumeAttempts(convId || undefined)
clearAllToolSlowTimers() // 整轮/错误/求助结束清全部工具慢执行计时器与已提示集合
clearTextIdleTimer() // 清文本空闲定时器 + 置 textIdle=true(整轮结束不该有活跃信号残留)
flushCurrentText()
@@ -86,6 +92,9 @@ function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | '
const removedApprovals = state.pendingApprovals.filter(p => p.conversationId === convId)
state.pendingApprovals = state.pendingApprovals.filter(p => !p.conversationId || p.conversationId !== convId)
for (const p of removedApprovals) clearApprovalTimer(p.id)
// 本会话已终止,其 per-conv 挂起集合作废(仅删集合项,视图保留上面无法归属的旧项;
// 切回该会话时由 pendingToolCalls 从后端审计表按需重建)
discardConvPendingApprovals(convId || undefined)
// 清分离窗口生成态快照(per-conv key,清本会话;兼容旧单 key)
if (convId) {
localStorage.removeItem(`df-ai-gen-${convId}`)
@@ -102,12 +111,14 @@ export function handleLifecycleEvent(event: AiChatEvent): boolean {
console.warn('[FE-AICOMPLETED] 收到 conv=', event.conversation_id, 'streaming=', state.streaming)
cleanupTerminatedConversation(event.conversation_id || '', 'AiCompleted')
console.warn('[FE-AICOMPLETED] cleanup 后 streaming=', state.streaming)
// 流中途失败保文:后端 emit AiCompleted(incomplete=true),前端追加系统提示气泡
// 流中途失败保文:后端 emit AiCompleted(incomplete=true),前端追加系统提示气泡。
// incomplete_reason 细分文案:"max_rounds"=达上限自动收尾(非网络中断,语义不同),
// 其他/缺省=网络中断等异常走原提示。
if (event.incomplete) {
state.messages.push({
id: `incomplete-${nextMsgId()}` as MessageId,
role: 'assistant',
content: t('ai.responseIncomplete'),
content: event.incomplete_reason === 'max_rounds' ? t('ai.responseIncompleteMaxRounds') : t('ai.responseIncomplete'),
timestamp: Date.now(),
})
}