From 4342fd32621f161518cc38732c7326ecf6ffd519 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Tue, 18 Aug 2026 17:43:49 +0300 Subject: [PATCH] perf(chat): release completed streaming parser history (#553) - Keep only the active assistant response in streaming Markdown mode.\n- Remount completed responses in static mode to release incremental AST caches.\n- Preserve final Markdown, code rendering, and theme behavior. --- CHANGELOG.md | 1 + src/components/ChatPanel.vue | 14 +++++++++++++- src/components/chat/ChatMessage.vue | 9 ++++++++- tests/e2e/chat/panel.spec.ts | 10 ++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce77d95a1..4142ca058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ ### Performance +- Release obsolete streamed Markdown parser history after each AI response completes, preventing chat memory from multiplying with every streamed chunk. (#544) - Use cached axis-aligned world positions for hit testing untransformed layer chains and add representative 500/2,000-node interaction profiles. (#527) - Coalesce writable-document autosaves that overlap an active `.fig` export while preserving a trailing save for newer edits. (#528) - Defer JSX generation and syntax highlighting until the Code panel is active, keeping large canvas selections responsive. (#500) diff --git a/src/components/ChatPanel.vue b/src/components/ChatPanel.vue index 5eab64a38..c30225287 100644 --- a/src/components/ChatPanel.vue +++ b/src/components/ChatPanel.vue @@ -73,6 +73,13 @@ const failureMessage = computed(() => { } }) const status = computed(() => chat.value?.status ?? 'ready') +function isStreamingMessage(message: UIMessage, index: number): boolean { + return ( + message.role === 'assistant' && + index === messages.value.length - 1 && + (status.value === 'submitted' || status.value === 'streaming') + ) +} const isThinking = computed(() => { const s = status.value if (s !== 'submitted' && s !== 'streaming') return false @@ -254,7 +261,12 @@ function handleClearChat() {
- +
diff --git a/src/components/chat/ChatMessage.vue b/src/components/chat/ChatMessage.vue index 8750c13dd..5674599d3 100644 --- a/src/components/chat/ChatMessage.vue +++ b/src/components/chat/ChatMessage.vue @@ -15,9 +15,13 @@ import ImageAttachment from '@/components/chat/attachment/image/ImageAttachment. import type { UIDataTypes, UIMessage, UIMessagePart, UITools } from 'ai' -const { message } = defineProps<{ message: UIMessage }>() +const { message, streaming = false } = defineProps<{ + message: UIMessage + streaming?: boolean +}>() const { dialogs } = useI18n() const isDark = computed(() => resolvedAppTheme.value === 'dark') +const markdownMode = computed(() => (streaming ? 'streaming' : 'static')) const imageAttachments = imageAttachmentsForMessage(message.id) type ToolPart = Extract, { toolCallId: string }> @@ -121,9 +125,12 @@ function partKey(part: UIMessagePart, index: number): stri class="rounded-xl rounded-tl-md bg-hover px-3 py-2 text-xs leading-relaxed text-surface" >
diff --git a/tests/e2e/chat/panel.spec.ts b/tests/e2e/chat/panel.spec.ts index 62587e8cd..c10b09e90 100644 --- a/tests/e2e/chat/panel.spec.ts +++ b/tests/e2e/chat/panel.spec.ts @@ -261,6 +261,16 @@ test('assistant responds', async () => { } }) +test('completed Markdown responses release streaming parser history', async () => { + await chatInput().fill('Show a code block') + await chatInput().press('Enter') + + const markdown = page.locator('.chat-markdown').last() + await expect(markdown).toBeVisible() + await expect(markdown).toHaveAttribute('data-chat-markdown-mode', 'static') + await expect(markdown.locator('.shiki').first()).toBeVisible() +}) + test('assistant code blocks follow the active theme with readable contrast', async () => { await chatInput().fill('Show a code block') await chatInput().press('Enter')