fix(ai): keep replies visible on legacy WebView
This commit is contained in:
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 < 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 });
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user