From 4836df97b994b0cd349e6ac305bb3bba39969740 Mon Sep 17 00:00:00 2001 From: Shem Date: Thu, 12 Feb 2026 11:41:34 +0000 Subject: [PATCH] webui: add lazy markdown loading for improved performance Add LazyMarkdownContent component that defers markdown rendering until messages scroll into view. This significantly improves load times for conversations with many messages (500+ messages would previously timeout). Changes: - New LazyMarkdownContent.svelte using IntersectionObserver - ChatMessageAssistant uses lazy loading for completed messages, eager rendering for streaming messages - ChatMessageUser and ChatMessageSystem use lazy loading - Short messages (<200 chars) render immediately for responsiveness The component shows a lightweight text placeholder until the message enters the viewport (with 200px margin for preloading), then renders full markdown with syntax highlighting, KaTeX, etc. --- .../ChatMessages/ChatMessageAssistant.svelte | 5 + .../ChatMessages/ChatMessageSystem.svelte | 4 +- .../chat/ChatMessages/ChatMessageUser.svelte | 4 +- .../webui/src/lib/components/app/index.ts | 1 + .../app/misc/LazyMarkdownContent.svelte | 98 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 tools/server/webui/src/lib/components/app/misc/LazyMarkdownContent.svelte diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte index 2b34b1c20a..31c83172a3 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte @@ -5,6 +5,7 @@ ChatMessageStatistics, ChatMessageThinkingBlock, CopyToClipboardIcon, + LazyMarkdownContent, MarkdownContent, ModelsSelector } from '$lib/components/app'; @@ -240,7 +241,11 @@ {:else if message.role === 'assistant'} {#if config().disableReasoningFormat}
{messageContent || ''}
+ {:else if message.timestamp && !isLoading()} + + {:else} + {/if} {:else} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte index c203822f60..24c24fface 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte @@ -2,7 +2,7 @@ import { Check, X } from '@lucide/svelte'; import { Card } from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import { MarkdownContent } from '$lib/components/app'; + import { LazyMarkdownContent } from '$lib/components/app'; import { INPUT_CLASSES } from '$lib/constants/input-classes'; import { config } from '$lib/stores/settings.svelte'; import ChatMessageActions from './ChatMessageActions.svelte'; @@ -145,7 +145,7 @@ > {#if currentConfig.renderUserContentAsMarkdown}
- +
{:else} import { Card } from '$lib/components/ui/card'; - import { ChatAttachmentsList, MarkdownContent } from '$lib/components/app'; + import { ChatAttachmentsList, LazyMarkdownContent, MarkdownContent } from '$lib/components/app'; import { config } from '$lib/stores/settings.svelte'; import ChatMessageActions from './ChatMessageActions.svelte'; import ChatMessageEditForm from './ChatMessageEditForm.svelte'; @@ -128,7 +128,7 @@ > {#if currentConfig.renderUserContentAsMarkdown}
- diff --git a/tools/server/webui/src/lib/components/app/index.ts b/tools/server/webui/src/lib/components/app/index.ts index 8631d4fb3b..a97fa83688 100644 --- a/tools/server/webui/src/lib/components/app/index.ts +++ b/tools/server/webui/src/lib/components/app/index.ts @@ -62,6 +62,7 @@ export { default as BadgeModality } from './misc/BadgeModality.svelte'; export { default as ConversationSelection } from './misc/ConversationSelection.svelte'; export { default as CopyToClipboardIcon } from './misc/CopyToClipboardIcon.svelte'; export { default as KeyboardShortcutInfo } from './misc/KeyboardShortcutInfo.svelte'; +export { default as LazyMarkdownContent } from './misc/LazyMarkdownContent.svelte'; export { default as MarkdownContent } from './misc/MarkdownContent.svelte'; export { default as RemoveButton } from './misc/RemoveButton.svelte'; export { default as SearchInput } from './misc/SearchInput.svelte'; diff --git a/tools/server/webui/src/lib/components/app/misc/LazyMarkdownContent.svelte b/tools/server/webui/src/lib/components/app/misc/LazyMarkdownContent.svelte new file mode 100644 index 0000000000..e1064e5039 --- /dev/null +++ b/tools/server/webui/src/lib/components/app/misc/LazyMarkdownContent.svelte @@ -0,0 +1,98 @@ + + +
+ {#if hasBeenVisible} + + {:else} + +
+ {content.slice(0, 500)}{content.length > 500 ? '...' : ''} +
+ {/if} +
+ +