优化: 会话状态 per-conv 化与交互守卫
- 待审批跨会话 per-conv 合并,审批类事件全局放行 - 看门狗/重试计数/流式残段按会话键控迁移 - 输入草稿与子代理完成卡数据按会话隔离 - 切换兜底 activeId,收敛守卫防幽灵气泡 - 发送错误按限流/额度/网络分类文案
This commit is contained in:
@@ -21,7 +21,7 @@ import { aiApi } from '@/api'
|
||||
import { state } from '@/stores/ai'
|
||||
import type { ContentPart, AiMessage, MentionSpan, MessageId } from '@/api/types'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
|
||||
import { resetStreamWatchdog, clearStreamWatchdog, clearResumeAttempts } from './useAiStream'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
import { startListener } from './useAiEvents'
|
||||
import { nextMsgId, startApprovalTimer, clearApprovalTimer, clearAllApprovalTimers, resolveAiLang, convStates, setConvCurrentText, setConvStreaming, flushCurrentText } from './aiShared'
|
||||
@@ -166,7 +166,8 @@ async function regenerate() {
|
||||
// 只清当前会话的排队消息,不误伤其他会话
|
||||
const regenConvId = state.activeConversationId
|
||||
state.queue = state.queue.filter(q => q.conversationId !== regenConvId)
|
||||
resetStreamWatchdog()
|
||||
// 会话态走 per-conv 键(与 doSend 一致),不再落 legacy 单 timer fallback
|
||||
resetStreamWatchdog(regenConvId || undefined)
|
||||
|
||||
await startListener()
|
||||
const lang = resolveAiLang()
|
||||
@@ -174,7 +175,7 @@ async function regenerate() {
|
||||
if (!convId) {
|
||||
// 无活跃对话:回滚占位,报错
|
||||
setStreaming(false, { reason: 'regenerate-no-conv' })
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(regenConvId || undefined)
|
||||
state.messages = state.messages.filter(m => m.id !== aiMsgId)
|
||||
throw new Error(t('aiChat.regenerateUnavailable'))
|
||||
}
|
||||
@@ -183,7 +184,7 @@ async function regenerate() {
|
||||
} catch (e) {
|
||||
// IPC 失败:回滚 streaming + 移除占位气泡(旧回复后端已弹但前端先弹了,保持弹后态)
|
||||
setStreaming(false, { convId, reason: 'regenerate-ipc-fail' })
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(convId || undefined)
|
||||
state.messages = state.messages.filter(m => m.id !== aiMsgId)
|
||||
throw e
|
||||
}
|
||||
@@ -238,14 +239,15 @@ async function editMessage(newMessage: string) {
|
||||
// 只清当前会话的排队消息,不误伤其他会话
|
||||
const editConvId = state.activeConversationId
|
||||
state.queue = state.queue.filter(q => q.conversationId !== editConvId)
|
||||
resetStreamWatchdog()
|
||||
// 会话态走 per-conv 键(与 doSend 一致),不再落 legacy 单 timer fallback
|
||||
resetStreamWatchdog(editConvId || undefined)
|
||||
|
||||
await startListener()
|
||||
const lang = resolveAiLang()
|
||||
const convId = state.activeConversationId
|
||||
if (!convId) {
|
||||
setStreaming(false, { reason: 'editMessage-no-conv' })
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(editConvId || undefined)
|
||||
state.messages = state.messages.filter(m => m.id !== aiMsgId)
|
||||
throw new Error(t('aiChat.editNotLastUser'))
|
||||
}
|
||||
@@ -254,7 +256,7 @@ async function editMessage(newMessage: string) {
|
||||
} catch (e) {
|
||||
// IPC 失败:回滚 streaming + 移除占位气泡(被截断的旧回复后端已标 truncated,前端已移除)
|
||||
setStreaming(false, { convId, reason: 'editMessage-ipc-fail' })
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(convId || undefined)
|
||||
state.messages = state.messages.filter(m => m.id !== aiMsgId)
|
||||
throw e
|
||||
}
|
||||
@@ -395,7 +397,7 @@ export async function tryForceSend(confirmFn: (msg: string) => Promise<boolean>,
|
||||
state.queue.splice(idx, 1)
|
||||
// 清看门狗:doSend 内 force_send 路径会重设 streaming=true 并重启看门狗,
|
||||
// 此处无需前置 false(前置 false 会触发消息列表 watch 清流式块再重建,产生瞬态抖动)
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(targetConv || undefined)
|
||||
try {
|
||||
await sendMessage(first.text, first.skill, true, first.parts, first.spans, first.conversationId)
|
||||
return true
|
||||
@@ -411,7 +413,7 @@ export async function tryForceSend(confirmFn: (msg: string) => Promise<boolean>,
|
||||
conversationId: first.conversationId,
|
||||
})
|
||||
setStreaming(false, { convId: targetConv ?? state.activeConversationId, reason: 'tryForceSend-fail' })
|
||||
clearStreamWatchdog()
|
||||
clearStreamWatchdog(targetConv || undefined)
|
||||
console.error('[AI] tryForceSend force_send 失败,消息已回填队首:', e)
|
||||
return false
|
||||
}
|
||||
@@ -509,7 +511,10 @@ async function stopChat() {
|
||||
// 又会因 flushCurrentText 的非流式守卫跳过回填,不在此处 flush 则部分回复随停止丢失。
|
||||
flushCurrentText()
|
||||
setStreaming(false, { convId: state.activeConversationId, reason: 'stopChat' }) // 本地先复位,不依赖后端 AiCompleted(审批态看门狗已 clear,竞态丢失则卡死)
|
||||
clearStreamWatchdog() // 清残留看门狗
|
||||
// 停止属正常收尾:按目标会话清 per-conv 看门狗与自动续跑计数(计数只放收尾路径清,
|
||||
// 不在发消息时清,否则会掩盖单轮内的真实重试次数)
|
||||
clearStreamWatchdog(state.activeConversationId || undefined)
|
||||
clearResumeAttempts(state.activeConversationId || undefined)
|
||||
// 决策 a(逐条续发):不再清队列,保留供 AiCompleted 链式触发 drainQueue 续发。
|
||||
// 后端 agentic/mod.rs emit AiCompleted 注释明确「保证前端收事件时后端已可接下一条,队列续发不被拒」;
|
||||
// 当前未完成回复已入库(agentic/mod.rs save_conversation)保留为截断回复。
|
||||
@@ -519,7 +524,7 @@ async function stopChat() {
|
||||
} catch (e) {
|
||||
// stop 信号未送达后端:重启看门狗(后端继续生成时活跃事件会重置它,真 hang 则 130s 后兜底收尾),
|
||||
// 避免后端仍在跑却无任何恢复路径(原 clearStreamWatchdog 已把兜底关掉)。
|
||||
resetStreamWatchdog()
|
||||
resetStreamWatchdog(state.activeConversationId || undefined)
|
||||
const errMsg = e instanceof Error ? e.message : String(e)
|
||||
state.messages.push({
|
||||
id: `stop-err-${nextMsgId()}`,
|
||||
|
||||
Reference in New Issue
Block a user