修复: force_send单锁竞态+classify unknown不重试(撤销误判)+UX交互批
This commit is contained in:
@@ -34,6 +34,20 @@ import type { AiChatEvent, AiConversationSummary, AiMessage, AiProviderConfig, A
|
||||
* - 现有 push 逻辑(composables 内)零改动,上限由本模块 watch 统一兜底。
|
||||
*/
|
||||
const MESSAGE_CAP = 200
|
||||
|
||||
/**
|
||||
* 单对话 messages 的 parts 体积软上限(UX-260617-20)。
|
||||
*
|
||||
* 背景:MESSAGE_CAP 仅按"条数"裁剪,不感知单条体积。多模态 base64 图片(F-260614-05)
|
||||
* 单条可达数 MB,极端 200 条 × 5MB ≈ 1GB 会撑爆渲染内存。
|
||||
*
|
||||
* 设计:
|
||||
* - 仅度量 image 片的 base64/url 字符串长度(其余 text/diff 普通字符串可忽略量级);
|
||||
* base64 char 数 ≈ 编码后字节数(3/4 编码开销),量纲稳定可作预算代理。
|
||||
* - 超出预算时,从最旧消息起逐条裁掉其 parts(置空数组),保条数与文本可见、丢历史图片。
|
||||
* - 与 MESSAGE_CAP 互补:条数先裁体积小场景,体积裁多模态大体积场景;两 watch 各司其职。
|
||||
*/
|
||||
const PARTS_SIZE_BUDGET = 50 * 1024 * 1024 // 50MB(base64 char 计)
|
||||
import { useAiEvents } from '@/composables/ai/useAiEvents'
|
||||
import { useAiStream } from '@/composables/ai/useAiStream'
|
||||
import { useAiSend, initDrainQueueListener } from '@/composables/ai/useAiSend'
|
||||
@@ -106,6 +120,49 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* 估算单条消息 parts 的字符体积(base64/url 字符串长度之和,UX-260617-20)。
|
||||
*
|
||||
* 只计 image 片的 base64 与 url(大体量载荷);text 片(正文/工具结果文本)不在此预算口径内,
|
||||
* 避免对常规长文本消息误伤。无 parts 或非 image 片返回 0。
|
||||
*/
|
||||
function estimatePartsSize(parts: ContentPart[] | undefined): number {
|
||||
if (!parts || parts.length === 0) return 0
|
||||
let size = 0
|
||||
for (const p of parts) {
|
||||
if (p.type === 'image') {
|
||||
if (p.base64) size += p.base64.length
|
||||
if (p.url) size += p.url.length
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* parts 体积软上限守卫(UX-260617-20):push 式增长使总 parts 体积超 PARTS_SIZE_BUDGET 时,
|
||||
* 从最旧消息起逐条置空其 parts(丢历史多模态图,保文本/条数)。
|
||||
*
|
||||
* 仅拦截 push(同上 length watch 的判定),避免切换对话整体替换时误裁后端历史图。
|
||||
*/
|
||||
watch(
|
||||
() => state.messages.length,
|
||||
(newLen, oldLen) => {
|
||||
if (oldLen <= 0 || newLen - oldLen > 2) return
|
||||
let total = 0
|
||||
for (const m of state.messages) total += estimatePartsSize(m.parts)
|
||||
if (total <= PARTS_SIZE_BUDGET) return
|
||||
// 超预算:从最旧起逐条清 parts,直到总量回落到预算内
|
||||
for (const m of state.messages) {
|
||||
if (total <= PARTS_SIZE_BUDGET) break
|
||||
const partSize = estimatePartsSize(m.parts)
|
||||
if (partSize > 0) {
|
||||
m.parts = []
|
||||
total -= partSize
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* 对话搜索过滤结果(UX-06 §3.1)。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user