fix(canvas): 画布资源恢复 - 生成结果下载或 OSS 落盘失败支持原任务重新加载

This commit is contained in:
ddCat committed 2026-08-25 15:06:45 +08:00
1 parent fc546c7989
commit 9d848b9daa
15 files changed
+116 -35

No files matched your search

@@ -97,6 +97,7 @@ export async function executeImageGeneration({
...styleMetadata,
imageBatchExpanded: count > 1 ? true : undefined,
generationErrorCode: undefined,
resourceReloadAvailable: undefined,
failedPromptFingerprint: undefined,
},
};
@@ -115,6 +116,7 @@ export async function executeImageGeneration({
...generationMetadata,
...styleMetadata,
generationErrorCode: undefined,
resourceReloadAvailable: undefined,
failedPromptFingerprint: undefined,
},
}));
@@ -51,6 +51,7 @@ export async function executeVideoGeneration({
status: NODE_STATUS_LOADING,
errorDetails: undefined,
generationErrorCode: undefined,
resourceReloadAvailable: undefined,
failedPromptFingerprint: undefined,
model: generationConfig.model,
size: generationConfig.size,
@@ -64,6 +64,7 @@ type CanvasProjectWorldLayersProps = {
onToggleBatch: (nodeId: string) => void;
onSetBatchPrimary: (node: CanvasNodeData) => void;
onRetry: (node: CanvasNodeData) => void;
onReloadResource: (node: CanvasNodeData) => void;
onOpenTaskDetails: (node: CanvasNodeData) => void;
onOpenVersions: (node: CanvasNodeData) => void;
onViewImage: (node: CanvasNodeData) => void;
@@ -161,6 +162,7 @@ export const CanvasProjectWorldLayers = memo(function CanvasProjectWorldLayers(p
onToggleBatch={props.onToggleBatch}
onSetBatchPrimary={props.onSetBatchPrimary}
onRetry={props.onRetry}
onReloadResource={props.onReloadResource}
onOpenTaskDetails={props.onOpenTaskDetails}
onOpenVersions={props.onOpenVersions}
onViewImage={props.onViewImage}
@@ -85,6 +85,6 @@ export async function executeTextGeneration({
if (controller.signal.aborted) return;
void answers;
if (isConfigNode) {
setNodes((current) => current.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS, errorDetails: undefined, generationErrorCode: undefined, failedPromptFingerprint: undefined } } : node)));
setNodes((current) => current.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS, errorDetails: undefined, generationErrorCode: undefined, resourceReloadAvailable: undefined, failedPromptFingerprint: undefined } } : node)));
}
}
+17
View File
@@ -88,6 +88,7 @@ import { CanvasProjectWorldLayers } from "./canvas-project-world-layers";
import { CanvasNodeActionContext } from "@/components/canvas/canvas-node-action-context";
import { CanvasNodeGraphContext, type CanvasNodeGraphContextValue } from "@/components/canvas/canvas-node-graph-context";
import { CanvasRefreshShell } from "./canvas-refresh-shell";
import { queryGenerationTask } from "@/services/api/task-center";
import type { CanvasImageEmotionPayload } from "@/components/canvas/canvas-node-emotion-panel";
import { CanvasEmotionWorkspace } from "@/components/canvas/canvas-emotion-workspace";
import { removeCanvasDrawing } from "@/lib/canvas/canvas-drawing-storage";
@@ -1515,6 +1516,21 @@ function InfiniteCanvasPage() {
bindGenerationTask,
applyGenerationTaskResult,
});
const reloadCanvasNodeResource = useCallback(
async (node: CanvasNodeData) => {
const taskId = node.metadata?.taskId;
if (!taskId || !node.metadata?.resourceReloadAvailable) return;
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, metadata: { ...item.metadata, status: "loading", taskStage: "正在重新加载资源", errorDetails: undefined } } : item)));
try {
const task = await queryGenerationTask(taskId);
if (task.status !== "succeeded") throw new Error("原生成任务尚未成功,无法重新加载资源");
await applyGenerationTaskResult(node.id, task);
} catch (error) {
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, metadata: { ...item.metadata, status: "error", errorDetails: error instanceof Error ? error.message : "资源重新加载失败", resourceReloadAvailable: true } } : item)));
}
},
[applyGenerationTaskResult, setNodes],
);
const reconcileImageBatchRootNode = useCallback((rootId: string) => {
setNodes((current) => {
const root = current.find((item) => item.id === rootId);
@@ -2002,6 +2018,7 @@ function InfiniteCanvasPage() {
onToggleBatch={toggleBatchExpanded}
onSetBatchPrimary={setBatchPrimary}
onRetry={retryCanvasNode}
onReloadResource={reloadCanvasNodeResource}
onOpenTaskDetails={openCanvasNodeTaskDetails}
onOpenVersions={openCanvasNodeVersions}
onViewImage={viewCanvasNodeImage}
@@ -124,6 +124,7 @@ export function useCanvasGenerationExecutor({
taskCreatedAt: new Date().toISOString(),
errorDetails: undefined,
generationErrorCode: undefined,
resourceReloadAvailable: undefined,
failedPromptFingerprint: undefined,
},
}
@@ -235,7 +236,7 @@ export function useCanvasGenerationExecutor({
if (markSourceStatus)
setNodes((current) =>
current.map((node) =>
node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt: statusPrompt, status: NODE_STATUS_LOADING, errorDetails: undefined, generationErrorCode: undefined, failedPromptFingerprint: undefined } } : node,
node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt: statusPrompt, status: NODE_STATUS_LOADING, errorDetails: undefined, generationErrorCode: undefined, resourceReloadAvailable: undefined, failedPromptFingerprint: undefined } } : node,
),
);
@@ -201,7 +201,7 @@ export function useCanvasGenerationRetry({
: undefined;
setRunningNodeId(node.id);
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_LOADING, errorDetails: undefined, generationErrorCode: undefined, failedPromptFingerprint: undefined } } : item)));
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_LOADING, errorDetails: undefined, generationErrorCode: undefined, resourceReloadAvailable: undefined, failedPromptFingerprint: undefined } } : item)));
const controller = startGenerationRequest(node.id, sourceNode.id, node.id);
const retryContext = node.metadata?.taskId ? await createGenerationRetryContext(node.metadata.taskId, node.metadata.attemptGroupId) : {};
const runAndConsumeRetry = async (input: Parameters<typeof runBackendCanvasGenerationTask>[0]) => {
+33 -25
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateA
import { useQueryClient } from "@tanstack/react-query";
import { App } from "antd";
import { applyGenerationTaskResultToNodes, generationTaskNodeId } from "@/lib/canvas/canvas-generation-task-sync";
import { applyGenerationTaskResultToNodes, generationTaskCanReloadResource, generationTaskNodeId } from "@/lib/canvas/canvas-generation-task-sync";
import { applyCanvasGenerationTaskNodeEffect, isCanvasGenerationDurableAckError } from "@/services/canvas-generation-consumer";
import { consumeGenerationTaskNode, ensureCanvasNodeAsset } from "@/services/project-asset-sync";
import { listGenerationTasks, listTaskLogs, queryGenerationTask, subscribeGenerationTasks, type GenerationTask, type TaskLog } from "@/services/api/task-center";
@@ -120,6 +120,7 @@ export async function recoverCanvasGenerationTaskNode(input: {
status: NODE_STATUS_SUCCESS,
errorDetails: undefined,
generationErrorCode: undefined,
resourceReloadAvailable: undefined,
failedPromptFingerprint: undefined,
storyboard: { rows: result.rows, visibleColumns: cinematicStoryboardColumns(item.metadata?.storyboard?.visibleColumns), referenceNodeIds: item.metadata?.storyboard?.referenceNodeIds || [] },
},
@@ -261,7 +262,7 @@ export function useCanvasGeneration({ projectId, domainProjectId, projectLoaded,
...node.metadata,
...generationTaskMetadata(task),
status: failed ? NODE_STATUS_ERROR : hasCompletedContent ? NODE_STATUS_SUCCESS : NODE_STATUS_LOADING,
...(failure || { errorDetails: undefined, generationErrorCode: undefined, failedPromptFingerprint: undefined }),
...(failure || { errorDetails: undefined, generationErrorCode: undefined, resourceReloadAvailable: undefined, failedPromptFingerprint: undefined }),
},
};
}),
@@ -281,30 +282,37 @@ export function useCanvasGeneration({ projectId, domainProjectId, projectLoaded,
const applyGenerationTaskResult = useCallback(
async (nodeId: string, task: GenerationTask) => {
if (!task.outputs?.length && task.type === "canvas_text") {
const applied = await applyGenerationTaskResultToNodes(nodesRef.current, task, nodeId);
if (!applied.updated || !applied.node) throw new Error("画布中找不到对应任务节点");
setNodes((current) => current.map((node) => (node.id === applied.nodeId ? applied.node! : node)));
return;
try {
if (!task.outputs?.length && task.type === "canvas_text") {
const applied = await applyGenerationTaskResultToNodes(nodesRef.current, task, nodeId);
if (!applied.updated || !applied.node) throw new Error("画布中找不到对应任务节点");
setNodes((current) => current.map((node) => (node.id === applied.nodeId ? applied.node! : node)));
return;
}
await consumeGenerationTaskNode(
task,
nodeId,
0,
async ({ task: materialized, output, effectKey, signal }) => {
await applyCanvasGenerationTaskNodeEffect({
projectId,
nodeId,
task: materialized,
output,
effectKey,
signal,
nodesRef,
setNodes,
});
},
{ signal: consumerControllerRef.current.signal },
);
} catch (error) {
if (generationTaskCanReloadResource(task)) {
setNodes((current) => current.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, resourceReloadAvailable: true } } : node)));
}
throw error;
}
await consumeGenerationTaskNode(
task,
nodeId,
0,
async ({ task: materialized, output, effectKey, signal }) => {
await applyCanvasGenerationTaskNodeEffect({
projectId,
nodeId,
task: materialized,
output,
effectKey,
signal,
nodesRef,
setNodes,
});
},
{ signal: consumerControllerRef.current.signal },
);
},
[nodesRef, projectId, setNodes],
);