From 977ffb13353e0e7b6d317a7bed86038a19d64c9e Mon Sep 17 00:00:00 2001 From: liangxu Date: Sun, 12 Jul 2026 14:57:25 +0800 Subject: [PATCH] fix(yuanbao-adapter): correct fragment merge and json candidate parsing Stop dropping short repeated fragments in mergeText (append them unless the duplicate is long enough to be a real echo) and drop the unreliable reverse-overlap branch. Return early from extractJSONCandidates when the whole body parses, and drop the seen-set so line-level frames are no longer deduped away. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/main/adapters/yuanbao.test.ts | 30 +++++++++++++++++++ .../src/main/adapters/yuanbao.ts | 23 +++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/apps/desktop-client/src/main/adapters/yuanbao.test.ts b/apps/desktop-client/src/main/adapters/yuanbao.test.ts index 7769b52..7580ef1 100644 --- a/apps/desktop-client/src/main/adapters/yuanbao.test.ts +++ b/apps/desktop-client/src/main/adapters/yuanbao.test.ts @@ -69,6 +69,36 @@ describe('yuanbao adapter helpers', () => { expect(mergeText(first, repeated)).toBe(repeated) }) + it('preserves repeated Markdown tokens from incremental SSE frames', () => { + const fragments = [ + '### 推荐清单\n\n- **', + '拾光里', + '**:本地工厂', + '\n- **', + '欧派', + '**:全国连锁', + '\n\n1. **', + '看板材', + '**', + '\n2. **', + '看封边', + '**', + ] + + const summary = parseYuanbaoCaptures([ + { + url: 'https://yuanbao.tencent.com/api/chat/conversation-markdown', + status: 200, + contentType: 'text/event-stream', + body: fragments.map((msg) => `data: ${JSON.stringify({ type: 'text', msg })}`).join('\n\n'), + }, + ]) + + expect(summary.answer).toBe( + '### 推荐清单\n\n- **拾光里**:本地工厂\n- **欧派**:全国连锁\n\n1. **看板材**\n2. **看封边**', + ) + }) + it('keeps a valid API SSE answer even when the DOM fallback is longer', () => { const apiAnswer = '### 核心结论\n\n混动车兼顾补能效率与低速油耗。\n\n- 适合长途出行\n- 适合没有固定充电桩的用户' diff --git a/apps/desktop-client/src/main/adapters/yuanbao.ts b/apps/desktop-client/src/main/adapters/yuanbao.ts index 1169848..9ad073c 100644 --- a/apps/desktop-client/src/main/adapters/yuanbao.ts +++ b/apps/desktop-client/src/main/adapters/yuanbao.ts @@ -426,8 +426,8 @@ function mergeText(current: string | null, nextFragment: string | null): string if (!current) { return nextFragment } - if (current === nextFragment || current.includes(nextFragment)) { - return current + if (current === nextFragment) { + return nextFragment.length >= 16 ? current : `${current}${nextFragment}` } if (nextFragment.startsWith(current)) { return nextFragment @@ -438,11 +438,6 @@ function mergeText(current: string | null, nextFragment: string | null): string return `${current}${nextFragment.slice(forwardOverlap)}` } - const reverseOverlap = sharedBoundaryLength(nextFragment, current) - if (reverseOverlap > 0) { - return `${nextFragment}${current.slice(reverseOverlap)}` - } - const commonPrefixLength = sharedPrefixLength(current, nextFragment) const duplicatePrefixThreshold = Math.max( 16, @@ -978,22 +973,24 @@ function attachYuanbaoResponseCapture( function extractJSONCandidates(body: string): unknown[] { const values: unknown[] = [] - const seen = new Set() - const pushParsed = (raw: string) => { + const pushParsed = (raw: string): boolean => { const normalized = raw.trim() - if (!normalized || seen.has(normalized)) { - return + if (!normalized) { + return false } try { values.push(JSON.parse(normalized)) - seen.add(normalized) + return true } catch { // Ignore malformed frames and continue with the remaining candidates. + return false } } - pushParsed(body) + if (pushParsed(body)) { + return values + } for (const rawLine of body.split(/\r?\n/)) { const line = rawLine.trim()