Fix canvas generation cleanup

This commit is contained in:
2026-06-10 14:12:14 +08:00
parent 77ffd01a50
commit bfb70bab26
3 changed files with 81 additions and 18 deletions
+30 -8
View File
@@ -371,12 +371,19 @@ function CanvasPage({
const textNodeIdRef = useRef(9);
const imageNodeIdRef = useRef(1);
const videoNodeIdRef = useRef(1);
const objectUrlsRef = useRef(new Set<string>());
const trackObjectUrl = (file: Blob) => {
const url = URL.createObjectURL(file);
objectUrlsRef.current.add(url);
return url;
};
const { pushSnapshot, undo, redo } = useCanvasHistory();
const {
textGenerationState, imageGenerationState, videoGenerationState,
generationToast, setGenerationToast,
imageGenerationInFlightRef, videoGenerationInFlightRef, textGenerationInFlightRef, textGenerationAbortControllersRef,
imageGenerationAbortRef, videoGenerationAbortRef,
canvasGenKeepaliveRestoredRef,
setTextGenerationStatus, setImageGenerationStatus, setVideoGenerationStatus,
restoreKeepaliveTasks, resetGenerationState,
@@ -527,6 +534,7 @@ function CanvasPage({
const autoSaveStatusTimerRef = useRef<number | null>(null);
useEffect(() => {
const objectUrls = objectUrlsRef.current;
return () => {
if (canvasAutoSaveTimerRef.current !== null) window.clearTimeout(canvasAutoSaveTimerRef.current);
if (canvasAutoSaveRetryTimerRef.current !== null) window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
@@ -535,6 +543,8 @@ function CanvasPage({
if (canvasAutoSaveIdleHandleRef.current !== null && "cancelIdleCallback" in window) {
window.cancelIdleCallback(canvasAutoSaveIdleHandleRef.current);
}
objectUrls.forEach((url) => URL.revokeObjectURL(url));
objectUrls.clear();
};
}, []);
@@ -1691,12 +1701,15 @@ function CanvasPage({
const quality = resolveImageQuality(model, imageNode.imageSize || "");
imageGenerationInFlightRef.current.add(nodeId);
const abortRef = { current: false };
imageGenerationAbortRef.current.set(nodeId, abortRef);
setImageGenerationStatus(nodeId, { status: "submitting", message: "正在提交生成", progress: 8 });
setGenerationToast("图片正在生成");
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
try {
const referenceUrls = await resolveConnectedImageReferenceUrls("image", nodeId, imageNode);
if (abortRef.current) return;
const taskInput: CreatePreviewTaskInput = {
title: imageNode.title || "图片节点生成",
type: "image",
@@ -1732,7 +1745,8 @@ function CanvasPage({
? "图片生成完成"
: "图片生成失败";
setImageGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
}));
}, abortRef));
if (abortRef.current || !outputUrl) return;
setImageGenerationStatus(nodeId, { status: "success", message: "生成完成", progress: 100 });
removeCanvasGenKeepalive(task.id);
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
@@ -1794,13 +1808,15 @@ function CanvasPage({
);
}
} catch (error) {
if (abortRef.current) return;
setImageGenerationStatus(nodeId, {
status: "error",
message: error instanceof Error ? error.message : "图片生成失败",
});
} finally {
imageGenerationInFlightRef.current.delete(nodeId);
if (task?.id) removeCanvasGenKeepalive(task.id);
imageGenerationAbortRef.current.delete(nodeId);
if (task?.id && !abortRef.current) removeCanvasGenKeepalive(task.id);
}
};
@@ -1843,12 +1859,15 @@ function CanvasPage({
const duration = Number(videoNode.duration) || 4;
videoGenerationInFlightRef.current.add(nodeId);
const abortRef = { current: false };
videoGenerationAbortRef.current.set(nodeId, abortRef);
setVideoGenerationStatus(nodeId, { status: "submitting", message: "正在提交视频生成", progress: 8 });
setGenerationToast("视频正在生成");
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
try {
const referenceUrls = await resolveConnectedImageReferenceUrls("video", nodeId);
if (abortRef.current) return;
if (videoNode.videoMode === "img2video" && referenceUrls.length === 0) {
throw new Error("图生视频需要先连接至少一个可用的图片节点");
}
@@ -1892,7 +1911,8 @@ function CanvasPage({
? "视频生成完成"
: "视频生成失败";
setVideoGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
}));
}, abortRef));
if (abortRef.current || !outputUrl) return;
setVideoGenerationStatus(nodeId, { status: "success", message: "视频生成完成", progress: 100 });
removeCanvasGenKeepalive(taskId);
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
@@ -1948,13 +1968,15 @@ function CanvasPage({
);
}
} catch (error) {
if (abortRef.current) return;
setVideoGenerationStatus(nodeId, {
status: "error",
message: error instanceof Error ? error.message : "视频生成失败",
});
} finally {
videoGenerationInFlightRef.current.delete(nodeId);
if (task?.id) removeCanvasGenKeepalive(task.id);
videoGenerationAbortRef.current.delete(nodeId);
if (task?.id && !abortRef.current) removeCanvasGenKeepalive(task.id);
}
};
@@ -1965,7 +1987,7 @@ function CanvasPage({
const file = event.target.files?.[0];
event.target.value = "";
if (!file) return;
const imageUrl = URL.createObjectURL(file);
const imageUrl = trackObjectUrl(file);
if (pendingImageToImageNodeId) {
const sourceNode = imageNodes.find((node) => node.id === pendingImageToImageNodeId);
if (sourceNode) {
@@ -2047,7 +2069,7 @@ function CanvasPage({
let offsetX = 0;
let offsetY = 0;
for (const file of files) {
const imageUrl = URL.createObjectURL(file);
const imageUrl = trackObjectUrl(file);
addImageNode(imageUrl, file.name, {
x: dropPosition.x + offsetX,
y: dropPosition.y + offsetY,
@@ -2103,7 +2125,7 @@ function CanvasPage({
let offsetX = 0;
let offsetY = 0;
for (const file of files) {
const imageUrl = URL.createObjectURL(file);
const imageUrl = trackObjectUrl(file);
addImageNode(imageUrl, file.name, {
x: sourceNode.position.x + sourceNode.size.width + 40 + offsetX,
y: sourceNode.position.y + offsetY,
@@ -5279,7 +5301,7 @@ function CanvasPage({
onChange={(event) => {
const file = event.target.files?.[0];
if (!file) return;
setAssetCoverUrl(URL.createObjectURL(file));
setAssetCoverUrl(trackObjectUrl(file));
setCoverSourceOpen(false);
}}
/>