fix: lazily load markdown gallery images

This commit is contained in:
Bohdan Triapitsyn
2026-08-13 14:01:26 +03:00
parent 4c80021296
commit 1a1170f9d2
2 changed files with 24 additions and 1 deletions
@@ -14,12 +14,32 @@ const MarkdownImageThumbnail: React.FC<{
directory: string;
onShowPopup?: (content: ToolPopupContent) => void;
}> = ({ candidate, directory, onShowPopup }) => {
const thumbnailRef = React.useRef<HTMLButtonElement>(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 (
<button
ref={thumbnailRef}
type="button"
className="w-[100px] shrink-0 text-left outline-none focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)]"
aria-label={candidate.filename}
@@ -68,6 +68,8 @@ Use this doc when you ask an agent to change tool/header/description behavior.
large inline image. A
completed assistant message hydrates at most 12 unique image candidates,
including persisted text parts that omit their optional part-level end time.
Thumbnail assets begin loading only when their gallery items approach the
viewport, so mounted historical messages do not eagerly read every image.
Gallery clicks do not introduce or alter preview chrome: desktop and mobile
both reuse the pre-existing attachment image preview overlay.
- `read` and `skill` are **static navigation tools** and render via `StaticToolRow`.