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) => {
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
const selectedNodes = nodes.filter((node) => node.selected);
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,
// connect all selected nodes that have the same source handle type
if (sourceNode?.selected && selectedNodes.length > 1 && connection.sourceHandle) {
const batchUsed = new Set<string>();
selectedNodes.forEach((node) => {
// Skip if this is already the connection source
if (node.id === connection.source) {
onConnect(connection);
const resolved = resolveImageCompareHandle(connection, batchUsed);
if (resolved.targetHandle) batchUsed.add(resolved.targetHandle);
onConnect(resolved);
return;
}
@ -339,16 +357,18 @@ export function WorkflowCanvas() {
targetHandle: connection.targetHandle,
};
if (isValidConnection(multiConnection)) {
onConnect(multiConnection);
const resolved = resolveImageCompareHandle(multiConnection, batchUsed);
if (resolved.targetHandle) batchUsed.add(resolved.targetHandle);
if (isValidConnection(resolved)) {
onConnect(resolved);
}
});
} else {
// Single connection
onConnect(connection);
onConnect(resolveImageCompareHandle(connection));
}
},
[onConnect, nodes]
[onConnect, nodes, edges]
);
// 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
// Note: New nodes start with default handles (image, text) before a model is selected
if (handleType === "image") {
if (nodeType === "annotation" || nodeType === "output" || nodeType === "splitGrid") {
if (nodeType === "annotation" || nodeType === "output" || nodeType === "splitGrid" || nodeType === "outputGallery" || nodeType === "imageCompare") {
targetHandleId = "image";
// annotation also has an image output
if (nodeType === "annotation") {
@ -701,8 +721,8 @@ export function WorkflowCanvas() {
if (nodeType === "llmGenerate") {
sourceHandleIdForNewNode = "text";
}
} else if (nodeType === "prompt") {
// prompt can receive and output text
} else if (nodeType === "prompt" || nodeType === "promptConstructor") {
// prompt and promptConstructor can receive and output text
targetHandleId = "text";
sourceHandleIdForNewNode = "text";
}
@ -715,14 +735,23 @@ export function WorkflowCanvas() {
// If the source node is selected and there are multiple selected nodes,
// connect all selected nodes to the new node
if (sourceNode?.selected && selectedNodes.length > 1 && sourceHandleId) {
const batchUsed = new Set<string>();
selectedNodes.forEach((node) => {
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
const connection: Connection = {
source: node.id,
sourceHandle: sourceHandleId,
target: newNodeId,
targetHandle: targetHandleId,
targetHandle: resolvedTargetHandle,
};
if (isValidConnection(connection)) {
onConnect(connection);

5
src/components/nodes/PromptNode.tsx

@ -195,6 +195,11 @@ export function PromptNode({ id, data, selected }: NodeProps<PromptNodeType>) {
type="text"
value={varNameInput}
onChange={handleVariableNameChange}
onKeyDown={(e) => {
if (e.key === "Enter" && varNameInput) {
handleSaveVariableName();
}
}}
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"
autoFocus

Loading…
Cancel
Save