fix(canvas): 画布批量生成 - 修复节点丢失、资源引用及刷新状态不一致

This commit is contained in:
ddCat committed 2026-09-21 21:13:29 +08:00
1 parent dc1ad68015
commit 6f45240599
26 files changed
+533 -179

No files matched your search

@@ -2,10 +2,10 @@ import { nanoid } from "nanoid";
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
import { canGenerateImageInPlace, findAvailableGenerationGroupPosition, imageGenerationChildPosition, imageGenerationGroupSize } from "@/lib/canvas/canvas-generation-layout";
import { cancelIncompleteImageBatch, retireImageBatchChildren } from "@/lib/canvas/canvas-image-batch-retry";
import { cancelIncompleteImageBatch, hasImageBatchResult, reconcileImageBatchRoot, retireImageBatchChildren } from "@/lib/canvas/canvas-image-batch-retry";
import { buildImageGenerationNodeTitle } from "@/lib/canvas/canvas-generation-title";
import { nodeSizeFromRatio } from "@/lib/canvas/canvas-node-size";
import { canvasImageReferenceLimitError, buildImageGenerationMetadata, getGenerationCount, isGenerationCanceled, runCanvasGenerationTaskToConsumer } from "@/lib/canvas/canvas-project-generation";
import { canvasImageReferenceLimitError, buildImageGenerationMetadata, getGenerationCount, isGenerationCanceled, resetGenerationTaskMetadata, runCanvasGenerationTaskToConsumer } from "@/lib/canvas/canvas-project-generation";
import { imageGenerationReferenceConnections } from "@/lib/canvas/canvas-resource-references";
import { canvasGenerationPromptMetadata } from "@/lib/canvas/canvas-generation-submission";
import { CONTENT_MODERATION_ERROR_CODE, generationFailureMetadata, type GenerationFailureMetadata } from "@/lib/generation-error";
@@ -54,7 +54,7 @@ export async function executeImageGeneration({
const count = getGenerationCount(generationConfig.count);
const isConfigNode = sourceNode?.type === CanvasNodeType.Config;
const isImageNode = sourceNode?.type === CanvasNodeType.Image;
const isExistingImageNode = isImageNode && Boolean(sourceNode?.metadata?.content);
const isExistingImageNode = isImageNode && hasImageBatchResult(sourceNode);
const reuseSourceNode = canGenerateImageInPlace(sourceNode);
const retired = reuseSourceNode && sourceNode ? retireImageBatchChildren(sourceNode, canvasNodes, canvasConnections) : { nodes: canvasNodes, connections: canvasConnections, removedIds: [] as string[] };
const workingNodes = retired.nodes;
@@ -70,7 +70,7 @@ export async function executeImageGeneration({
const imageConfig = requestedImageSize || imageDefaults;
// auto 图生图沿用来源节点尺寸;用户明确选择比例时必须以目标比例创建节点。
const referenceNode = referenceImages.length === 1 ? canvasNodes.find((node) => node.id === referenceImages[0].id && node.type === CanvasNodeType.Image) : undefined;
const imageSizeSource = requestedImageSize ? undefined : isImageNode && sourceNode?.metadata?.content ? sourceNode : referenceNode;
const imageSizeSource = requestedImageSize ? undefined : isExistingImageNode ? sourceNode : referenceNode;
const outputNodeSize = imageSizeSource ? { width: imageSizeSource.width, height: imageSizeSource.height } : imageConfig;
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
const parentWidth = sourceNode?.width || parentConfig.width;
@@ -95,7 +95,7 @@ export async function executeImageGeneration({
width: rootWidth,
height: rootHeight,
metadata: {
...(reuseSourceNode ? sourceNode?.metadata || {} : {}),
...resetGenerationTaskMetadata(reuseSourceNode ? sourceNode?.metadata : undefined, NODE_STATUS_LOADING),
...canvasGenerationPromptMetadata(prompt, effectivePrompt),
status: NODE_STATUS_LOADING,
size: generationConfig.size,
@@ -105,6 +105,8 @@ export async function executeImageGeneration({
batchUsesReferenceImages: referenceImages.length > 0,
primaryImageId: undefined,
content: reuseSourceNode ? "" : undefined,
storageKey: undefined,
assetId: undefined,
...generationMetadata,
...styleMetadata,
...skillMetadata,
@@ -145,7 +147,7 @@ export async function executeImageGeneration({
...workingNodes.map((node) => {
if (node.id !== nodeId) return node;
if (isConfigNode) return { ...node, metadata: { ...node.metadata, ...canvasGenerationPromptMetadata(prompt, effectivePrompt), status: NODE_STATUS_LOADING, errorDetails: undefined } };
if (reuseSourceNode) return { ...node, position: rootNode.position, width: rootNode.width, height: rootNode.height, title: rootNode.title, metadata: { ...node.metadata, ...rootNode.metadata, errorDetails: undefined } };
if (reuseSourceNode) return { ...node, position: rootNode.position, width: rootNode.width, height: rootNode.height, title: rootNode.title, metadata: rootNode.metadata };
if (isImageNode) return node;
return {
...node,
@@ -211,7 +213,7 @@ export async function executeImageGeneration({
setNodes((current) => {
const child = current.find((node) => node.id === targetId);
const root = current.find((node) => node.id === rootId);
if (!child?.metadata?.content || !root || root.metadata?.primaryImageId) return current;
if (!child || !hasImageBatchResult(child) || !root || root.metadata?.primaryImageId) return current;
const center = { x: root.position.x + root.width / 2, y: root.position.y + root.height / 2 };
const geometry = root.metadata?.locked
? {}
@@ -290,6 +292,7 @@ export async function executeImageGeneration({
};
}
if (node.id === rootId) {
if (count > 1) return reconcileImageBatchRoot(node, current);
return {
...node,
metadata: {