From 535e696a53681dedef8b8f3c245da2cb595b66f9 Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Wed, 26 Aug 2026 14:32:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(canvas):=20=E5=9B=BE=E7=89=87=E4=BA=BA?= =?UTF-8?q?=E7=89=A9=E8=B4=A8=E6=84=9F=20-=20=E7=82=B9=E5=87=BB=E5=85=88?= =?UTF-8?q?=E6=89=93=E5=BC=80=E5=8F=82=E6=95=B0=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canvas/canvas-node-generation.ts | 8 ++- web/src/pages/canvas/project.tsx | 4 +- .../pages/canvas/use-canvas-media-tools.ts | 49 +++---------------- 3 files changed, 17 insertions(+), 44 deletions(-) diff --git a/web/src/components/canvas/canvas-node-generation.ts b/web/src/components/canvas/canvas-node-generation.ts index eaf1ee1bf..592ab070c 100644 --- a/web/src/components/canvas/canvas-node-generation.ts +++ b/web/src/components/canvas/canvas-node-generation.ts @@ -61,8 +61,14 @@ export type NodeGenerationInput = { export function buildNodeGenerationContext(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[], prompt: string, assets: Asset[], promptOnly = false): NodeGenerationContext { const connectedInputs = buildNodeGenerationInputs(nodeId, nodes, connections); - const inputs = [...connectedInputs, ...buildAssetGenerationInputs(assets)]; const sourceNode = nodes.find((node) => node.id === nodeId); + const portraitTextureInput = sourceNode?.type === CanvasNodeType.Image && sourceNode.metadata?.content && sourceNode.metadata?.portraitTexture + ? (() => { + const image = readReferenceImage(sourceNode, nodes, connections); + return image ? [{ nodeId: sourceNode.id, type: "image" as const, title: sourceNode.title, image }] : []; + })() + : []; + const inputs = [...connectedInputs, ...portraitTextureInput, ...buildAssetGenerationInputs(assets)]; const storyboardInputs = getConnectedStoryboardRows(nodeId, nodes, connections); const hasExplicitResourceMention = /@\[(?:node|asset):[^\]]+\]/.test(normalizeLegacyNodeMentions(prompt, inputs)); if ((sourceNode?.type === CanvasNodeType.Config && Boolean(sourceNode.metadata?.composerContent?.trim())) || hasExplicitResourceMention) { diff --git a/web/src/pages/canvas/project.tsx b/web/src/pages/canvas/project.tsx index 6bfa36ad6..2a8adfb4b 100644 --- a/web/src/pages/canvas/project.tsx +++ b/web/src/pages/canvas/project.tsx @@ -734,7 +734,7 @@ function InfiniteCanvasPage() { emotionNodeId, annotationNodeId, createImageReversePromptNodes, - generatePortraitTextureNode, + openPortraitTextureEditor, cropImageNode, cropNodeId, closeSegmentDialog, @@ -2233,7 +2233,7 @@ function InfiniteCanvasPage() { setDialogNodeId(null); setEmotionNodeId((current) => (current === node.id ? null : node.id)); }} - onPortraitTexture={generatePortraitTextureNode} + onPortraitTexture={openPortraitTextureEditor} onCrop={(node) => setCropNodeId(node.id)} onSplit={(node) => setSplitNodeId(node.id)} onUpscale={(node) => setUpscaleNodeId(node.id)} diff --git a/web/src/pages/canvas/use-canvas-media-tools.ts b/web/src/pages/canvas/use-canvas-media-tools.ts index e66cfe3d5..f399527d9 100644 --- a/web/src/pages/canvas/use-canvas-media-tools.ts +++ b/web/src/pages/canvas/use-canvas-media-tools.ts @@ -24,7 +24,7 @@ import { } from "@/lib/canvas/canvas-project-generation"; import { fitNodeSize, VIDEO_NODE_MAX_SIZE } from "@/lib/canvas/canvas-node-size"; import { compositeEmotionImage, emotionGenerationSize, emotionProviderMask, normalizeEmotionPromptForProvider, resolveEmotionEditPlan } from "@/lib/canvas/canvas-emotion"; -import { DEFAULT_PORTRAIT_TEXTURE_SETTINGS, buildPortraitTexturePrompt } from "@/lib/canvas/canvas-portrait-texture"; +import { DEFAULT_PORTRAIT_TEXTURE_SETTINGS } from "@/lib/canvas/canvas-portrait-texture"; import { captureVideoLastFrame } from "@/lib/canvas/canvas-video-frame"; import { mergeVideos, type MergeVideoProgress } from "@/lib/canvas/canvas-video-merge"; import { extractVideoAudio, trimVideoSegment } from "@/lib/canvas/canvas-video-segment"; @@ -157,53 +157,20 @@ export function useCanvasMediaTools({ setContextMenu(null); }, [effectiveConfig.model, effectiveConfig.textModel, message, setConnections, setContextMenu, setDialogNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds]); - const generatePortraitTextureNode = useCallback(async (node: CanvasNodeData) => { + const openPortraitTextureEditor = useCallback((node: CanvasNodeData) => { if (node.type !== CanvasNodeType.Image || !node.metadata?.content) { message.warning("图片节点为空,无法调节人物质感"); return; } - const generationConfig = { ...buildGenerationConfig(effectiveConfig, node, "image"), count: "1" }; - if (!isAiConfigReady(generationConfig, generationConfig.model)) { - navigateToSettings({ continueCreation: true }); - return; - } - const childId = nanoid(); - const imageSpec = NODE_DEFAULT_SIZE[CanvasNodeType.Image]; - const composerContent = `@[node:${node.id}]`; - const source = nodeReferenceImage(node); - if (!source) return; - const prompt = buildPortraitTexturePrompt(composerContent, { ...DEFAULT_PORTRAIT_TEXTURE_SETTINGS, ...node.metadata?.portraitTexture }); - const styleExecution = resolveImageEditStyle(node, prompt, generationConfig); - if (!styleExecution) return; - const { prompt: effectivePrompt, metadata: styleMetadata } = styleExecution; - const generationMetadata = buildImageGenerationMetadata("edit", generationConfig, 1, [source]); const portraitTextureSettings = { ...DEFAULT_PORTRAIT_TEXTURE_SETTINGS, ...node.metadata?.portraitTexture }; + const composerContent = node.metadata?.composerContent?.trim() || node.metadata?.prompt?.trim() || `@[node:${node.id}]`; setHoveredNodeId(null); setToolbarNodeId(null); - setRunningNodeId(childId); - setNodes((current) => [...current, { id: childId, type: CanvasNodeType.Image, title: "人物质感调节", position: { x: node.position.x + node.width + 96 + imageSpec.width / 2, y: node.position.y + node.height / 2 }, width: imageSpec.width, height: imageSpec.height, metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, composerContent, portraitTexture: portraitTextureSettings, ...generationMetadata, ...styleMetadata } }]); - setConnections((current) => [...current, { id: nanoid(), fromNodeId: node.id, toNodeId: childId }]); - setSelectedNodeIds(new Set([childId])); + setNodes((current) => current.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, prompt: composerContent, composerContent, portraitTexture: portraitTextureSettings } } : item)); + setSelectedNodeIds(new Set([node.id])); setSelectedConnectionId(null); - setDialogNodeId(childId); - const controller = startGenerationRequest(childId, node.id, childId); - try { - const result = await runBackendCanvasGenerationTask({ projectId, nodeId: childId, mode: "image", prompt: effectivePrompt, config: generationConfig, referenceImages: [source], signal: controller.signal, metadata: { sourceNodeId: node.id, edit: "portraitTexture", portraitTexture: portraitTextureSettings, ...styleMetadata }, onTaskCreated: (task) => bindGenerationTask(childId, task) }); - const image = result.images?.[0]; - if (!image?.dataUrl) throw new Error("后端任务没有返回图片"); - const uploaded = await uploadImage(image.dataUrl); - const size = fitNodeSize(uploaded.width, uploaded.height, imageSpec.width, imageSpec.height); - setNodes((current) => current.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...item.metadata, ...imageMetadata(uploaded), prompt: effectivePrompt, ...generationMetadata, portraitTexture: portraitTextureSettings } } : item)); - } catch (error) { - if (isGenerationCanceled(error)) return; - const details = generationErrorMessage(error); - message.error(details); - setNodes((current) => current.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails: details } } : item)); - } finally { - finishGenerationRequest(childId, controller); - setRunningNodeId(null); - } - }, [bindGenerationTask, effectiveConfig, finishGenerationRequest, isAiConfigReady, message, projectId, resolveImageEditStyle, setConnections, setDialogNodeId, setNodes, setRunningNodeId, setSelectedConnectionId, setSelectedNodeIds, startGenerationRequest]); + setDialogNodeId(node.id); + }, [message, setDialogNodeId, setHoveredNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, setToolbarNodeId]); const cropImageNode = useCallback(async (node: CanvasNodeData, crop: CanvasImageCropRect) => { if (!node.metadata?.content) return; @@ -713,7 +680,7 @@ export function useCanvasMediaTools({ emotionNodeId, annotationNodeId, createImageReversePromptNodes, - generatePortraitTextureNode, + openPortraitTextureEditor, cropImageNode, cropNodeId, closeSegmentDialog,