feat(canvas): enhance configuration node with new input handling and reference selection features

This commit is contained in:
HouYunFei
2026-06-04 17:03:48 +08:00
parent 75d5af5d8c
commit 96cca4d97c
10 changed files with 492 additions and 347 deletions
@@ -24,6 +24,7 @@ import { fitNodeSize, nodeSizeFromRatio } from "../utils/canvas-node-size";
import { App, Button, Dropdown, Modal } from "antd";
import { NODE_DEFAULT_SIZE, getNodeSpec } from "../constants";
import { ActiveConnectionPath, ConnectionPath } from "../components/canvas-connections";
import { CanvasConfigComposer } from "../components/canvas-config-composer";
import { CanvasConfigNodePanel } from "../components/canvas-config-node-panel";
import { CanvasAssistantPanel } from "../components/canvas-assistant-panel";
import { CanvasNodeContextMenu } from "../components/canvas-context-menu";
@@ -41,7 +42,7 @@ import { CanvasToolbar } from "../components/canvas-toolbar";
import { AssetPickerModal, type AssetPickerTab, type InsertAssetPayload } from "../components/asset-picker-modal";
import { CanvasZoomControls } from "../components/canvas-zoom-controls";
import { useCanvasStore } from "../stores/use-canvas-store";
import { buildCanvasResourceReferences, buildInputMentionReferences, buildNodeMentionReferences } from "../utils/canvas-resource-references";
import { buildCanvasResourceReferences, buildNodeMentionReferences } from "../utils/canvas-resource-references";
import {
CanvasNodeType,
type CanvasAssistantImage,
@@ -638,12 +639,6 @@ function InfiniteCanvasPage() {
nodes.forEach((node) => map.set(node.id, buildNodeMentionReferences(node, nodes, connections)));
return map;
}, [connections, nodes]);
const configMentionReferencesById = useMemo(() => {
const map = new Map<string, ReturnType<typeof buildInputMentionReferences>>();
configInputsById.forEach((inputs, nodeId) => map.set(nodeId, buildInputMentionReferences(inputs)));
return map;
}, [configInputsById]);
const createNode = useCallback(
(type: CanvasNodeType, position?: Position) => {
const targetPosition = position || getCanvasCenter();
@@ -1231,7 +1226,8 @@ function InfiniteCanvasPage() {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement) return;
const target = event.target instanceof Element ? event.target : null;
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement || target?.closest("[contenteditable='true'],[data-canvas-no-zoom]")) return;
const key = event.key.toLowerCase();
const isModifierShortcut = event.metaKey || event.ctrlKey;
@@ -1735,12 +1731,13 @@ function InfiniteCanvasPage() {
);
const effectivePrompt = generationContext.prompt.trim();
const markSourceStatus = sourceNode?.type !== CanvasNodeType.Image && !editingTextNode;
const statusPrompt = sourceNode?.type === CanvasNodeType.Config ? effectivePrompt : prompt;
if (!effectivePrompt && (mode === "text" || mode === "audio")) {
setRunningNodeId(null);
return;
}
let pendingChildIds: string[] = [];
if (markSourceStatus) setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node)));
if (markSourceStatus) setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, prompt: statusPrompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node)));
try {
if (mode === "image") {
@@ -1804,7 +1801,7 @@ function InfiniteCanvasPage() {
? isConfigNode
? {
...node,
metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined },
metadata: { ...node.metadata, prompt: effectivePrompt, status: NODE_STATUS_LOADING, errorDetails: undefined },
}
: isEmptyImageNode
? {
@@ -1955,16 +1952,16 @@ function InfiniteCanvasPage() {
const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({
id,
type: CanvasNodeType.Text,
title: prompt.slice(0, 32) || "Generated Text",
title: effectivePrompt.slice(0, 32) || "Generated Text",
position: {
x: parentPosition.x + parentConfig.width + 96,
y: parentPosition.y + parentConfig.height / 2 - textConfig.height / 2 + (index - (textCount - 1) / 2) * (textConfig.height + 36),
},
width: textConfig.width,
height: textConfig.height,
metadata: { prompt, status: NODE_STATUS_LOADING, fontSize: 14 },
metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, fontSize: 14 },
}));
setNodes((prev) => [...prev.map((node) => (node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node)), ...childNodes]);
setNodes((prev) => [...prev.map((node) => (node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, prompt: effectivePrompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node)), ...childNodes]);
setConnections((prev) => [...prev, ...childIds.map((childId) => ({ id: nanoid(), fromNodeId: nodeId, toNodeId: childId }))]);
}
@@ -2293,32 +2290,39 @@ function InfiniteCanvasPage() {
showImageInfo={showImageInfo}
resourceLabel={resourceReferenceByNodeId.get(node.id)}
mentionReferences={mentionReferencesByNodeId.get(node.id) || []}
renderPanel={(panelNode) => (
<CanvasNodePromptPanel
node={panelNode}
isRunning={runningNodeId === panelNode.id}
mentionReferences={mentionReferencesByNodeId.get(panelNode.id) || []}
onPromptChange={handleNodePromptChange}
onConfigChange={handleConfigNodeChange}
onGenerate={handleGenerateNode}
onImageSettingsOpenChange={(open) => {
setNodeImageSettingsOpen(open);
if (open) setToolbarNodeId(null);
}}
/>
)}
renderPanel={(panelNode) =>
panelNode.type === CanvasNodeType.Config ? (
<CanvasConfigComposer
value={panelNode.metadata?.composerContent ?? panelNode.metadata?.prompt ?? ""}
inputs={configInputsById.get(panelNode.id) || []}
onChange={(composerContent) => handleConfigNodeChange(panelNode.id, { composerContent })}
onClose={() => setDialogNodeId(null)}
/>
) : (
<CanvasNodePromptPanel
node={panelNode}
isRunning={runningNodeId === panelNode.id}
mentionReferences={mentionReferencesByNodeId.get(panelNode.id) || []}
onPromptChange={handleNodePromptChange}
onConfigChange={handleConfigNodeChange}
onGenerate={handleGenerateNode}
onImageSettingsOpenChange={(open) => {
setNodeImageSettingsOpen(open);
if (open) setToolbarNodeId(null);
}}
/>
)
}
renderNodeContent={(contentNode) => (
<CanvasConfigNodePanel
node={contentNode}
isRunning={runningNodeId === contentNode.id}
inputSummary={getInputSummary(configInputsById.get(contentNode.id) || [])}
inputs={configInputsById.get(contentNode.id) || []}
mentionReferences={configMentionReferencesById.get(contentNode.id) || []}
onConfigChange={handleConfigNodeChange}
onTextInputChange={handleNodeContentChange}
onComposerToggle={() => setDialogNodeId((current) => (current === contentNode.id ? null : contentNode.id))}
onGenerate={(nodeId) => {
const target = nodesRef.current.find((item) => item.id === nodeId);
void handleGenerateNode(nodeId, target?.metadata?.generationMode || "image", target?.metadata?.prompt || "");
void handleGenerateNode(nodeId, target?.metadata?.generationMode || "image", target?.metadata?.composerContent ?? target?.metadata?.prompt ?? "");
}}
/>
)}