feat(canvas): Agent引用与局部重绘 - 完善附件预览多图展开并修复取消清理
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
897981b893
commit
6c1d17dbf6
26 files changed
+999
-236
No files matched your search
@@ -2,9 +2,11 @@ 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 { 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 { 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";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
@@ -21,6 +23,7 @@ export async function executeImageGeneration({
|
||||
nodeId,
|
||||
sourceNode,
|
||||
canvasNodes,
|
||||
canvasConnections,
|
||||
prompt,
|
||||
effectivePrompt,
|
||||
generationConfig,
|
||||
@@ -52,6 +55,9 @@ export async function executeImageGeneration({
|
||||
const isConfigNode = sourceNode?.type === CanvasNodeType.Config;
|
||||
const isImageNode = sourceNode?.type === CanvasNodeType.Image;
|
||||
const reuseSourceNode = canGenerateImageInPlace(sourceNode);
|
||||
const retired = reuseSourceNode && sourceNode ? retireImageBatchChildren(sourceNode, canvasNodes, canvasConnections) : { nodes: canvasNodes, connections: canvasConnections, removedIds: [] as string[] };
|
||||
const workingNodes = retired.nodes;
|
||||
const workingConnections = retired.connections;
|
||||
// 已有图片生成新结果并保留旧版本;参考图只来自入边,避免把旧结果误当成自身输入。
|
||||
const referenceImages = generationContext.referenceImages;
|
||||
const generationType = referenceImages.length ? ("edit" as const) : ("generation" as const);
|
||||
@@ -78,7 +84,7 @@ export async function executeImageGeneration({
|
||||
x: parentPosition.x + parentWidth + 96,
|
||||
y: parentPosition.y + parentHeight / 2 - rootHeight / 2,
|
||||
};
|
||||
const rootPosition = reuseSourceNode ? parentPosition : findAvailableGenerationGroupPosition(canvasNodes, preferredPosition, imageGenerationGroupSize({ width: rootWidth, height: rootHeight }, outputNodeSize, childIds.length));
|
||||
const rootPosition = reuseSourceNode ? parentPosition : findAvailableGenerationGroupPosition(workingNodes, preferredPosition, imageGenerationGroupSize({ width: rootWidth, height: rootHeight }, outputNodeSize, childIds.length));
|
||||
|
||||
const rootNode: CanvasNodeData = {
|
||||
id: rootId,
|
||||
@@ -101,7 +107,7 @@ export async function executeImageGeneration({
|
||||
...generationMetadata,
|
||||
...styleMetadata,
|
||||
...skillMetadata,
|
||||
imageBatchExpanded: count > 1 ? false : undefined,
|
||||
imageBatchExpanded: count > 1 ? true : undefined,
|
||||
generationErrorCode: undefined,
|
||||
resourceReloadAvailable: undefined,
|
||||
failedPromptFingerprint: undefined,
|
||||
@@ -127,10 +133,14 @@ export async function executeImageGeneration({
|
||||
failedPromptFingerprint: undefined,
|
||||
},
|
||||
}));
|
||||
const batchConnections = [...(reuseSourceNode ? [] : [{ id: nanoid(), fromNodeId: nodeId, toNodeId: rootId }]), ...childIds.map((childId) => ({ id: nanoid(), fromNodeId: rootId, toNodeId: childId }))];
|
||||
const batchConnections = [
|
||||
...(reuseSourceNode ? [] : imageGenerationReferenceConnections(nodeId, rootId, workingNodes, workingConnections, nanoid)),
|
||||
...(reuseSourceNode ? [] : [{ id: nanoid(), fromNodeId: nodeId, toNodeId: rootId }]),
|
||||
...childIds.map((childId) => ({ id: nanoid(), fromNodeId: rootId, toNodeId: childId })),
|
||||
];
|
||||
|
||||
const nextNodes: CanvasNodeData[] = [
|
||||
...canvasNodes.map((node) => {
|
||||
...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 } };
|
||||
@@ -150,7 +160,8 @@ export async function executeImageGeneration({
|
||||
|
||||
setNodes(nextNodes);
|
||||
setConnections((current) => {
|
||||
const nextConnections = [...current, ...batchConnections];
|
||||
const removed = new Set(retired.removedIds);
|
||||
const nextConnections = [...current.filter((connection) => !removed.has(connection.fromNodeId) && !removed.has(connection.toNodeId)), ...batchConnections];
|
||||
if (projectId) {
|
||||
useCanvasStore.getState().updateProject(projectId, { nodes: nextNodes, connections: nextConnections });
|
||||
}
|
||||
@@ -253,7 +264,14 @@ export async function executeImageGeneration({
|
||||
);
|
||||
if (count > 1) finishGenerationRequest(rootId, controller);
|
||||
if (controller.signal.aborted) {
|
||||
setNodes((current) => current.map((node) => (node.id === nodeId && isConfigNode && node.metadata?.status === NODE_STATUS_LOADING ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_IDLE, errorDetails: undefined } } : node)));
|
||||
setNodes((current) => {
|
||||
const cancelled = cancelIncompleteImageBatch(rootId, childIds, current, []);
|
||||
if (cancelled.removedIds.length) {
|
||||
const removed = new Set(cancelled.removedIds);
|
||||
setConnections((connections) => connections.filter((connection) => !removed.has(connection.fromNodeId) && !removed.has(connection.toNodeId)));
|
||||
}
|
||||
return cancelled.nodes.map((node) => (node.id === nodeId && isConfigNode && node.metadata?.status === NODE_STATUS_LOADING ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_IDLE, errorDetails: undefined } } : node));
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (hasFailure) showError(hasSuccess ? "部分图片生成失败" : "全部图片生成失败");
|
||||
|
||||
Reference in new issue
Block a user