fix: queue monitor tasks and recover doubao dom answers
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Backend CI / Backend (push) Successful in 15m54s
Desktop Client Build / Build Desktop Client (push) Successful in 22m56s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 28s

This commit is contained in:
2026-06-22 16:37:41 +08:00
parent 12681105f2
commit 8f7a83bba9
6 changed files with 154 additions and 73 deletions
@@ -251,10 +251,42 @@ describe('doubao adapter helpers', () => {
})
})
it('does not submit a success answer when the SSE stream has no answer text', () => {
it('uses DOM answer when Doubao finishes the stream without SSE answer text', () => {
expect(
resolveDoubaoSubmissionAnswer({
answer: null,
domAnswer:
'口袋门墙体预埋件通常需要确认墙体厚度、轨道承重、缓冲阻尼和检修口位置。选购时建议优先核对门扇重量、轨道长度和五金兼容性。',
allowDomAnswer: true,
}),
).toEqual({
answer:
'口袋门墙体预埋件通常需要确认墙体厚度、轨道承重、缓冲阻尼和检修口位置。选购时建议优先核对门扇重量、轨道长度和五金兼容性。',
source: 'dom',
})
})
it('does not submit DOM answer before a finish signal allows DOM recovery', () => {
expect(
resolveDoubaoSubmissionAnswer({
answer: null,
domAnswer:
'口袋门墙体预埋件通常需要确认墙体厚度、轨道承重、缓冲阻尼和检修口位置。选购时建议优先核对门扇重量、轨道长度和五金兼容性。',
allowDomAnswer: false,
}),
).toEqual({
answer: null,
source: null,
})
})
it('does not submit a weak history-title DOM fragment as a success answer', () => {
expect(
resolveDoubaoSubmissionAnswer({
answer: null,
domAnswer:
'口袋门墙体预埋件全解析 卧室口袋门阻尼缓冲导轨选购安装指南 口袋门防夹手缓冲方案 隐形门合页选购指南',
allowDomAnswer: true,
}),
).toEqual({
answer: null,
@@ -469,14 +469,45 @@ function selectBestDoubaoText(primary: string | null, secondary: string | null):
return secondaryScore > primaryScore ? secondaryText : primaryText
}
function resolveDoubaoSubmissionAnswer(input: { answer: string | null }): {
function isDoubaoSSESubmissionCandidate(value: string | null): boolean {
const answer = normalizeOptionalString(value)
if (!answer || containsDoubaoMojibakeSignal(answer)) {
return false
}
return true
}
function isDoubaoDOMSubmissionCandidate(value: string | null): boolean {
const answer = normalizeOptionalString(value)
if (!answer || containsDoubaoMojibakeSignal(answer)) {
return false
}
if (answer.length < 48) {
return false
}
const punctuationCount = countDoubaoMatches(answer, /[。!?;:,、,.!?;:]/g)
if (punctuationCount < 2 && !doubaoHasStructuredAnswerMarkers(answer) && !/\n/.test(answer)) {
return false
}
return doubaoTextQualityScore(answer) >= 180
}
function resolveDoubaoSubmissionAnswer(input: {
answer: string | null
source: 'sse' | null
domAnswer?: string | null
allowDomAnswer?: boolean
}): {
answer: string | null
source: 'sse' | 'dom' | null
} {
const answer = normalizeOptionalString(input.answer)
if (answer && !containsDoubaoMojibakeSignal(answer)) {
if (isDoubaoSSESubmissionCandidate(answer)) {
return { answer, source: 'sse' }
}
const domAnswer = normalizeOptionalString(input.domAnswer)
if (input.allowDomAnswer === true && isDoubaoDOMSubmissionCandidate(domAnswer)) {
return { answer: domAnswer, source: 'dom' }
}
return { answer: null, source: null }
}
@@ -2505,7 +2536,8 @@ const doubaoQueryInPage = async (
return
}
seen.add(href)
const visibleText = normalize(element.innerText) ?? normalize(element.textContent) ?? fallbackText
const visibleText =
normalize(element.innerText) ?? normalize(element.textContent) ?? fallbackText
links.push({
url: href,
title:
@@ -2643,9 +2675,7 @@ const doubaoQueryInPage = async (
/搜索\s*\d+\s*个关键词|参考\s*\d+\s*篇资料|查看\d+篇资料|参考资料|资料来源/.test(line)
const isReferenceQueryLine = (line: string): boolean =>
/[“"「].+[”"」]/.test(line) ||
/、/.test(line) ||
/关键词|搜索词|查询词/.test(line)
/[“"「].+[”"」]/.test(line) || /、/.test(line) || /关键词|搜索词|查询词/.test(line)
const isReferenceItemLine = (line: string): boolean =>
/^\d+[.、]\s*\S+/.test(line) &&
@@ -3240,6 +3270,8 @@ export const doubaoAdapter: MonitorAdapter = {
pageResult.ok === false && isDoubaoRecoverablePageError(pageResult.error)
const submission = resolveDoubaoSubmissionAnswer({
answer,
domAnswer: pageResult.domAnswer,
allowDomAnswer: streamSawAnswerFinish || pageResult.ok === true,
})
const hasRecoveredAnswer = Boolean(submission.answer)
const hasRecoveredContent = hasRecoveredAnswer || searchResults.length > 0