fix: harden generation task recovery
This commit is contained in:
@@ -396,7 +396,6 @@ function CanvasPage({
|
||||
const canvasUploadInputRef = useRef<HTMLInputElement>(null);
|
||||
const imageNodeInputRef = useRef<HTMLInputElement>(null);
|
||||
const canvasRef = useRef<HTMLElement>(null);
|
||||
const videoGenerationInFlightRef = useRef(new Set<string>());
|
||||
const canvasReferenceUploadPromisesRef = useRef(new Map<string, Promise<string | null>>());
|
||||
const canvasDragCounterRef = useRef(0);
|
||||
const [isCanvasDragging, setIsCanvasDragging] = useState(false);
|
||||
@@ -417,7 +416,7 @@ function CanvasPage({
|
||||
const {
|
||||
textGenerationState, imageGenerationState, videoGenerationState,
|
||||
generationToast, setGenerationToast,
|
||||
imageGenerationInFlightRef, textGenerationInFlightRef, textGenerationAbortControllersRef,
|
||||
imageGenerationInFlightRef, videoGenerationInFlightRef, textGenerationInFlightRef, textGenerationAbortControllersRef,
|
||||
canvasGenKeepaliveRestoredRef,
|
||||
setTextGenerationStatus, setImageGenerationStatus, setVideoGenerationStatus,
|
||||
restoreKeepaliveTasks, resetGenerationState,
|
||||
@@ -1887,13 +1886,14 @@ function CanvasPage({
|
||||
setVideoGenerationStatus(nodeId, { status: "submitting", message: "正在提交视频生成", progress: 8 });
|
||||
setGenerationToast("视频正在生成");
|
||||
|
||||
let task: Awaited<ReturnType<typeof onCreateTask>> | null = null;
|
||||
try {
|
||||
const referenceUrls = await resolveConnectedImageReferenceUrls("video", nodeId);
|
||||
if (videoNode.videoMode === "img2video" && referenceUrls.length === 0) {
|
||||
throw new Error("图生视频需要先连接至少一个可用的图片节点");
|
||||
}
|
||||
let requestModel = resolveVideoRequestModel({ model, referenceUrls });
|
||||
const task = await onCreateTask({
|
||||
task = await onCreateTask({
|
||||
title: videoNode.title || "视频节点生成",
|
||||
type: "video",
|
||||
prompt: prompt || "根据参考图片生成视频",
|
||||
@@ -1916,10 +1916,12 @@ function CanvasPage({
|
||||
if (task.status === "completed" && !task.outputUrl) {
|
||||
throw new Error("视频生成任务已完成,但服务器没有返回结果地址,请稍后重试");
|
||||
}
|
||||
const taskId = task.id;
|
||||
addCanvasGenKeepalive(taskId, nodeId, "video", projectId || "");
|
||||
setVideoGenerationStatus(nodeId, { status: "running", message: "视频生成中", progress: Math.max(18, Number(task.progress || 0)) });
|
||||
const outputUrl =
|
||||
task.outputUrl ||
|
||||
(await waitForImageTaskResult(task.id, (status) => {
|
||||
(await waitForVideoTaskResult(taskId, (status) => {
|
||||
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
||||
const statusLabel =
|
||||
status.status === "pending"
|
||||
@@ -1932,11 +1934,12 @@ function CanvasPage({
|
||||
setVideoGenerationStatus(nodeId, { status: "running", message: statusLabel, progress });
|
||||
}));
|
||||
setVideoGenerationStatus(nodeId, { status: "success", message: "视频生成完成", progress: 100 });
|
||||
removeCanvasGenKeepalive(taskId);
|
||||
const immediateAssetRef = createCanvasAssetRefFromGeneratedResult({
|
||||
url: outputUrl,
|
||||
mediaType: "video/mp4",
|
||||
resultType: "video",
|
||||
taskId: task.id,
|
||||
taskId,
|
||||
originalUrl: outputUrl,
|
||||
});
|
||||
setVideoNodes((currentNodes) =>
|
||||
@@ -1947,7 +1950,7 @@ function CanvasPage({
|
||||
videoUrl: outputUrl,
|
||||
assetRef: immediateAssetRef,
|
||||
taskRef: {
|
||||
taskId: task.id,
|
||||
taskId,
|
||||
status: "completed",
|
||||
resultUrl: outputUrl,
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -1961,7 +1964,7 @@ function CanvasPage({
|
||||
url: outputUrl,
|
||||
mediaType: "video/mp4",
|
||||
resultType: "video",
|
||||
taskId: task.id,
|
||||
taskId,
|
||||
originalUrl: outputUrl,
|
||||
});
|
||||
await delay(420);
|
||||
@@ -1974,7 +1977,7 @@ function CanvasPage({
|
||||
videoUrl: assetRef.url,
|
||||
assetRef,
|
||||
taskRef: {
|
||||
taskId: task.id,
|
||||
taskId,
|
||||
status: "completed",
|
||||
resultUrl: assetRef.url,
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -1991,6 +1994,7 @@ function CanvasPage({
|
||||
});
|
||||
} finally {
|
||||
videoGenerationInFlightRef.current.delete(nodeId);
|
||||
if (task?.id) removeCanvasGenKeepalive(task.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
CanvasVideoGenerationState,
|
||||
CanvasVideoNode,
|
||||
} from "./canvasTypes";
|
||||
import { aiGenerationClient } from "../../api/aiGenerationClient";
|
||||
import { createCanvasAssetRefFromGeneratedResult, persistCanvasGeneratedResultAsset } from "./canvasAssetPersistence";
|
||||
import { waitForImageTaskResult, waitForVideoTaskResult } from "./canvasUtils";
|
||||
|
||||
@@ -41,6 +42,13 @@ export function removeCanvasGenKeepalive(taskId: string): void {
|
||||
saveCanvasGenKeepalive(loadCanvasGenKeepalive().filter((e) => e.taskId !== taskId));
|
||||
}
|
||||
|
||||
export function cancelCanvasGenKeepaliveOnUnload(): void {
|
||||
const entries = loadCanvasGenKeepalive();
|
||||
if (!entries.length) return;
|
||||
entries.forEach((entry) => aiGenerationClient.cancelTaskOnUnload(entry.taskId));
|
||||
saveCanvasGenKeepalive([]);
|
||||
}
|
||||
|
||||
export interface UseCanvasGenerationParams {
|
||||
setImageNodes: Dispatch<SetStateAction<CanvasImageNode[]>>;
|
||||
setVideoNodes: Dispatch<SetStateAction<CanvasVideoNode[]>>;
|
||||
@@ -55,6 +63,7 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
||||
const [generationToast, setGenerationToast] = useState<string | null>(null);
|
||||
|
||||
const imageGenerationInFlightRef = useRef(new Set<string>());
|
||||
const videoGenerationInFlightRef = useRef(new Set<string>());
|
||||
const textGenerationInFlightRef = useRef(new Set<string>());
|
||||
const textGenerationAbortControllersRef = useRef(new Map<string, AbortController>());
|
||||
const canvasGenKeepaliveRestoredRef = useRef(false);
|
||||
@@ -125,7 +134,7 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
||||
imageGenerationInFlightRef.current.delete(entry.nodeId);
|
||||
});
|
||||
} else if (entry.nodeKind === "video") {
|
||||
imageGenerationInFlightRef.current.add(entry.nodeId);
|
||||
videoGenerationInFlightRef.current.add(entry.nodeId);
|
||||
setVideoGenerationStatus(entry.nodeId, { status: "running", message: "正在恢复视频生成", progress: 20 });
|
||||
void waitForVideoTaskResult(entry.taskId, (status) => {
|
||||
const progress = Math.max(18, Math.min(status.status === "completed" ? 100 : 96, Math.trunc(status.progress || 0)));
|
||||
@@ -154,7 +163,7 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
||||
removeCanvasGenKeepalive(entry.taskId);
|
||||
setVideoGenerationStatus(entry.nodeId, { status: "error", message: "视频生成失败" });
|
||||
}).finally(() => {
|
||||
imageGenerationInFlightRef.current.delete(entry.nodeId);
|
||||
videoGenerationInFlightRef.current.delete(entry.nodeId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -165,11 +174,36 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
||||
textGenerationAbortControllersRef.current.clear();
|
||||
textGenerationInFlightRef.current.clear();
|
||||
imageGenerationInFlightRef.current.clear();
|
||||
videoGenerationInFlightRef.current.clear();
|
||||
setTextGenerationState({});
|
||||
setImageGenerationState({});
|
||||
setVideoGenerationState({});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handlePageHide = () => {
|
||||
cancelCanvasGenKeepaliveOnUnload();
|
||||
textGenerationAbortControllersRef.current.forEach((controller) => controller.abort());
|
||||
textGenerationAbortControllersRef.current.clear();
|
||||
textGenerationInFlightRef.current.clear();
|
||||
imageGenerationInFlightRef.current.clear();
|
||||
videoGenerationInFlightRef.current.clear();
|
||||
setTextGenerationState({});
|
||||
setImageGenerationState({});
|
||||
setVideoGenerationState({});
|
||||
};
|
||||
const handleOnline = () => {
|
||||
aiGenerationClient.flushPendingTaskCancellations();
|
||||
};
|
||||
window.addEventListener("pagehide", handlePageHide);
|
||||
window.addEventListener("online", handleOnline);
|
||||
aiGenerationClient.flushPendingTaskCancellations();
|
||||
return () => {
|
||||
window.removeEventListener("pagehide", handlePageHide);
|
||||
window.removeEventListener("online", handleOnline);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
textGenerationState,
|
||||
imageGenerationState,
|
||||
@@ -177,6 +211,7 @@ export function useCanvasGeneration(params: UseCanvasGenerationParams) {
|
||||
generationToast,
|
||||
setGenerationToast,
|
||||
imageGenerationInFlightRef,
|
||||
videoGenerationInFlightRef,
|
||||
textGenerationInFlightRef,
|
||||
textGenerationAbortControllersRef,
|
||||
canvasGenKeepaliveRestoredRef,
|
||||
|
||||
Reference in New Issue
Block a user