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_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<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 {
progress: ReturnType<typeof getAgentWorkflowProgress>;
}
@ -1177,12 +1248,6 @@ function AgentWorkflowPanelDarkV2Component({
const urls = new Set<string>();
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;