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.
This commit is contained in:
parent
4e48420ac1
commit
4342fd3262
|
|
@ -51,6 +51,7 @@
|
||||||
|
|
||||||
### Performance
|
### 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)
|
- 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)
|
- 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)
|
- Defer JSX generation and syntax highlighting until the Code panel is active, keeping large canvas selections responsive. (#500)
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,13 @@ const failureMessage = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const status = computed(() => chat.value?.status ?? 'ready')
|
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 isThinking = computed(() => {
|
||||||
const s = status.value
|
const s = status.value
|
||||||
if (s !== 'submitted' && s !== 'streaming') return false
|
if (s !== 'submitted' && s !== 'streaming') return false
|
||||||
|
|
@ -254,7 +261,12 @@ function handleClearChat() {
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<div v-else data-test-id="chat-messages" class="flex flex-col gap-3">
|
<div v-else data-test-id="chat-messages" class="flex flex-col gap-3">
|
||||||
<ChatMessage v-for="msg in messages" :key="msg.id" :message="msg" />
|
<ChatMessage
|
||||||
|
v-for="(msg, index) in messages"
|
||||||
|
:key="msg.id"
|
||||||
|
:message="msg"
|
||||||
|
:streaming="isStreamingMessage(msg, index)"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Thinking indicator: shown when AI is working but no visible activity -->
|
<!-- Thinking indicator: shown when AI is working but no visible activity -->
|
||||||
<div v-if="isThinking" data-test-id="chat-typing-indicator" class="flex gap-2">
|
<div v-if="isThinking" data-test-id="chat-typing-indicator" class="flex gap-2">
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,13 @@ import ImageAttachment from '@/components/chat/attachment/image/ImageAttachment.
|
||||||
|
|
||||||
import type { UIDataTypes, UIMessage, UIMessagePart, UITools } from 'ai'
|
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 { dialogs } = useI18n()
|
||||||
const isDark = computed(() => resolvedAppTheme.value === 'dark')
|
const isDark = computed(() => resolvedAppTheme.value === 'dark')
|
||||||
|
const markdownMode = computed(() => (streaming ? 'streaming' : 'static'))
|
||||||
const imageAttachments = imageAttachmentsForMessage(message.id)
|
const imageAttachments = imageAttachmentsForMessage(message.id)
|
||||||
|
|
||||||
type ToolPart = Extract<UIMessagePart<UIDataTypes, UITools>, { toolCallId: string }>
|
type ToolPart = Extract<UIMessagePart<UIDataTypes, UITools>, { toolCallId: string }>
|
||||||
|
|
@ -121,9 +125,12 @@ function partKey(part: UIMessagePart<UIDataTypes, UITools>, index: number): stri
|
||||||
class="rounded-xl rounded-tl-md bg-hover px-3 py-2 text-xs leading-relaxed text-surface"
|
class="rounded-xl rounded-tl-md bg-hover px-3 py-2 text-xs leading-relaxed text-surface"
|
||||||
>
|
>
|
||||||
<Markdown
|
<Markdown
|
||||||
|
:key="markdownMode"
|
||||||
:content="part.text"
|
:content="part.text"
|
||||||
:is-dark="isDark"
|
:is-dark="isDark"
|
||||||
:mermaid="false"
|
:mermaid="false"
|
||||||
|
:mode="markdownMode"
|
||||||
|
:data-chat-markdown-mode="markdownMode"
|
||||||
class="chat-markdown [&_[data-stream-markdown=code]]:!bg-input"
|
class="chat-markdown [&_[data-stream-markdown=code]]:!bg-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
test('assistant code blocks follow the active theme with readable contrast', async () => {
|
||||||
await chatInput().fill('Show a code block')
|
await chatInput().fill('Show a code block')
|
||||||
await chatInput().press('Enter')
|
await chatInput().press('Enter')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue