-
Notifications
You must be signed in to change notification settings - Fork 691
feat: optimize chat markdown rendering #1894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+964
−55
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c4fa8cf
feat: optimize chat markdown rendering
zhangmo8 39e6480
feat: tune markstream chat rendering
zhangmo8 9c8328e
fix: reduce chat send jitter
zhangmo8 63f67b8
fix: stabilize stream start row
zhangmo8 34f8ff8
fix: reduce first chunk flicker
zhangmo8 6be74a5
chore: update
zhangmo8 41737c9
fix: address markdown review comments
zhangmo8 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
98 changes: 98 additions & 0 deletions
98
docs/features/markstream-chat-rendering-optimization/plan.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # MarkStream Chat Rendering Optimization Plan | ||
|
|
||
| ## Chosen approach | ||
|
|
||
| Use MarkStream's own chat-mode and performance presets inside each Markdown message, while keeping DeepChat's existing outer message windowing and row behavior. This gives the main performance gain where Markdown is expensive without breaking chat-level features that depend on the current DOM/scroll model. | ||
|
|
||
| ## Current path | ||
|
|
||
| `ChatPage` builds display messages, `MessageList` renders rows, assistant rows render `MessageBlockContent`, and text parts render `MarkdownRenderer`, which wraps `markstream-vue`'s `NodeRenderer` with DeepChat custom components. | ||
|
|
||
| ## Rendering strategy | ||
|
|
||
| ### Streaming / live content | ||
|
|
||
| - Keep `mode="chat"` stable. | ||
| - Pass `final=false` while the block or part is still loading. | ||
| - Use `smooth-streaming="auto"` unless the caller disables smooth streaming. | ||
| - Enable `typewriter` and `code-block-stream` for the generating message. | ||
| - Use incremental rendering by setting live node limit to `0` while streaming. | ||
| - Tune MarkStream batch props for small frame-friendly chunks: | ||
| - small initial/render batch sizes; | ||
| - short render delay; | ||
| - low per-frame budget; | ||
| - idle timeout for catch-up. | ||
| - Keep `fade=false` to avoid repeated opacity animation during streaming updates. | ||
|
|
||
| ### Completed / static content | ||
|
|
||
| - Pass `final=true` after generation completes. | ||
| - Disable smooth-streaming/typewriter/code-block-stream. | ||
| - Use MarkStream node virtualization and viewport priority for completed long Markdown: | ||
| - `node-virtual="auto"` when allowed; | ||
| - `max-live-nodes` and `live-node-buffer` bounded; | ||
| - deferred offscreen nodes; | ||
| - viewport-priority rendering. | ||
| - Keep the custom code block, Mermaid, reference, and link mappings registered through scoped `custom-id`. | ||
|
|
||
| ### Compatibility guardrails | ||
|
|
||
| - Inline chat search disables Markdown node virtualization for all rendered messages while search is open. | ||
| - Search-result messages always disable Markdown node virtualization. | ||
| - Streaming content disables Markdown node virtualization even if the caller allows virtualization. | ||
| - Message capture keeps using the complete display message list through `allMessagesForCapture`. | ||
| - Session switch clears message-window measurements before loading the new session. | ||
|
|
||
| ## Affected interfaces | ||
|
|
||
| - `MarkdownRenderer.vue` | ||
| - `streaming?: boolean` | ||
| - `final?: boolean` | ||
| - `virtualizeNodes?: boolean` | ||
| - internal MarkStream tuning constants for streaming and completed content. | ||
| - `MessageBlockContent.vue` | ||
| - derives streaming/final state from block status and processed part loading; | ||
| - passes virtualization guard to MarkdownRenderer. | ||
| - `MessageItemAssistant.vue`, `MessageListRow.vue`, `MessageList.vue` | ||
| - pass through `disableMarkdownVirtualization`. | ||
| - `ChatPage.vue` | ||
| - disables Markdown virtualization while inline chat search is open; | ||
| - clears row measurements during session switch. | ||
|
|
||
| ## Test strategy | ||
|
|
||
| Focused tests should assert both behavior and configuration: | ||
|
|
||
| - `MarkdownRenderer.test.ts` | ||
| - static completed defaults; | ||
| - streaming configuration; | ||
| - smooth streaming opt-out; | ||
| - node virtualization opt-out; | ||
| - custom preview/reference behavior remains wired. | ||
| - `MessageBlockContent.test.ts` | ||
| - pending/loading parts pass streaming/final state; | ||
| - completed parts pass final/static state; | ||
| - search-result and parent disable flags disable node virtualization. | ||
| - `MessageList.test.ts` | ||
| - disable flag propagates through list rows. | ||
| - `ChatPage.test.ts` | ||
| - chat search disables markdown virtualization; | ||
| - session switch clears measurements. | ||
|
|
||
| Project checks before handoff: | ||
|
|
||
| - `pnpm run format` | ||
| - `pnpm run i18n` | ||
| - `pnpm run lint` | ||
| - `pnpm run typecheck:web` | ||
| - focused renderer/list/page Vitest command. | ||
|
|
||
| ## Self-review loop | ||
|
|
||
| After implementation: | ||
|
|
||
| 1. Inspect `git diff` for accidental unrelated changes. | ||
| 2. Re-read the render path and verify streaming/static/search/capture/jump invariants. | ||
| 3. Run focused tests and project checks. | ||
| 4. Fix any issues and repeat diff/test review until clean. | ||
| 5. Push the branch and update the PR body with concrete behavior and validation. |
46 changes: 46 additions & 0 deletions
46
docs/features/markstream-chat-rendering-optimization/spec.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # MarkStream Chat Rendering Optimization Spec | ||
|
|
||
| ## User need | ||
|
|
||
| DeepChat's assistant messages can stream long Markdown responses with code blocks, Mermaid diagrams, tables, references, artifacts, and search highlights. Rendering must feel smooth while content is generating and stay fast when browsing completed long conversations. | ||
|
|
||
| ## Goal | ||
|
|
||
| Build a high-performance, high-experience Markdown rendering path using `markstream-vue` for both streaming and completed chat content while preserving DeepChat's current behavior. | ||
|
|
||
| The optimized path remains: | ||
|
|
||
| `ChatPage` → `MessageList` / `MessageListRow` → `MessageItemAssistant` → `MessageBlockContent` → `MarkdownRenderer` → `markstream-vue` `NodeRenderer` | ||
|
|
||
| ## Acceptance criteria | ||
|
|
||
| - Streaming assistant text blocks pass explicit live state to `markstream-vue` (`mode="chat"`, `final=false`) and use MarkStream's streaming-friendly features. | ||
| - Completed assistant text blocks pass `final=true` and use MarkStream's completed-content virtualization/deferral features for long Markdown. | ||
| - Streaming Markdown uses incremental rendering settings that prioritize frame budget and smooth typing cadence instead of large per-token DOM commits. | ||
| - Static/completed Markdown uses a virtualized node window for long documents to preserve scrollback responsiveness and memory usage. | ||
| - Code blocks remain Monaco/custom-renderer backed and support DeepChat artifact preview behavior. | ||
| - Mermaid, references, links, artifact previews, tool calls, message capture, spotlight jump, scroll anchoring, and inline chat search remain compatible. | ||
| - Chat search and search-result messages keep Markdown DOM available by disabling node virtualization where DOM search/highlighting needs it. | ||
| - Session changes reset message height measurements so old session row heights cannot affect new session windowing. | ||
| - The implementation is explicit, documented, and covered by focused renderer/list/page tests. | ||
| - The PR description clearly explains the concrete performance/UX behavior, non-goals, and validation. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - Use `markstream-vue`; do not replace it with another renderer. | ||
| - Prefer focused changes in the existing render chain and avoid broad unrelated refactors. | ||
| - Do not replace the outer `MessageList` with `MarkstreamVirtualTimeline` in this slice; DeepChat's row-level tool/action/artifact/search/capture/jump/anchor behaviors need separate design before an outer virtual timeline can safely own them. | ||
| - Keep `fade=false` to avoid opacity restart flicker during high-frequency streaming. | ||
| - Preserve Monaco-backed code block rendering and artifact preview behavior. | ||
| - Do not claim benchmark numbers unless measured in this task. | ||
|
|
||
| ## Non-goals | ||
|
|
||
| - Full outer chat-list virtualization. | ||
| - Redesign of DOM-based chat search/highlight. | ||
| - New performance instrumentation UI. | ||
| - Replacing DeepChat's custom code block/artifact preview components. | ||
|
|
||
| ## Open questions | ||
|
|
||
| None for this implementation slice. |
17 changes: 17 additions & 0 deletions
17
docs/features/markstream-chat-rendering-optimization/tasks.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # MarkStream Chat Rendering Optimization Tasks | ||
|
|
||
| - [x] Create SDD spec, plan, and task documents. | ||
| - [x] Reconfirm MarkStream Vue props and existing focused tests. | ||
| - [x] Add streaming/final MarkStream option handling in `MarkdownRenderer.vue`. | ||
| - [x] Pass explicit streaming/final state from `MessageBlockContent.vue`. | ||
| - [x] Enable streaming code-block mode for live Markdown. | ||
| - [x] Add a Markdown node virtualization guard for chat search/search-result messages. | ||
| - [x] Clear message height measurements on session switch. | ||
| - [x] Update focused component tests for the first pass. | ||
| - [x] Tune MarkStream streaming batch settings for smoother live rendering. | ||
| - [x] Tune completed-node virtualization settings for responsive static scrollback. | ||
| - [x] Add/adjust tests for batch tuning props and compatibility guardrails. | ||
| - [x] Run self-review pass for render-path invariants and unrelated changes. | ||
| - [x] Run focused renderer/list/page tests. | ||
| - [x] Run format, i18n, lint, and typecheck:web. | ||
| - [x] Push branch and update PR description. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Chat send jitter | ||
|
|
||
| ## Issue | ||
|
|
||
| When the user sends a message while the chat is pinned to the bottom, the message list can visibly jump for one frame immediately after submit or flash when the first assistant chunk starts rendering. | ||
|
|
||
| ## Impact | ||
|
|
||
| The MarkStream rendering work feels smoother during generation, but the first outgoing-turn UI updates can still feel unstable because the list scroll position is corrected twice and the pending assistant row can be replaced by a different keyed DOM node at stream start. | ||
|
|
||
| ## Suspected root cause | ||
|
|
||
| `ChatPage` inserts both an optimistic user message and an empty pending assistant placeholder before the backend starts streaming. `useMessageWindow` estimates an empty pending assistant row as a normal assistant message (`ASSISTANT_BASE`, 136px), while the real DOM for that row is only the assistant header plus spinner. `MessageListRow` then reports the smaller measured height on the next animation frame, and `ChatPage.onMessageMeasure` scrolls to the bottom again, causing a visible submit-time adjustment. | ||
|
|
||
| At stream start, `chat.stream.updated` can set `isStreaming` before the real assistant record is available. The old placeholder was hidden immediately on `isStreaming`, leaving a one-frame gap. When the real assistant message then appears, `MessageList` keyed it by the real message id instead of the pending placeholder id, unmounting the spinner row and mounting a new assistant row. | ||
|
|
||
| ## Fix plan | ||
|
|
||
| - Teach `useMessageWindow` to estimate empty pending assistant placeholders close to their actual spinner row height. | ||
| - Keep the change narrowly scoped to synthetic pending assistant rows so real assistant messages keep their existing estimates. | ||
| - Keep the pending assistant placeholder visible until the first real streaming assistant content exists. | ||
| - Carry the pending placeholder render key onto the first real assistant row so Vue patches the existing row instead of replacing it. | ||
| - Add regression tests covering the placeholder estimate, measurement delta, and stream-start row transition. | ||
|
|
||
| ## Task checklist | ||
|
|
||
| - [x] Link GitHub bug issue | ||
| - [x] Update placeholder height estimate | ||
| - [x] Keep placeholder stable through stream-start | ||
| - [x] Add regression coverage | ||
| - [x] Run focused tests and required checks | ||
| - [x] Commit and push the PR branch | ||
|
|
||
| ## Validation | ||
|
|
||
| - `pnpm exec vitest --config vitest.config.renderer.ts test/renderer/composables/useMessageWindow.test.ts test/renderer/components/MessageListRow.test.ts test/renderer/components/ChatPage.test.ts` | ||
| - `pnpm run format` | ||
| - `pnpm run i18n` | ||
| - `pnpm run lint` | ||
| - `pnpm run typecheck:web` | ||
|
|
||
| ## Linked GitHub issue | ||
|
|
||
| https://github.com/ThinkInAIXYZ/deepchat/issues/1897 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.