From 291eb9b11ce1235e59e930f23eb4be35a9fc5a68 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 13:56:21 +0800 Subject: [PATCH] fix: improve material preview loading --- .../agent-workflow/material-search/route.ts | 3 ++ components/agents/AgentPreviewImage.tsx | 43 ++++++++++++++---- .../agents/AgentWorkflowMaterialPicker.tsx | 44 +++++++++++++++++-- lib/agents/workflow-material-config.ts | 2 + types/agent.ts | 1 + 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/app/api/agent-workflow/material-search/route.ts b/app/api/agent-workflow/material-search/route.ts index 476dbd22c..43640c112 100644 --- a/app/api/agent-workflow/material-search/route.ts +++ b/app/api/agent-workflow/material-search/route.ts @@ -34,6 +34,7 @@ type ExternalImageRecord = { label?: string; fileName?: string; fileSize?: number; + previewUrl?: string; downloadUrl?: string; }; @@ -81,6 +82,7 @@ type MaterialSearchImage = { label: string; fileName: string; imageUrl: string; + previewUrl: string | null; assetId: string | null; mimeType: string; group: "fixed" | "other"; @@ -611,6 +613,7 @@ function mapExternalImage(params: { label: String(params.image.label || "").trim() || `图片${params.sortOrder + 1}`, fileName: filename, imageUrl: downloadUrl, + previewUrl: String(params.image.previewUrl || "").trim() || null, assetId: null, mimeType: inferMimeTypeFromFilename(filename), group: params.group, diff --git a/components/agents/AgentPreviewImage.tsx b/components/agents/AgentPreviewImage.tsx index 8fa069b98..e013d0244 100644 --- a/components/agents/AgentPreviewImage.tsx +++ b/components/agents/AgentPreviewImage.tsx @@ -18,6 +18,9 @@ type AgentPreviewImageProps = Omit< previewWidth?: number; previewQuality?: number; previewFormat?: "webp" | "avif" | "jpeg" | "png"; + delivery?: "display" | "proxy"; + fallbackSrc?: string | null; + fallbackToOriginal?: boolean; rootMargin?: string; eager?: boolean; raw?: boolean; @@ -31,6 +34,7 @@ function buildPreviewUrl(params: { width: number; quality: number; format: "webp" | "avif" | "jpeg" | "png"; + delivery?: "display" | "proxy"; raw?: boolean; }) { if (params.raw) return params.src; @@ -39,6 +43,7 @@ function buildPreviewUrl(params: { width: params.width, quality: params.quality, format: params.format, + delivery: params.delivery, }) || params.src ); } @@ -48,6 +53,9 @@ function AgentPreviewImageBase({ previewWidth = 480, previewQuality = 78, previewFormat = "webp", + delivery = "display", + fallbackSrc, + fallbackToOriginal = true, rootMargin = "640px 0px", eager = false, raw = false, @@ -58,15 +66,22 @@ function AgentPreviewImageBase({ const imageRef = useRef(null); const [shouldLoad, setShouldLoad] = useState(eager); const [useOriginal, setUseOriginal] = useState(false); + const [useFallbackSource, setUseFallbackSource] = useState(false); const normalizedSrc = String(src || "").trim(); + const normalizedFallbackSrc = String(fallbackSrc || "").trim(); + const currentSrc = + useFallbackSource && normalizedFallbackSrc + ? normalizedFallbackSrc + : normalizedSrc; useEffect(() => { setUseOriginal(false); + setUseFallbackSource(false); setShouldLoad(eager); - }, [eager, normalizedSrc]); + }, [eager, normalizedFallbackSrc, normalizedSrc]); useEffect(() => { - if (eager || shouldLoad || !normalizedSrc) return; + if (eager || shouldLoad || !currentSrc) return; const image = imageRef.current; if (!image) return; @@ -86,20 +101,22 @@ function AgentPreviewImageBase({ ); observer.observe(image); return () => observer.disconnect(); - }, [eager, normalizedSrc, rootMargin, shouldLoad]); + }, [currentSrc, eager, rootMargin, shouldLoad]); const displaySrc = useMemo(() => { - if (!normalizedSrc || !shouldLoad) return EMPTY_PIXEL; - if (useOriginal) return normalizedSrc; + if (!currentSrc || !shouldLoad) return EMPTY_PIXEL; + if (useOriginal) return currentSrc; return buildPreviewUrl({ - src: normalizedSrc, + src: currentSrc, width: previewWidth, quality: previewQuality, format: previewFormat, + delivery, raw, }); }, [ - normalizedSrc, + currentSrc, + delivery, previewFormat, previewQuality, previewWidth, @@ -117,10 +134,20 @@ function AgentPreviewImageBase({ loading={eager ? "eager" : "lazy"} decoding="async" onError={(event) => { - if (normalizedSrc && !useOriginal && displaySrc !== normalizedSrc) { + if ( + fallbackToOriginal && + currentSrc && + !useOriginal && + displaySrc !== currentSrc + ) { setUseOriginal(true); return; } + if (!useFallbackSource && normalizedFallbackSrc) { + setUseFallbackSource(true); + setUseOriginal(false); + return; + } onError?.(event); }} onLoad={onLoad} diff --git a/components/agents/AgentWorkflowMaterialPicker.tsx b/components/agents/AgentWorkflowMaterialPicker.tsx index 2f8f27955..14e13c060 100644 --- a/components/agents/AgentWorkflowMaterialPicker.tsx +++ b/components/agents/AgentWorkflowMaterialPicker.tsx @@ -75,6 +75,17 @@ const MaterialImageCard = memo(function MaterialImageCard({ onToggle: (imageId: string) => void; }) { const isPlaceholder = !String(image.imageUrl || "").trim(); + const previewUrl = String(image.previewUrl || "").trim(); + const displayImageUrl = previewUrl || String(image.imageUrl || "").trim(); + const fallbackImageUrl = previewUrl ? String(image.imageUrl || "").trim() : null; + const [imageStatus, setImageStatus] = useState<"loading" | "loaded" | "error">( + isPlaceholder ? "loaded" : "loading" + ); + + useEffect(() => { + setImageStatus(isPlaceholder ? "loaded" : "loading"); + }, [displayImageUrl, fallbackImageUrl, isPlaceholder]); + return (