diff --git a/web-admin/src/api/aiconfig.ts b/web-admin/src/api/aiconfig.ts index f724228..94bfd92 100644 --- a/web-admin/src/api/aiconfig.ts +++ b/web-admin/src/api/aiconfig.ts @@ -319,6 +319,33 @@ export function normalizeAiText(value?: string): string { } } +function findOverlapLength(current: string, next: string) { + const max = Math.min(current.length, next.length) + for (let size = max; size > 0; size -= 1) { + if (current.slice(-size) === next.slice(0, size)) return size + } + return 0 +} + +function mergeStreamOutput(current: string, chunk?: string) { + const next = normalizeAiText(chunk || '') + if (!next) return { output: current, delta: '' } + if (!current) return { output: next, delta: next } + if (next === current) return { output: current, delta: '' } + if (next.length >= 16 && current.endsWith(next)) return { output: current, delta: '' } + if (next.startsWith(current)) { + return { output: next, delta: next.slice(current.length) } + } + const currentIndex = next.length > current.length ? next.indexOf(current) : -1 + if (currentIndex >= 0) { + return { output: next, delta: next.slice(currentIndex + current.length) } + } + const overlap = findOverlapLength(current, next) + if (overlap < 8) return { output: current + next, delta: next } + const delta = next.slice(overlap) + return { output: current + delta, delta } +} + function extractTextValue(value: any): string { if (!value || typeof value !== 'object' || Array.isArray(value)) return '' for (const key of ['output', 'answer', 'content', 'text', 'result']) { @@ -383,7 +410,7 @@ async function fetchSseStream( const event = parseSseFrame(frame) if (!event) return if (event.type === 'delta') { - output += normalizeAiText(event.content || '') + output = mergeStreamOutput(output, event.content).output } onEvent(event, output) if (event.type === 'error' && finishRecovered(event)) {