fix: lazily load markdown gallery images
This commit is contained in:
@@ -14,12 +14,32 @@ const MarkdownImageThumbnail: React.FC<{
|
|||||||
directory: string;
|
directory: string;
|
||||||
onShowPopup?: (content: ToolPopupContent) => void;
|
onShowPopup?: (content: ToolPopupContent) => void;
|
||||||
}> = ({ candidate, directory, onShowPopup }) => {
|
}> = ({ candidate, directory, onShowPopup }) => {
|
||||||
|
const thumbnailRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
const [shouldLoad, setShouldLoad] = React.useState(false);
|
||||||
const [image, setImage] = React.useState<{
|
const [image, setImage] = React.useState<{
|
||||||
url: string;
|
url: string;
|
||||||
status: 'loading' | 'ready' | 'error';
|
status: 'loading' | 'ready' | 'error';
|
||||||
}>({ url: '', status: 'loading' });
|
}>({ url: '', status: 'loading' });
|
||||||
|
|
||||||
React.useEffect(() => {
|
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();
|
const controller = new AbortController();
|
||||||
setImage({ url: '', status: 'loading' });
|
setImage({ url: '', status: 'loading' });
|
||||||
void resolveMarkdownImageSource(candidate.source, directory, controller.signal)
|
void resolveMarkdownImageSource(candidate.source, directory, controller.signal)
|
||||||
@@ -30,7 +50,7 @@ const MarkdownImageThumbnail: React.FC<{
|
|||||||
if (!controller.signal.aborted) setImage({ url: '', status: 'error' });
|
if (!controller.signal.aborted) setImage({ url: '', status: 'error' });
|
||||||
});
|
});
|
||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [candidate.source, directory]);
|
}, [candidate.source, directory, shouldLoad]);
|
||||||
|
|
||||||
const openPreview = React.useCallback(() => {
|
const openPreview = React.useCallback(() => {
|
||||||
if (image.status !== 'ready' || !onShowPopup) return;
|
if (image.status !== 'ready' || !onShowPopup) return;
|
||||||
@@ -45,6 +65,7 @@ const MarkdownImageThumbnail: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
ref={thumbnailRef}
|
||||||
type="button"
|
type="button"
|
||||||
className="w-[100px] shrink-0 text-left outline-none focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)]"
|
className="w-[100px] shrink-0 text-left outline-none focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)]"
|
||||||
aria-label={candidate.filename}
|
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
|
large inline image. A
|
||||||
completed assistant message hydrates at most 12 unique image candidates,
|
completed assistant message hydrates at most 12 unique image candidates,
|
||||||
including persisted text parts that omit their optional part-level end time.
|
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
|
Gallery clicks do not introduce or alter preview chrome: desktop and mobile
|
||||||
both reuse the pre-existing attachment image preview overlay.
|
both reuse the pre-existing attachment image preview overlay.
|
||||||
- `read` and `skill` are **static navigation tools** and render via `StaticToolRow`.
|
- `read` and `skill` are **static navigation tools** and render via `StaticToolRow`.
|
||||||
|
|||||||
Reference in New Issue
Block a user