perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突

This commit is contained in:
ddCat
2026-09-05 13:24:10 +08:00
parent 165b4443ab
commit 58f01d1eb6
107 changed files with 3025 additions and 1089 deletions

View File

@@ -2,6 +2,7 @@ import { canvasNodeToAsset, declaredCanvasNodeAssetCategory, findCanvasNodeAsset
import { canvasVideoAssetPreviewUrl } from "@/lib/canvas/canvas-media-preview";
import { readImageMeta } from "@/lib/image-utils";
import { parseBackendGenerationResult, type BackendGenerationResult } from "@/services/api/generation-task";
import { ApiError } from "@/services/api/request";
import { linkProjectAsset, moveProjectAsset, updateProjectAssetCategory } from "@/services/api/projects";
import type { GenerationTask, GenerationTaskOutput } from "@/services/api/task-center";
import { getMediaBlob, resolveMediaUrl, setMediaBlob } from "@/services/file-storage";
@@ -41,6 +42,47 @@ export type CanvasNodeAssetResult = {
};
const pendingAssetSyncs = new Map<string, Promise<CanvasNodeAssetResult>>();
const DEFAULT_RATE_LIMIT_RETRY_MS = 60_000;
const MAX_RATE_LIMIT_RETRY_MS = 5 * 60_000;
type CanvasAssetSyncRetryOptions = {
signal?: AbortSignal;
maxRetries?: number;
wait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
};
function waitForCanvasAssetSyncRetry(delayMs: number, signal?: AbortSignal) {
return new Promise<void>((resolve, reject) => {
if (signal?.aborted) {
reject(new DOMException("The operation was aborted", "AbortError"));
return;
}
const timer = globalThis.setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, delayMs);
const onAbort = () => {
globalThis.clearTimeout(timer);
reject(new DOMException("The operation was aborted", "AbortError"));
};
signal?.addEventListener("abort", onAbort, { once: true });
});
}
export async function retryCanvasAssetSyncAfterRateLimit<T>(operation: () => Promise<T>, options: CanvasAssetSyncRetryOptions = {}): Promise<T> {
const maxRetries = Math.max(0, options.maxRetries ?? 2);
const wait = options.wait ?? waitForCanvasAssetSyncRetry;
for (let attempt = 0; ; attempt += 1) {
throwIfAborted(options.signal);
try {
return await operation();
} catch (error) {
if (!(error instanceof ApiError) || error.status !== 429 || attempt >= maxRetries) throw error;
const delayMs = Math.min(MAX_RATE_LIMIT_RETRY_MS, Math.max(0, error.retryAfterMs ?? DEFAULT_RATE_LIMIT_RETRY_MS));
await wait(delayMs, options.signal);
}
}
}
export function ensureCanvasNodeAsset(options: EnsureCanvasNodeAssetOptions) {
const scope = getActiveUserScope();