"use client"; import { useEffect, useState, useSyncExternalStore } from "react"; import { useTheme } from "@/components/theme/app-theme"; import { getBackendConfig } from "@/lib/config"; interface AgentLogoProps { agentName: string; size?: number; className?: string; } const emptySubscribe = () => () => {}; // Module-level cache: logo URL → object URL (blob). Survives re-renders and remounts. const logoCache = new Map(); function fetchLogo(url: string): Promise { const cached = logoCache.get(url); if (cached) return Promise.resolve(cached); return fetch(url) .then((res) => { if (!res.ok) throw new Error(`Logo fetch failed: ${res.status}`); return res.blob(); }) .then((blob) => { const objectUrl = URL.createObjectURL(blob); logoCache.set(url, objectUrl); return objectUrl; }); } /** Wrapper that re-mounts the inner image when agentName changes, resetting error state. */ export function AgentLogo({ agentName, size = 16, className }: AgentLogoProps) { return ; } function AgentLogoImage({ agentName, size = 16, className }: AgentLogoProps) { const { resolvedTheme } = useTheme(); const mounted = useSyncExternalStore( emptySubscribe, () => true, () => false, ); const [fetchedSrc, setFetchedSrc] = useState(null); const [error, setError] = useState(false); const variant = resolvedTheme === "dark" ? "dark" : "light"; const logoUrl = mounted ? `${getBackendConfig().apiBaseUrl}/api/v1/agents/${encodeURIComponent(agentName)}/logo?variant=${variant}` : null; // Read from cache during render to avoid setState in effect const cachedSrc = logoUrl ? (logoCache.get(logoUrl) ?? null) : null; useEffect(() => { if (!logoUrl || logoCache.has(logoUrl)) return; fetchLogo(logoUrl) .then(setFetchedSrc) .catch(() => setError(true)); }, [logoUrl]); const src = cachedSrc ?? fetchedSrc; if (!mounted || error || !src) { return ( ); } return ; }