From 9a631ebf15dd2fbffb4583b46d87578fd23b504b Mon Sep 17 00:00:00 2001 From: thzxx Date: Sat, 11 Jul 2026 21:31:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=81=E5=BC=8F=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=B8=B2=E6=9F=93Markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 流式期间用marked实时渲染HTML替代纯textContent,消除视觉跳变 2. rAF节流避免每chunk全量渲染卡顿 3. patchIncompleteMarkdown智能补全未闭合代码块围栏和行内反引号 4. 代码块复制按钮仅在最终渲染时添加 --- src/renderer/components/chat-area.ts | 70 ++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/chat-area.ts b/src/renderer/components/chat-area.ts index 58453eb..2f091d1 100644 --- a/src/renderer/components/chat-area.ts +++ b/src/renderer/components/chat-area.ts @@ -338,6 +338,64 @@ function renderToolCallCard(tc: ToolCallRecord): string { `; } +/** + * 流式 Markdown 智能补全:检测并修复流式传输中不完整的 Markdown 语法 + * - 未闭合的代码块(``` / ~~~)→ 追加闭合标记 + * - 未闭合的行内代码(`)→ 追加闭合 ` + * - 未闭合的粗体(**)→ 追加闭合 ** + * 返回补全后的字符串(不修改原始内容,仅用于渲染) + */ +function patchIncompleteMarkdown(md: string): string { + let patched = md; + + // ── 代码块围栏检测(``` 或 ~~~)── + // 统计未闭合的围栏对 + const fenceRegex = /^([`~]{3,})/gm; + const openFences: string[] = []; + let match: RegExpExecArray | null; + while ((match = fenceRegex.exec(patched)) !== null) { + openFences.push(match[1]); + } + // 如果有奇数个围栏,说明最后一个未闭合 + if (openFences.length % 2 === 1) { + const lastFence = openFences[openFences.length - 1]; + patched += '\n' + lastFence; + } + + // ── 行内代码反引号检测(仅当不在代码块内时才有意义)── + // 简化处理:统计不在代码围栏内的奇数个反引号 + // 这里用一个保守的检测:如果末尾有奇数个 `(且不是围栏),补一个 + const inlineBackticks = (patched.match(/(?