From e9e0d04a2cb1be84c39f4035fe97fe080840cb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E9=A3=9F=E5=88=86=E5=AD=90?= <54768887+wzlstudy@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:35:10 +0800 Subject: [PATCH] fix(ai): keep replies visible on legacy WebView --- .../lib/__tests__/ai/aiMessageRender.spec.ts | 20 +++++++++++++++++ apps/desktop/src/lib/ai/aiMessageRender.ts | 11 +++++++++- apps/desktop/src/lib/backend/tauri.ts | 12 +++++++--- .../app-tests/aiAgentEventSession.test.ts | 22 +++++++++++++++++++ src-tauri/src/commands/ai.rs | 11 +++++++++- 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 packages/app-tests/aiAgentEventSession.test.ts diff --git a/apps/desktop/src/lib/__tests__/ai/aiMessageRender.spec.ts b/apps/desktop/src/lib/__tests__/ai/aiMessageRender.spec.ts index 30aac26493..2af8638ae0 100644 --- a/apps/desktop/src/lib/__tests__/ai/aiMessageRender.spec.ts +++ b/apps/desktop/src/lib/__tests__/ai/aiMessageRender.spec.ts @@ -80,6 +80,26 @@ describe("createAiMessageRenderer", () => { expect(code).toMatchObject({ type: "code", pending: false, html: "SELECT 1" }); }); + it("falls back to escaped code when highlighting is not supported", () => { + const markdown = (text: string) => `
${text}
`; + const highlightCode = vi.fn(() => { + throw new SyntaxError("Invalid regular expression: invalid group specifier name"); + }); + const renderer = createAiMessageRenderer({ markdown, highlightCode }); + + const [code] = renderer.render("```sql\nSELECT < 1\n```"); + + expect(highlightCode).toHaveBeenCalledWith("SELECT < 1", "SQL"); + expect(code).toEqual({ + type: "code", + content: "SELECT < 1", + html: "SELECT < 1", + lang: "SQL", + isSql: true, + pending: false, + }); + }); + it("re-parses only the last paragraph of a long streaming answer", () => { const markdown = vi.fn((text: string) => `${text}
`); const renderer = createAiMessageRenderer({ markdown }); diff --git a/apps/desktop/src/lib/ai/aiMessageRender.ts b/apps/desktop/src/lib/ai/aiMessageRender.ts index bf5266af7e..1d4af9e07b 100644 --- a/apps/desktop/src/lib/ai/aiMessageRender.ts +++ b/apps/desktop/src/lib/ai/aiMessageRender.ts @@ -97,7 +97,7 @@ export function createAiMessageRenderer(options: AiMessageRendererOptions) { } const lang = normalizeAiCodeLanguage(segment.lang); // Highlighting a block that is still streaming is wasted work: it is re-highlighted once the fence closes. - const highlighted = flags.live && flags.pending ? undefined : options.highlightCode?.(segment.content, lang); + const highlighted = flags.live && flags.pending ? undefined : safeHighlightCode(segment.content, lang); return { type: "code", content: segment.content, @@ -108,6 +108,15 @@ export function createAiMessageRenderer(options: AiMessageRendererOptions) { }; } + function safeHighlightCode(content: string, lang: string): string | undefined { + if (!options.highlightCode) return undefined; + try { + return options.highlightCode(content, lang); + } catch { + return undefined; + } + } + function renderCachedSegment(segment: MessageSegment, flags: SegmentRenderFlags): AiMessageRenderSegment { if (segment.content.length > maxCacheableChars) return renderSegment(segment, flags); diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index 3c3cc7bcf7..1de6a44336 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -441,6 +441,10 @@ export type AgentEvent = } | { type: "error"; message: string }; +type TauriAgentEvent = AgentEvent & { + session_id?: string; +}; + export async function aiAgentStream( sessionId: string, request: AiCompletionRequest, @@ -457,9 +461,11 @@ export async function aiAgentStream( confirmedSchema?: string, _signal?: AbortSignal, ): Promise