diff --git a/components/agents/AgentWorkflowPanelDarkV2.tsx b/components/agents/AgentWorkflowPanelDarkV2.tsx index 42e717273..4127047d6 100644 --- a/components/agents/AgentWorkflowPanelDarkV2.tsx +++ b/components/agents/AgentWorkflowPanelDarkV2.tsx @@ -111,12 +111,12 @@ const VIRTUAL_OPTION_GRID_GAP = 16; const VIRTUAL_OPTION_GRID_MAX_HEIGHT = 640; const VIRTUAL_OPTION_GRID_MIN_CARD_HEIGHT = 122; const VIRTUAL_OPTION_GRID_THRESHOLD = 12; -const WORKFLOW_OPTION_PREVIEW_WIDTH = 512; -const WORKFLOW_OPTION_PREVIEW_QUALITY = 62; -const WORKFLOW_OPTION_PREVIEW_EAGER_COUNT = 10; -const WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT = 12; -const WORKFLOW_OPTION_NEXT_PREVIEW_PREFETCH_COUNT = 24; -const WORKFLOW_OPTION_PREFETCH_CACHE_LIMIT = 320; +const WORKFLOW_OPTION_PREVIEW_WIDTH = 360; +const WORKFLOW_OPTION_PREVIEW_QUALITY = 58; +const WORKFLOW_OPTION_PREVIEW_EAGER_COUNT = 4; +const WORKFLOW_OPTION_NEXT_PREVIEW_PREFETCH_COUNT = 6; +const WORKFLOW_OPTION_PREFETCH_CACHE_LIMIT = 48; +const WORKFLOW_OPTION_PREFETCH_BATCH_SIZE = 2; function isLikelyStoredWorkflowPreview(url: string, sourceUrl?: string | null) { const source = String(sourceUrl || "").trim(); @@ -182,10 +182,81 @@ function trimWorkflowPreviewImageCache( while (cache.size > limit) { const firstKey = cache.keys().next().value; if (!firstKey) return; + const image = cache.get(firstKey); + if (image) { + image.src = ""; + } cache.delete(firstKey); } } +function shouldSkipWorkflowPreviewPrefetch() { + if (typeof document !== "undefined" && document.visibilityState === "hidden") { + return true; + } + + const connection = (typeof navigator !== "undefined" + ? (navigator as Navigator & { + connection?: { saveData?: boolean }; + }).connection + : null); + return Boolean(connection?.saveData); +} + +function preloadWorkflowOptionPreviewImages(params: { + urls: string[]; + cache: Map; + markPrefetched: Set; +}) { + if (typeof window === "undefined" || params.urls.length === 0) { + return () => {}; + } + + let cancelled = false; + let cleanupIdleTask: (() => void) | null = null; + const urls = params.urls.slice(); + + const preloadNextBatch = () => { + if (cancelled || shouldSkipWorkflowPreviewPrefetch()) { + return; + } + + const batch = urls.splice(0, WORKFLOW_OPTION_PREFETCH_BATCH_SIZE); + for (const src of batch) { + if (cancelled || params.cache.has(src)) { + continue; + } + + const image = new Image(); + image.decoding = "async"; + ( + image as HTMLImageElement & { + fetchPriority?: "high" | "low" | "auto"; + } + ).fetchPriority = "low"; + image.onerror = () => { + params.cache.delete(src); + params.markPrefetched.delete(src); + }; + image.src = src; + params.cache.set(src, image); + } + + trimWorkflowPreviewImageCache(params.cache); + + if (urls.length > 0) { + cleanupIdleTask = scheduleWorkflowIdleTask(preloadNextBatch, 900); + } + }; + + cleanupIdleTask = scheduleWorkflowIdleTask(preloadNextBatch, 700); + + return () => { + cancelled = true; + cleanupIdleTask?.(); + }; +} + interface WorkflowPanelDerivedState { progress: ReturnType; } @@ -1177,12 +1248,6 @@ function AgentWorkflowPanelDarkV2Component({ const urls = new Set(); if (embedded) { - appendWorkflowOptionPreviewUrls( - urls, - activeEmbeddedOptionLevel?.options, - WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT - ); - const currentSelectedOption = activeEmbeddedOptionLevel?.selectedOption || null; appendWorkflowOptionPreviewUrls( @@ -1227,11 +1292,6 @@ function AgentWorkflowPanelDarkV2Component({ } } } else { - appendWorkflowOptionPreviewUrls( - urls, - flattenedResolvedSteps[0]?.step.options, - WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT - ); appendWorkflowOptionPreviewUrls( urls, flattenedResolvedSteps[1]?.step.options, @@ -1361,24 +1421,23 @@ function AgentWorkflowPanelDarkV2Component({ workflowPreviewPrefetchedUrlsRef.current.add(src); }); - scheduleWorkflowIdleTask(() => { - urlsToPrefetch.forEach((src, index) => { - const image = new Image(); - image.decoding = "async"; - ( - image as HTMLImageElement & { - fetchPriority?: "high" | "low" | "auto"; - } - ).fetchPriority = - index < WORKFLOW_OPTION_PREVIEW_EAGER_COUNT ? "high" : "low"; - image.src = src; - workflowPreviewImageCacheRef.current.set(src, image); - }); - - trimWorkflowPreviewImageCache(workflowPreviewImageCacheRef.current); - }, 80); + return preloadWorkflowOptionPreviewImages({ + urls: urlsToPrefetch, + cache: workflowPreviewImageCacheRef.current, + markPrefetched: workflowPreviewPrefetchedUrlsRef.current, + }); }, [workflowOptionPreviewPrefetchUrls]); + useEffect(() => { + return () => { + workflowPreviewImageCacheRef.current.forEach((image) => { + image.src = ""; + }); + workflowPreviewImageCacheRef.current.clear(); + workflowPreviewPrefetchedUrlsRef.current.clear(); + }; + }, []); + useEffect(() => { if (!embedded || !activeEmbeddedStepEntry) { return;