From 1a1170f9d28e5071ebfdc6eb5491c48f918af115 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 13 Aug 2026 14:01:26 +0300 Subject: [PATCH] fix: lazily load markdown gallery images --- .../components/chat/MarkdownImageGallery.tsx | 23 ++++++++++++++++++- .../chat/message/parts/DOCUMENTATION.md | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/chat/MarkdownImageGallery.tsx b/packages/ui/src/components/chat/MarkdownImageGallery.tsx index bdb2aa27..faffc569 100644 --- a/packages/ui/src/components/chat/MarkdownImageGallery.tsx +++ b/packages/ui/src/components/chat/MarkdownImageGallery.tsx @@ -14,12 +14,32 @@ const MarkdownImageThumbnail: React.FC<{ directory: string; onShowPopup?: (content: ToolPopupContent) => void; }> = ({ candidate, directory, onShowPopup }) => { + const thumbnailRef = React.useRef(null); + const [shouldLoad, setShouldLoad] = React.useState(false); const [image, setImage] = React.useState<{ url: string; status: 'loading' | 'ready' | 'error'; }>({ url: '', status: 'loading' }); React.useEffect(() => { + const thumbnail = thumbnailRef.current; + if (!thumbnail || shouldLoad) return; + if (typeof IntersectionObserver === 'undefined') { + setShouldLoad(true); + return; + } + + const observer = new IntersectionObserver((entries) => { + if (!entries.some((entry) => entry.isIntersecting)) return; + setShouldLoad(true); + observer.disconnect(); + }, { rootMargin: '200px' }); + observer.observe(thumbnail); + return () => observer.disconnect(); + }, [shouldLoad]); + + React.useEffect(() => { + if (!shouldLoad) return; const controller = new AbortController(); setImage({ url: '', status: 'loading' }); void resolveMarkdownImageSource(candidate.source, directory, controller.signal) @@ -30,7 +50,7 @@ const MarkdownImageThumbnail: React.FC<{ if (!controller.signal.aborted) setImage({ url: '', status: 'error' }); }); return () => controller.abort(); - }, [candidate.source, directory]); + }, [candidate.source, directory, shouldLoad]); const openPreview = React.useCallback(() => { if (image.status !== 'ready' || !onShowPopup) return; @@ -45,6 +65,7 @@ const MarkdownImageThumbnail: React.FC<{ return (