diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 425c798f..4d3c9205 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -333,12 +333,22 @@ export function WorkflowCanvas() { const nodeData = node.data as { inputSchema?: Array<{ name: string; type: string }> }; if (nodeData.inputSchema && nodeData.inputSchema.length > 0) { if (needInput) { - // Find first input handle matching the type + // Find input handles matching the type const matchingInputs = nodeData.inputSchema.filter(i => i.type === handleType); - if (matchingInputs.length > 0) { - // Return normalized handle ID, not schema name - // Always use indexed IDs (image-0, text-0) for schema inputs for consistency - return `${handleType}-0`; + const numHandles = matchingInputs.length; + if (numHandles > 0) { + // Find the first unoccupied indexed handle by checking existing edges + for (let i = 0; i < numHandles; i++) { + const candidateHandle = `${handleType}-${i}`; + const isOccupied = edges.some( + (edge) => edge.target === node.id && edge.targetHandle === candidateHandle + ); + if (!isOccupied) { + return candidateHandle; + } + } + // All handles are occupied + return null; } } // Output handle - check for video or image type @@ -425,7 +435,7 @@ export function WorkflowCanvas() { sourceHandleId: fromHandleId, }); }, - [screenToFlowPosition, nodes, handleConnect] + [screenToFlowPosition, nodes, edges, handleConnect] ); // Handle the splitGrid action - uses automated grid detection diff --git a/src/components/__tests__/QuickstartInitialView.test.tsx b/src/components/__tests__/QuickstartInitialView.test.tsx index 31a89128..09706010 100644 --- a/src/components/__tests__/QuickstartInitialView.test.tsx +++ b/src/components/__tests__/QuickstartInitialView.test.tsx @@ -75,7 +75,7 @@ describe("QuickstartInitialView", () => { expect(screen.getByText("Start from scratch")).toBeInTheDocument(); expect(screen.getByText("Open existing file")).toBeInTheDocument(); expect(screen.getByText("Pre-built workflows")).toBeInTheDocument(); - expect(screen.getByText("Describe what you want")).toBeInTheDocument(); + expect(screen.getByText("Prompt a workflow")).toBeInTheDocument(); }); }); diff --git a/src/components/nodes/GenerateImageNode.tsx b/src/components/nodes/GenerateImageNode.tsx index dbbd36f6..5ee59481 100644 --- a/src/components/nodes/GenerateImageNode.tsx +++ b/src/components/nodes/GenerateImageNode.tsx @@ -831,8 +831,8 @@ export function GenerateImageNode({ id, data, selected }: NodeProps )} - {/* Aspect ratio and resolution row - only for Gemini-only mode */} - {isGeminiOnly && ( + {/* Aspect ratio and resolution row - only for Gemini models */} + {currentProvider === "gemini" && (
0 && inputImages.every(i => i === "") ? [] : inputImages, + inputImageRefs: inputImageRefs.length > 0 ? inputImageRefs : undefined, + }; + break; + } + case "output": { const d = data as import("@/types").OutputNodeData; // Output displays generated content, save to generations @@ -374,6 +404,27 @@ async function hydrateNodeImages( break; } + case "generateVideo": { + const d = data as import("@/types").GenerateVideoNodeData; + const inputImages = [...d.inputImages]; + + // Hydrate input images from refs + if (d.inputImageRefs && d.inputImageRefs.length > 0) { + for (let i = 0; i < d.inputImageRefs.length; i++) { + const ref = d.inputImageRefs[i]; + if (ref) { + inputImages[i] = await loadImageById(ref, workflowPath, loadedImages, "inputs"); + } + } + } + + newData = { + ...d, + inputImages, + }; + break; + } + case "output": { const d = data as import("@/types").OutputNodeData; if (d.imageRef && !d.image) {