Browse Source

fix: PromptNode Enter-to-save and ConnectionDropMenu auto-connect for new nodes

Add onKeyDown handler to PromptNode variable name input so Enter saves.
Add outputGallery, imageCompare, and promptConstructor to ConnectionDropMenu
handle mappings for auto-connect on drag-drop. For imageCompare, redirect
connections to the second handle (image-1) when the first is occupied.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
259a256290
  1. 47
      src/components/WorkflowCanvas.tsx
  2. 5
      src/components/nodes/PromptNode.tsx

47
src/components/WorkflowCanvas.tsx

@ -310,6 +310,20 @@ export function WorkflowCanvas() {
(connection: Connection) => { (connection: Connection) => {
if (!isValidConnection(connection)) return; if (!isValidConnection(connection)) return;
// For imageCompare nodes, redirect to the second handle if the first is occupied
const resolveImageCompareHandle = (conn: Connection, batchUsed?: Set<string>): Connection => {
const targetNode = nodes.find((n) => n.id === conn.target);
if (targetNode?.type === "imageCompare" && conn.targetHandle === "image") {
const imageOccupied = edges.some(
(e) => e.target === conn.target && e.targetHandle === "image"
) || batchUsed?.has("image");
if (imageOccupied) {
return { ...conn, targetHandle: "image-1" };
}
}
return conn;
};
// Get all selected nodes // Get all selected nodes
const selectedNodes = nodes.filter((node) => node.selected); const selectedNodes = nodes.filter((node) => node.selected);
const sourceNode = nodes.find((node) => node.id === connection.source); const sourceNode = nodes.find((node) => node.id === connection.source);
@ -317,10 +331,14 @@ export function WorkflowCanvas() {
// If the source node is selected and there are multiple selected nodes, // If the source node is selected and there are multiple selected nodes,
// connect all selected nodes that have the same source handle type // connect all selected nodes that have the same source handle type
if (sourceNode?.selected && selectedNodes.length > 1 && connection.sourceHandle) { if (sourceNode?.selected && selectedNodes.length > 1 && connection.sourceHandle) {
const batchUsed = new Set<string>();
selectedNodes.forEach((node) => { selectedNodes.forEach((node) => {
// Skip if this is already the connection source // Skip if this is already the connection source
if (node.id === connection.source) { if (node.id === connection.source) {
onConnect(connection); const resolved = resolveImageCompareHandle(connection, batchUsed);
if (resolved.targetHandle) batchUsed.add(resolved.targetHandle);
onConnect(resolved);
return; return;
} }
@ -339,16 +357,18 @@ export function WorkflowCanvas() {
targetHandle: connection.targetHandle, targetHandle: connection.targetHandle,
}; };
if (isValidConnection(multiConnection)) { const resolved = resolveImageCompareHandle(multiConnection, batchUsed);
onConnect(multiConnection); if (resolved.targetHandle) batchUsed.add(resolved.targetHandle);
if (isValidConnection(resolved)) {
onConnect(resolved);
} }
}); });
} else { } else {
// Single connection // Single connection
onConnect(connection); onConnect(resolveImageCompareHandle(connection));
} }
}, },
[onConnect, nodes] [onConnect, nodes, edges]
); );
// Handle connection dropped on empty space or on a node // Handle connection dropped on empty space or on a node
@ -683,7 +703,7 @@ export function WorkflowCanvas() {
// Map handle type to the correct handle ID based on node type // Map handle type to the correct handle ID based on node type
// Note: New nodes start with default handles (image, text) before a model is selected // Note: New nodes start with default handles (image, text) before a model is selected
if (handleType === "image") { if (handleType === "image") {
if (nodeType === "annotation" || nodeType === "output" || nodeType === "splitGrid") { if (nodeType === "annotation" || nodeType === "output" || nodeType === "splitGrid" || nodeType === "outputGallery" || nodeType === "imageCompare") {
targetHandleId = "image"; targetHandleId = "image";
// annotation also has an image output // annotation also has an image output
if (nodeType === "annotation") { if (nodeType === "annotation") {
@ -701,8 +721,8 @@ export function WorkflowCanvas() {
if (nodeType === "llmGenerate") { if (nodeType === "llmGenerate") {
sourceHandleIdForNewNode = "text"; sourceHandleIdForNewNode = "text";
} }
} else if (nodeType === "prompt") { } else if (nodeType === "prompt" || nodeType === "promptConstructor") {
// prompt can receive and output text // prompt and promptConstructor can receive and output text
targetHandleId = "text"; targetHandleId = "text";
sourceHandleIdForNewNode = "text"; sourceHandleIdForNewNode = "text";
} }
@ -715,14 +735,23 @@ export function WorkflowCanvas() {
// If the source node is selected and there are multiple selected nodes, // If the source node is selected and there are multiple selected nodes,
// connect all selected nodes to the new node // connect all selected nodes to the new node
if (sourceNode?.selected && selectedNodes.length > 1 && sourceHandleId) { if (sourceNode?.selected && selectedNodes.length > 1 && sourceHandleId) {
const batchUsed = new Set<string>();
selectedNodes.forEach((node) => { selectedNodes.forEach((node) => {
if (connectionType === "source" && targetHandleId) { if (connectionType === "source" && targetHandleId) {
// For imageCompare, alternate between image and image-1
let resolvedTargetHandle = targetHandleId;
if (nodeType === "imageCompare" && targetHandleId === "image" && batchUsed.has("image")) {
resolvedTargetHandle = "image-1";
}
batchUsed.add(resolvedTargetHandle);
// Dragging from source (output), connect selected nodes to new node's input // Dragging from source (output), connect selected nodes to new node's input
const connection: Connection = { const connection: Connection = {
source: node.id, source: node.id,
sourceHandle: sourceHandleId, sourceHandle: sourceHandleId,
target: newNodeId, target: newNodeId,
targetHandle: targetHandleId, targetHandle: resolvedTargetHandle,
}; };
if (isValidConnection(connection)) { if (isValidConnection(connection)) {
onConnect(connection); onConnect(connection);

5
src/components/nodes/PromptNode.tsx

@ -195,6 +195,11 @@ export function PromptNode({ id, data, selected }: NodeProps<PromptNodeType>) {
type="text" type="text"
value={varNameInput} value={varNameInput}
onChange={handleVariableNameChange} onChange={handleVariableNameChange}
onKeyDown={(e) => {
if (e.key === "Enter" && varNameInput) {
handleSaveVariableName();
}
}}
placeholder="e.g. color, style, subject" placeholder="e.g. color, style, subject"
className="w-full px-3 py-2 text-sm text-neutral-100 bg-neutral-900 border border-neutral-700 rounded focus:outline-none focus:ring-1 focus:ring-blue-500" className="w-full px-3 py-2 text-sm text-neutral-100 bg-neutral-900 border border-neutral-700 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
autoFocus autoFocus

Loading…
Cancel
Save