fix: throttle workflow preview preloading
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-15 09:36:16 +08:00
parent edc24cb956
commit 4e49e5e5b5

View File

@ -111,12 +111,12 @@ const VIRTUAL_OPTION_GRID_GAP = 16;
const VIRTUAL_OPTION_GRID_MAX_HEIGHT = 640; const VIRTUAL_OPTION_GRID_MAX_HEIGHT = 640;
const VIRTUAL_OPTION_GRID_MIN_CARD_HEIGHT = 122; const VIRTUAL_OPTION_GRID_MIN_CARD_HEIGHT = 122;
const VIRTUAL_OPTION_GRID_THRESHOLD = 12; const VIRTUAL_OPTION_GRID_THRESHOLD = 12;
const WORKFLOW_OPTION_PREVIEW_WIDTH = 512; const WORKFLOW_OPTION_PREVIEW_WIDTH = 360;
const WORKFLOW_OPTION_PREVIEW_QUALITY = 62; const WORKFLOW_OPTION_PREVIEW_QUALITY = 58;
const WORKFLOW_OPTION_PREVIEW_EAGER_COUNT = 10; const WORKFLOW_OPTION_PREVIEW_EAGER_COUNT = 4;
const WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT = 12; const WORKFLOW_OPTION_NEXT_PREVIEW_PREFETCH_COUNT = 6;
const WORKFLOW_OPTION_NEXT_PREVIEW_PREFETCH_COUNT = 24; const WORKFLOW_OPTION_PREFETCH_CACHE_LIMIT = 48;
const WORKFLOW_OPTION_PREFETCH_CACHE_LIMIT = 320; const WORKFLOW_OPTION_PREFETCH_BATCH_SIZE = 2;
function isLikelyStoredWorkflowPreview(url: string, sourceUrl?: string | null) { function isLikelyStoredWorkflowPreview(url: string, sourceUrl?: string | null) {
const source = String(sourceUrl || "").trim(); const source = String(sourceUrl || "").trim();
@ -182,10 +182,81 @@ function trimWorkflowPreviewImageCache(
while (cache.size > limit) { while (cache.size > limit) {
const firstKey = cache.keys().next().value; const firstKey = cache.keys().next().value;
if (!firstKey) return; if (!firstKey) return;
const image = cache.get(firstKey);
if (image) {
image.src = "";
}
cache.delete(firstKey); 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<string, HTMLImageElement>;
markPrefetched: Set<string>;
}) {
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 { interface WorkflowPanelDerivedState {
progress: ReturnType<typeof getAgentWorkflowProgress>; progress: ReturnType<typeof getAgentWorkflowProgress>;
} }
@ -1177,12 +1248,6 @@ function AgentWorkflowPanelDarkV2Component({
const urls = new Set<string>(); const urls = new Set<string>();
if (embedded) { if (embedded) {
appendWorkflowOptionPreviewUrls(
urls,
activeEmbeddedOptionLevel?.options,
WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT
);
const currentSelectedOption = const currentSelectedOption =
activeEmbeddedOptionLevel?.selectedOption || null; activeEmbeddedOptionLevel?.selectedOption || null;
appendWorkflowOptionPreviewUrls( appendWorkflowOptionPreviewUrls(
@ -1227,11 +1292,6 @@ function AgentWorkflowPanelDarkV2Component({
} }
} }
} else { } else {
appendWorkflowOptionPreviewUrls(
urls,
flattenedResolvedSteps[0]?.step.options,
WORKFLOW_OPTION_PREVIEW_PREFETCH_COUNT
);
appendWorkflowOptionPreviewUrls( appendWorkflowOptionPreviewUrls(
urls, urls,
flattenedResolvedSteps[1]?.step.options, flattenedResolvedSteps[1]?.step.options,
@ -1361,24 +1421,23 @@ function AgentWorkflowPanelDarkV2Component({
workflowPreviewPrefetchedUrlsRef.current.add(src); workflowPreviewPrefetchedUrlsRef.current.add(src);
}); });
scheduleWorkflowIdleTask(() => { return preloadWorkflowOptionPreviewImages({
urlsToPrefetch.forEach((src, index) => { urls: urlsToPrefetch,
const image = new Image(); cache: workflowPreviewImageCacheRef.current,
image.decoding = "async"; markPrefetched: workflowPreviewPrefetchedUrlsRef.current,
( });
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);
}, [workflowOptionPreviewPrefetchUrls]); }, [workflowOptionPreviewPrefetchUrls]);
useEffect(() => {
return () => {
workflowPreviewImageCacheRef.current.forEach((image) => {
image.src = "";
});
workflowPreviewImageCacheRef.current.clear();
workflowPreviewPrefetchedUrlsRef.current.clear();
};
}, []);
useEffect(() => { useEffect(() => {
if (!embedded || !activeEmbeddedStepEntry) { if (!embedded || !activeEmbeddedStepEntry) {
return; return;