fix(ai): keep replies visible on legacy WebView

This commit is contained in:
知食分子 authored and GitHub committed 2026-08-12 12:35:10 +08:00
1 parent 0e42de5f7f
commit e9e0d04a2c
5 files changed
+71 -5

No files matched your search

@@ -80,6 +80,26 @@ describe("createAiMessageRenderer", () => {
expect(code).toMatchObject({ type: "code", pending: false, html: "<span>SELECT 1</span>" });
});
it("falls back to escaped code when highlighting is not supported", () => {
const markdown = (text: string) => `<p>${text}</p>`;
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 &lt; 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) => `<p>${text}</p>`);
const renderer = createAiMessageRenderer({ markdown });
+10 -1
View File
@@ -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);
+9 -3
View File
@@ -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<string> {
const unlisten: UnlistenFn = await listen<AgentEvent>("ai-agent-event", (event) => {
onEvent(event.payload);
if (event.payload.type === "agent_end" || event.payload.type === "error") {
const unlisten: UnlistenFn = await listen<TauriAgentEvent>("ai-agent-event", (event) => {
const payload = event.payload;
if (payload.session_id && payload.session_id !== sessionId) return;
onEvent(payload);
if (payload.type === "agent_end" || payload.type === "error") {
unlisten();
}
});