diff --git a/components/agents/AgentPsdTemplateEditorDialog.tsx b/components/agents/AgentPsdTemplateEditorDialog.tsx index 04baf125e..c22b4aabf 100644 --- a/components/agents/AgentPsdTemplateEditorDialog.tsx +++ b/components/agents/AgentPsdTemplateEditorDialog.tsx @@ -271,6 +271,111 @@ function buildTemplatePreviewThumbUrl( return `/api/image-proxy?${searchParams.toString()}`; } +function buildTemplatePreviewFallbackUrls( + template: Pick, + params?: { + width?: number; + quality?: number; + preferRenderBase?: boolean; + } +) { + const sourceUrls = ( + params?.preferRenderBase + ? [template.renderBaseUrl, template.previewUrl] + : [template.previewUrl, template.renderBaseUrl] + ) + .map((url) => String(url || "").trim()) + .filter(Boolean); + const candidates = [ + ...sourceUrls.map((url) => + buildTemplatePreviewThumbUrl(url, { + width: params?.width, + quality: params?.quality, + }) + ), + ...sourceUrls, + ]; + const seen = new Set(); + + return candidates.filter((url) => { + if (!url || seen.has(url)) { + return false; + } + seen.add(url); + return true; + }); +} + +function TemplatePreviewImage({ + template, + alt, + className, + width, + quality, + preferRenderBase, + decoding = "async", + fetchPriority, + loading = "lazy", +}: { + template: Pick; + alt?: string; + className?: string; + width?: number; + quality?: number; + preferRenderBase?: boolean; + decoding?: "async" | "auto" | "sync"; + fetchPriority?: "auto" | "high" | "low"; + loading?: "eager" | "lazy"; +}) { + const fallbackUrls = useMemo( + () => + buildTemplatePreviewFallbackUrls(template, { + width, + quality, + preferRenderBase, + }), + [preferRenderBase, quality, template.previewUrl, template.renderBaseUrl, width] + ); + const fallbackUrlKey = fallbackUrls.join("\n"); + const [activeUrlIndex, setActiveUrlIndex] = useState(0); + + useEffect(() => { + setActiveUrlIndex(0); + }, [fallbackUrlKey]); + + const src = fallbackUrls[activeUrlIndex] || ""; + if (!src) { + return ( +
+ ); + } + const intrinsicSize = Math.max(1, Math.round(width || 1)); + + return ( + // biome-ignore lint/a11y/noNoninteractiveElementInteractions: the image swaps to the next remote fallback when a thumbnail URL fails. + // biome-ignore lint/performance/noImgElement: these dynamic PSD previews already use the app image proxy and need browser-level error fallback. + {alt + setActiveUrlIndex((current) => + current + 1 < fallbackUrls.length ? current + 1 : current + ) + } + src={src} + width={intrinsicSize} + /> + ); +} + function getScrollAreaViewportElement(root: HTMLDivElement | null) { return root?.querySelector( "[data-radix-scroll-area-viewport]" @@ -521,14 +626,11 @@ function renderTemplateSelectContent( : "flex h-[46px] w-[46px] shrink-0 overflow-hidden rounded-lg border border-white/10 bg-white/5" } > - {template.name}
@@ -580,15 +682,12 @@ function renderTemplatePickerCardContent( return (
- {template.name}
@@ -2876,17 +2975,12 @@ export function AgentPsdTemplateEditorDialog({ aspectRatio: `${Math.max(1, selectedTemplate.width)} / ${Math.max(1, selectedTemplate.height)}`, }} > - {selectedTemplate.name}
@@ -2966,18 +3060,14 @@ export function AgentPsdTemplateEditorDialog({ > {shouldShowCompositeFallback && orderedVisiblePreviewLayers.length > 0 ? ( - {`${selectedTemplate.name} ) : null}