Fix canvas generation cleanup
This commit is contained in:
@@ -371,12 +371,19 @@ function CanvasPage({
|
|||||||
const textNodeIdRef = useRef(9);
|
const textNodeIdRef = useRef(9);
|
||||||
const imageNodeIdRef = useRef(1);
|
const imageNodeIdRef = useRef(1);
|
||||||
const videoNodeIdRef = 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 { pushSnapshot, undo, redo } = useCanvasHistory();
|
||||||
const {
|
const {
|
||||||
textGenerationState, imageGenerationState, videoGenerationState,
|
textGenerationState, imageGenerationState, videoGenerationState,
|
||||||
generationToast, setGenerationToast,
|
generationToast, setGenerationToast,
|
||||||
imageGenerationInFlightRef, videoGenerationInFlightRef, textGenerationInFlightRef, textGenerationAbortControllersRef,
|
imageGenerationInFlightRef, videoGenerationInFlightRef, textGenerationInFlightRef, textGenerationAbortControllersRef,
|
||||||
|
imageGenerationAbortRef, videoGenerationAbortRef,
|
||||||
canvasGenKeepaliveRestoredRef,
|
canvasGenKeepaliveRestoredRef,
|
||||||
setTextGenerationStatus, setImageGenerationStatus, setVideoGenerationStatus,
|
setTextGenerationStatus, setImageGenerationStatus, setVideoGenerationStatus,
|
||||||
restoreKeepaliveTasks, resetGenerationState,
|
restoreKeepaliveTasks, resetGenerationState,
|
||||||
@@ -527,6 +534,7 @@ function CanvasPage({
|
|||||||
const autoSaveStatusTimerRef = useRef<number | null>(null);
|
const autoSaveStatusTimerRef = useRef<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const objectUrls = objectUrlsRef.current;
|
||||||
return () => {
|
return () => {
|
||||||
if (canvasAutoSaveTimerRef.current !== null) window.clearTimeout(canvasAutoSaveTimerRef.current);
|
if (canvasAutoSaveTimerRef.current !== null) window.clearTimeout(canvasAutoSaveTimerRef.current);
|
||||||
if (canvasAutoSaveRetryTimerRef.current !== null) window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
if (canvasAutoSaveRetryTimerRef.current !== null) window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
||||||
@@ -535,6 +543,8 @@ function CanvasPage({
|
|||||||
if (canvasAutoSaveIdleHandleRef.current !== null && "cancelIdleCallback" in window) {
|
if (canvasAutoSaveIdleHandleRef.current !== null && "cancelIdleCallback" in window) {
|
||||||
window.cancelIdleCallback(canvasAutoSaveIdleHandleRef.current);
|
window.cancelIdleCallback(canvasAutoSaveIdleHandleRef.current);
|
||||||
}
|
}
|
||||||
|
objectUrls.forEach((url) => URL.revokeObjectURL(url));
|
||||||
|
objectUrls.clear();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -1691,12 +1701,15 @@ function CanvasPage({
|
|||||||
const quality = resolveImageQuality(model, imageNode.imageSize || "");
|
const quality = resolveImageQuality(model, imageNode.imageSize || "");
|
||||||
|
|
||||||
imageGenerationInFlightRef.current.add(nodeId);
|
imageGenerationInFlightRef.current.add(nodeId);
|
||||||
|
const abortRef = { current: false };
|
||||||
|
imageGenerationAbortRef.current.set(nodeId, abortRef);
|
||||||
setImageGenerationStatus(nodeId, { status: "submitting", message: "正在提交生成", progress: 8 });
|
setImageGenerationStatus(nodeId, { status: "submitting", message: "正在提交生成", progress: 8 });
|
||||||
setGenerationToast("图片正在生成");
|
setGenerationToast("图片正在生成");
|
||||||
|
|
||||||
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
|
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
|
||||||
try {
|
try {
|
||||||
const referenceUrls = await resolveConnectedImageReferenceUrls("image", nodeId, imageNode);
|
const referenceUrls = await resolveConnectedImageReferenceUrls("image", nodeId, imageNode);
|
||||||
|
if (abortRef.current) return;
|
||||||
const taskInput: CreatePreviewTaskInput = {
|
const taskInput: CreatePreviewTaskInput = {
|
||||||
title: imageNode.title || "图片节点生成",
|
title: imageNode.title || "图片节点生成",
|
||||||
type: "image",
|
type: "image",
|
||||||
@@ -1732,7 +1745,8 @@ function CanvasPage({
|
|||||||
? "图片生成完成"
|
? "图片生成完成"
|
||||||
: "图片生成失败";
|
: "图片生成失败";
|
||||||
setImageGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
|
setImageGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
|
||||||
}));
|
}, abortRef));
|
||||||
|
if (abortRef.current || !outputUrl) return;
|
||||||
setImageGenerationStatus(nodeId, { status: "success", message: "生成完成", progress: 100 });
|
setImageGenerationStatus(nodeId, { status: "success", message: "生成完成", progress: 100 });
|
||||||
removeCanvasGenKeepalive(task.id);
|
removeCanvasGenKeepalive(task.id);
|
||||||
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
|
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
|
||||||
@@ -1794,13 +1808,15 @@ function CanvasPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (abortRef.current) return;
|
||||||
setImageGenerationStatus(nodeId, {
|
setImageGenerationStatus(nodeId, {
|
||||||
status: "error",
|
status: "error",
|
||||||
message: error instanceof Error ? error.message : "图片生成失败",
|
message: error instanceof Error ? error.message : "图片生成失败",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
imageGenerationInFlightRef.current.delete(nodeId);
|
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;
|
const duration = Number(videoNode.duration) || 4;
|
||||||
|
|
||||||
videoGenerationInFlightRef.current.add(nodeId);
|
videoGenerationInFlightRef.current.add(nodeId);
|
||||||
|
const abortRef = { current: false };
|
||||||
|
videoGenerationAbortRef.current.set(nodeId, abortRef);
|
||||||
setVideoGenerationStatus(nodeId, { status: "submitting", message: "正在提交视频生成", progress: 8 });
|
setVideoGenerationStatus(nodeId, { status: "submitting", message: "正在提交视频生成", progress: 8 });
|
||||||
setGenerationToast("视频正在生成");
|
setGenerationToast("视频正在生成");
|
||||||
|
|
||||||
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
|
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
|
||||||
try {
|
try {
|
||||||
const referenceUrls = await resolveConnectedImageReferenceUrls("video", nodeId);
|
const referenceUrls = await resolveConnectedImageReferenceUrls("video", nodeId);
|
||||||
|
if (abortRef.current) return;
|
||||||
if (videoNode.videoMode === "img2video" && referenceUrls.length === 0) {
|
if (videoNode.videoMode === "img2video" && referenceUrls.length === 0) {
|
||||||
throw new Error("图生视频需要先连接至少一个可用的图片节点");
|
throw new Error("图生视频需要先连接至少一个可用的图片节点");
|
||||||
}
|
}
|
||||||
@@ -1892,7 +1911,8 @@ function CanvasPage({
|
|||||||
? "视频生成完成"
|
? "视频生成完成"
|
||||||
: "视频生成失败";
|
: "视频生成失败";
|
||||||
setVideoGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
|
setVideoGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
|
||||||
}));
|
}, abortRef));
|
||||||
|
if (abortRef.current || !outputUrl) return;
|
||||||
setVideoGenerationStatus(nodeId, { status: "success", message: "视频生成完成", progress: 100 });
|
setVideoGenerationStatus(nodeId, { status: "success", message: "视频生成完成", progress: 100 });
|
||||||
removeCanvasGenKeepalive(taskId);
|
removeCanvasGenKeepalive(taskId);
|
||||||
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
|
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
|
||||||
@@ -1948,13 +1968,15 @@ function CanvasPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (abortRef.current) return;
|
||||||
setVideoGenerationStatus(nodeId, {
|
setVideoGenerationStatus(nodeId, {
|
||||||
status: "error",
|
status: "error",
|
||||||
message: error instanceof Error ? error.message : "视频生成失败",
|
message: error instanceof Error ? error.message : "视频生成失败",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
videoGenerationInFlightRef.current.delete(nodeId);
|
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];
|
const file = event.target.files?.[0];
|
||||||
event.target.value = "";
|
event.target.value = "";
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = trackObjectUrl(file);
|
||||||
if (pendingImageToImageNodeId) {
|
if (pendingImageToImageNodeId) {
|
||||||
const sourceNode = imageNodes.find((node) => node.id === pendingImageToImageNodeId);
|
const sourceNode = imageNodes.find((node) => node.id === pendingImageToImageNodeId);
|
||||||
if (sourceNode) {
|
if (sourceNode) {
|
||||||
@@ -2047,7 +2069,7 @@ function CanvasPage({
|
|||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = trackObjectUrl(file);
|
||||||
addImageNode(imageUrl, file.name, {
|
addImageNode(imageUrl, file.name, {
|
||||||
x: dropPosition.x + offsetX,
|
x: dropPosition.x + offsetX,
|
||||||
y: dropPosition.y + offsetY,
|
y: dropPosition.y + offsetY,
|
||||||
@@ -2103,7 +2125,7 @@ function CanvasPage({
|
|||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = trackObjectUrl(file);
|
||||||
addImageNode(imageUrl, file.name, {
|
addImageNode(imageUrl, file.name, {
|
||||||
x: sourceNode.position.x + sourceNode.size.width + 40 + offsetX,
|
x: sourceNode.position.x + sourceNode.size.width + 40 + offsetX,
|
||||||
y: sourceNode.position.y + offsetY,
|
y: sourceNode.position.y + offsetY,
|
||||||
@@ -5279,7 +5301,7 @@ function CanvasPage({
|
|||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
setAssetCoverUrl(URL.createObjectURL(file));
|
setAssetCoverUrl(trackObjectUrl(file));
|
||||||
setCoverSourceOpen(false);
|
setCoverSourceOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -252,28 +252,40 @@ export function blobToDataUrl(blob: Blob) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForImageTaskResult(taskId: string, onStatus?: (status: AiTaskStatus) => void) {
|
export async function waitForImageTaskResult(
|
||||||
|
taskId: string,
|
||||||
|
onStatus?: (status: AiTaskStatus) => void,
|
||||||
|
abortRef?: { current: boolean },
|
||||||
|
) {
|
||||||
const resultUrl = await waitForTask(taskId, {
|
const resultUrl = await waitForTask(taskId, {
|
||||||
kind: "image",
|
kind: "image",
|
||||||
|
abortRef,
|
||||||
onProgress: (e) => {
|
onProgress: (e) => {
|
||||||
if (onStatus) {
|
if (onStatus) {
|
||||||
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (abortRef?.current) return "";
|
||||||
if (!resultUrl) throw new Error("生成任务已完成,但服务器没有返回结果地址,请稍后重试");
|
if (!resultUrl) throw new Error("生成任务已完成,但服务器没有返回结果地址,请稍后重试");
|
||||||
return resultUrl;
|
return resultUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForVideoTaskResult(taskId: string, onStatus?: (status: AiTaskStatus) => void) {
|
export async function waitForVideoTaskResult(
|
||||||
|
taskId: string,
|
||||||
|
onStatus?: (status: AiTaskStatus) => void,
|
||||||
|
abortRef?: { current: boolean },
|
||||||
|
) {
|
||||||
const resultUrl = await waitForTask(taskId, {
|
const resultUrl = await waitForTask(taskId, {
|
||||||
kind: "video",
|
kind: "video",
|
||||||
|
abortRef,
|
||||||
onProgress: (e) => {
|
onProgress: (e) => {
|
||||||
if (onStatus) {
|
if (onStatus) {
|
||||||
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (abortRef?.current) return "";
|
||||||
if (!resultUrl) throw new Error("视频生成任务已完成,但服务器没有返回结果地址,请稍后重试");
|
if (!resultUrl) throw new Error("视频生成任务已完成,但服务器没有返回结果地址,请稍后重试");
|
||||||
return resultUrl;
|
return resultUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Dispatch, type SetStateAction, useEffect, useRef, useState } from "react";
|
import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import type {
|
import type {
|
||||||
CanvasImageGenerationState,
|
CanvasImageGenerationState,
|
||||||
CanvasImageNode,
|
CanvasImageNode,
|
||||||
@@ -66,6 +66,8 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
const videoGenerationInFlightRef = useRef(new Set<string>());
|
const videoGenerationInFlightRef = useRef(new Set<string>());
|
||||||
const textGenerationInFlightRef = useRef(new Set<string>());
|
const textGenerationInFlightRef = useRef(new Set<string>());
|
||||||
const textGenerationAbortControllersRef = useRef(new Map<string, AbortController>());
|
const textGenerationAbortControllersRef = useRef(new Map<string, AbortController>());
|
||||||
|
const imageGenerationAbortRef = useRef(new Map<string, { current: boolean }>());
|
||||||
|
const videoGenerationAbortRef = useRef(new Map<string, { current: boolean }>());
|
||||||
const canvasGenKeepaliveRestoredRef = useRef(false);
|
const canvasGenKeepaliveRestoredRef = useRef(false);
|
||||||
|
|
||||||
const setTextGenerationStatus = (nodeId: string, state: CanvasTextGenerationState) => {
|
const setTextGenerationStatus = (nodeId: string, state: CanvasTextGenerationState) => {
|
||||||
@@ -80,6 +82,15 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
setVideoGenerationState((current) => ({ ...current, [nodeId]: state }));
|
setVideoGenerationState((current) => ({ ...current, [nodeId]: state }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const abortAllGenerationPollers = useCallback(() => {
|
||||||
|
textGenerationAbortControllersRef.current.forEach((c) => c.abort());
|
||||||
|
textGenerationAbortControllersRef.current.clear();
|
||||||
|
imageGenerationAbortRef.current.forEach((ref) => { ref.current = true; });
|
||||||
|
imageGenerationAbortRef.current.clear();
|
||||||
|
videoGenerationAbortRef.current.forEach((ref) => { ref.current = true; });
|
||||||
|
videoGenerationAbortRef.current.clear();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Toast auto-dismiss
|
// Toast auto-dismiss
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!generationToast) return undefined;
|
if (!generationToast) return undefined;
|
||||||
@@ -103,11 +114,14 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
}
|
}
|
||||||
if (entry.nodeKind === "image") {
|
if (entry.nodeKind === "image") {
|
||||||
imageGenerationInFlightRef.current.add(entry.nodeId);
|
imageGenerationInFlightRef.current.add(entry.nodeId);
|
||||||
|
const abortRef = { current: false };
|
||||||
|
imageGenerationAbortRef.current.set(entry.nodeId, abortRef);
|
||||||
setImageGenerationStatus(entry.nodeId, { status: "running", message: "正在恢复图片生成", progress: 20 });
|
setImageGenerationStatus(entry.nodeId, { status: "running", message: "正在恢复图片生成", progress: 20 });
|
||||||
void waitForImageTaskResult(entry.taskId, (status) => {
|
void waitForImageTaskResult(entry.taskId, (status) => {
|
||||||
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
||||||
setImageGenerationStatus(entry.nodeId, { status: "running", message: "图片生成中", progress });
|
setImageGenerationStatus(entry.nodeId, { status: "running", message: "图片生成中", progress });
|
||||||
}).then(async (outputUrl) => {
|
}, abortRef).then(async (outputUrl) => {
|
||||||
|
if (abortRef.current || !outputUrl) return;
|
||||||
removeCanvasGenKeepalive(entry.taskId);
|
removeCanvasGenKeepalive(entry.taskId);
|
||||||
setImageGenerationStatus(entry.nodeId, { status: "success", message: "生成完成", progress: 100 });
|
setImageGenerationStatus(entry.nodeId, { status: "success", message: "生成完成", progress: 100 });
|
||||||
const ref = createCanvasAssetRefFromGeneratedResult({
|
const ref = createCanvasAssetRefFromGeneratedResult({
|
||||||
@@ -128,18 +142,23 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
if (abortRef.current) return;
|
||||||
removeCanvasGenKeepalive(entry.taskId);
|
removeCanvasGenKeepalive(entry.taskId);
|
||||||
setImageGenerationStatus(entry.nodeId, { status: "error", message: "图片生成失败" });
|
setImageGenerationStatus(entry.nodeId, { status: "error", message: "图片生成失败" });
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
imageGenerationInFlightRef.current.delete(entry.nodeId);
|
imageGenerationInFlightRef.current.delete(entry.nodeId);
|
||||||
|
imageGenerationAbortRef.current.delete(entry.nodeId);
|
||||||
});
|
});
|
||||||
} else if (entry.nodeKind === "video") {
|
} else if (entry.nodeKind === "video") {
|
||||||
videoGenerationInFlightRef.current.add(entry.nodeId);
|
videoGenerationInFlightRef.current.add(entry.nodeId);
|
||||||
|
const abortRef = { current: false };
|
||||||
|
videoGenerationAbortRef.current.set(entry.nodeId, abortRef);
|
||||||
setVideoGenerationStatus(entry.nodeId, { status: "running", message: "正在恢复视频生成", progress: 20 });
|
setVideoGenerationStatus(entry.nodeId, { status: "running", message: "正在恢复视频生成", progress: 20 });
|
||||||
void waitForVideoTaskResult(entry.taskId, (status) => {
|
void waitForVideoTaskResult(entry.taskId, (status) => {
|
||||||
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
||||||
setVideoGenerationStatus(entry.nodeId, { status: "running", message: "视频生成中", progress });
|
setVideoGenerationStatus(entry.nodeId, { status: "running", message: "视频生成中", progress });
|
||||||
}).then(async (outputUrl) => {
|
}, abortRef).then(async (outputUrl) => {
|
||||||
|
if (abortRef.current || !outputUrl) return;
|
||||||
removeCanvasGenKeepalive(entry.taskId);
|
removeCanvasGenKeepalive(entry.taskId);
|
||||||
setVideoGenerationStatus(entry.nodeId, { status: "success", message: "生成完成", progress: 100 });
|
setVideoGenerationStatus(entry.nodeId, { status: "success", message: "生成完成", progress: 100 });
|
||||||
const ref = createCanvasAssetRefFromGeneratedResult({
|
const ref = createCanvasAssetRefFromGeneratedResult({
|
||||||
@@ -160,18 +179,19 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
if (abortRef.current) return;
|
||||||
removeCanvasGenKeepalive(entry.taskId);
|
removeCanvasGenKeepalive(entry.taskId);
|
||||||
setVideoGenerationStatus(entry.nodeId, { status: "error", message: "视频生成失败" });
|
setVideoGenerationStatus(entry.nodeId, { status: "error", message: "视频生成失败" });
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
videoGenerationInFlightRef.current.delete(entry.nodeId);
|
videoGenerationInFlightRef.current.delete(entry.nodeId);
|
||||||
|
videoGenerationAbortRef.current.delete(entry.nodeId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetGenerationState = () => {
|
const resetGenerationState = () => {
|
||||||
textGenerationAbortControllersRef.current.forEach((c) => c.abort());
|
abortAllGenerationPollers();
|
||||||
textGenerationAbortControllersRef.current.clear();
|
|
||||||
textGenerationInFlightRef.current.clear();
|
textGenerationInFlightRef.current.clear();
|
||||||
imageGenerationInFlightRef.current.clear();
|
imageGenerationInFlightRef.current.clear();
|
||||||
videoGenerationInFlightRef.current.clear();
|
videoGenerationInFlightRef.current.clear();
|
||||||
@@ -180,11 +200,18 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
setVideoGenerationState({});
|
setVideoGenerationState({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stop all in-flight front-end polling/setState when the canvas unmounts (route change).
|
||||||
|
// Keepalive records are intentionally preserved so restoreKeepaliveTasks can resume on return.
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
abortAllGenerationPollers();
|
||||||
|
};
|
||||||
|
}, [abortAllGenerationPollers]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handlePageHide = () => {
|
const handlePageHide = () => {
|
||||||
cancelCanvasGenKeepaliveOnUnload();
|
cancelCanvasGenKeepaliveOnUnload();
|
||||||
textGenerationAbortControllersRef.current.forEach((controller) => controller.abort());
|
abortAllGenerationPollers();
|
||||||
textGenerationAbortControllersRef.current.clear();
|
|
||||||
textGenerationInFlightRef.current.clear();
|
textGenerationInFlightRef.current.clear();
|
||||||
imageGenerationInFlightRef.current.clear();
|
imageGenerationInFlightRef.current.clear();
|
||||||
videoGenerationInFlightRef.current.clear();
|
videoGenerationInFlightRef.current.clear();
|
||||||
@@ -202,7 +229,7 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
window.removeEventListener("pagehide", handlePageHide);
|
window.removeEventListener("pagehide", handlePageHide);
|
||||||
window.removeEventListener("online", handleOnline);
|
window.removeEventListener("online", handleOnline);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [abortAllGenerationPollers]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
textGenerationState,
|
textGenerationState,
|
||||||
@@ -214,6 +241,8 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
|||||||
videoGenerationInFlightRef,
|
videoGenerationInFlightRef,
|
||||||
textGenerationInFlightRef,
|
textGenerationInFlightRef,
|
||||||
textGenerationAbortControllersRef,
|
textGenerationAbortControllersRef,
|
||||||
|
imageGenerationAbortRef,
|
||||||
|
videoGenerationAbortRef,
|
||||||
canvasGenKeepaliveRestoredRef,
|
canvasGenKeepaliveRestoredRef,
|
||||||
setTextGenerationStatus,
|
setTextGenerationStatus,
|
||||||
setImageGenerationStatus,
|
setImageGenerationStatus,
|
||||||
|
|||||||
Reference in New Issue
Block a user