修复: 队列发送失败回填不丢消息+池权重revert+lang解析DRY+注释变量名修正

This commit is contained in:
2026-06-17 03:55:30 +08:00
parent 6b2dd8fb5d
commit a622bdca61
9 changed files with 111 additions and 32 deletions

View File

@@ -21,7 +21,7 @@ import { ref } from 'vue'
import { aiApi } from '@/api'
import { useAppSettingsStore } from '@/stores/appSettings'
import { state } from '@/stores/ai'
import type { ContentPart } from '@/api/types'
import type { ContentPart, AiMessage } from '@/api/types'
import i18n from '@/i18n'
import { resetStreamWatchdog, clearStreamWatchdog } from './useAiStream'
import { startListener } from './useAiEvents'
@@ -31,6 +31,19 @@ const t = ((i18n as any).global.t as (k: string, named?: Record<string, unknown>
const appSettings = useAppSettingsStore()
/**
* B-260617-03 DRY: 解析 AI 回复语言。
*
* df-ai-language='auto' → 回落到 df-language;否则用 df-ai-language 指定值。
* doSend/regenerate/editMessage 三处复用(原各内联同一段表达式)。
*/
function resolveLang(): string {
const raw = appSettings.get<string>('df-ai-language', 'auto')
return raw === 'auto'
? appSettings.get<string>('df-language', 'zh-CN')
: raw
}
/// 待发送队列上限(超过抛错提示用户)
const QUEUE_LIMIT = 10
@@ -101,10 +114,7 @@ async function doSend(text: string, skill?: string, force = false, parts?: Conte
resetStreamWatchdog() // 启动流式看门狗,无数据超时兜底
await startListener()
const raw = appSettings.get<string>('df-ai-language', 'auto')
const lang = raw === 'auto'
? appSettings.get<string>('df-language', 'zh-CN')
: raw
const lang = resolveLang()
try {
// force=true 走 force_send IPC(复位残留 generating),否则走正常 send
// F-01 阶段6: 透传 modelOverride(主对话专用,后端兜底校验池内才用)
@@ -159,10 +169,7 @@ async function regenerate() {
resetStreamWatchdog()
await startListener()
const raw = appSettings.get<string>('df-ai-language', 'auto')
const lang = raw === 'auto'
? appSettings.get<string>('df-language', 'zh-CN')
: raw
const lang = resolveLang()
const convId = state.activeConversationId
if (!convId) {
// 无活跃对话:回滚占位,报错
@@ -232,10 +239,7 @@ async function editMessage(newMessage: string) {
resetStreamWatchdog()
await startListener()
const raw = appSettings.get<string>('df-ai-language', 'auto')
const lang = raw === 'auto'
? appSettings.get<string>('df-language', 'zh-CN')
: raw
const lang = resolveLang()
const convId = state.activeConversationId
if (!convId) {
state.streaming = false
@@ -254,11 +258,36 @@ async function editMessage(newMessage: string) {
}
}
/** 取出队首并发送AiCompleted 触发,此时 streaming 已 false */
/**
* 取出队首并发送AiCompleted 触发,此时 streaming 已 false
*
* B-260617-02: 原 void sendMessage(...) fire-and-forget 吞没了 doSend IPC 失败 throw,
* 既无 AiCompleted 触发下次 drain(队列永久卡死),也无任何用户反馈。
* 现显式 catch:复位 streaming + 推错误气泡(复用 AiError 的 isError 气泡模式) +
* 回填失败消息到队首并停 drain(不静默丢用户输入,保留剩余队列供手动重试/编辑/取消)。
* 成功路径不受影响——doSend 成功后端会再 emit AiCompleted 续 drain 链路。
*/
export function drainQueue() {
if (state.queue.length === 0) return
const next = state.queue.shift()!
void sendMessage(next.text, next.skill, false, next.parts)
void (async () => {
try {
await sendMessage(next.text, next.skill, false, next.parts)
} catch (e) {
state.streaming = false
clearStreamWatchdog()
const errMsg = e instanceof Error ? e.message : String(e)
state.queue.unshift(next) // 回填失败消息到队首,保留剩余队列(不静默丢用户输入)
state.messages.push({
id: `err-${nextMsgId()}`,
role: 'assistant',
content: t('ai.queuedSendFailed', { error: errMsg }),
isError: true,
timestamp: Date.now(),
} as AiMessage)
console.error('[AI] drainQueue 续发失败:', e)
}
})()
}
/**
@@ -314,7 +343,14 @@ export function isQueueTimedOut(): boolean {
return first !== undefined && (Date.now() - first.enqueuedAt > QUEUE_TIMEOUT_MS)
}
/** 超时时弹 confirm,用户确认后以 force_mode 重发队首消息;取消则保持排队 */
/**
* 超时时弹 confirm,用户确认后以 force_mode 重发队首消息;取消则保持排队。
*
* B-260617-04: force_send 失败时,原实现队首已 shift 致消息静默丢失。
* 现失败把队首 unshift 回队列(保留 UI 队列可见,用户可改普通发送/编辑/取消),
* 并复位 streaming/clearStreamWatchdog(对齐 doSend 失败回填模式)让 UI 脱卡死态。
* 不自动重试 force(避免循环),交用户决定下一步。
*/
export async function tryForceSend(confirmFn: (msg: string) => Promise<boolean>): Promise<boolean> {
const first = state.queue[0]
if (!first) return false
@@ -328,8 +364,17 @@ export async function tryForceSend(confirmFn: (msg: string) => Promise<boolean>)
try {
await sendMessage(first.text, first.skill, true, first.parts)
return true
} catch {
// force_send 也失败:消息不回队列(避免循环),让用户看到错误
} catch (e) {
// force_send 也失败:回填队首保消息不丢,复位 streaming 让 UI 可继续操作
state.queue.unshift({
text: first.text,
skill: first.skill,
enqueuedAt: first.enqueuedAt,
parts: first.parts,
})
state.streaming = false
clearStreamWatchdog()
console.error('[AI] tryForceSend force_send 失败,消息已回填队首:', e)
return false
}
}