"use client"; import { useState, useCallback } from "react"; import { useWorkflowStore } from "@/store/workflowStore"; import { SplitGridNodeData, AspectRatio, Resolution, ModelType } from "@/types"; interface SplitGridSettingsModalProps { nodeId: string; nodeData: SplitGridNodeData; onClose: () => void; } const LAYOUT_OPTIONS = [ { rows: 2, cols: 2 }, { rows: 1, cols: 5 }, { rows: 2, cols: 3 }, { rows: 3, cols: 2 }, { rows: 2, cols: 4 }, { rows: 3, cols: 3 }, { rows: 2, cols: 5 }, ] as const; const ASPECT_RATIOS: AspectRatio[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; const RESOLUTIONS: Resolution[] = ["1K", "2K", "4K"]; const MODELS: { value: ModelType; label: string }[] = [ { value: "nano-banana", label: "Nano Banana" }, { value: "nano-banana-pro", label: "Nano Banana Pro" }, ]; const findLayoutIndex = (rows: number, cols: number): number => { const idx = LAYOUT_OPTIONS.findIndex(l => l.rows === rows && l.cols === cols); return idx >= 0 ? idx : 2; // default to 2x3 }; export function SplitGridSettingsModal({ nodeId, nodeData, onClose, }: SplitGridSettingsModalProps) { const { updateNodeData, addNode, onConnect, addEdgeWithType, getNodeById } = useWorkflowStore(); const [selectedLayoutIndex, setSelectedLayoutIndex] = useState( findLayoutIndex(nodeData.gridRows, nodeData.gridCols) ); const [defaultPrompt, setDefaultPrompt] = useState(nodeData.defaultPrompt); const [aspectRatio, setAspectRatio] = useState(nodeData.generateSettings.aspectRatio); const [resolution, setResolution] = useState(nodeData.generateSettings.resolution); const [model, setModel] = useState(nodeData.generateSettings.model); const [useGoogleSearch, setUseGoogleSearch] = useState(nodeData.generateSettings.useGoogleSearch); const { rows, cols } = LAYOUT_OPTIONS[selectedLayoutIndex]; const targetCount = rows * cols; const isNanoBananaPro = model === "nano-banana-pro"; const handleCreate = useCallback(() => { const splitNode = getNodeById(nodeId); if (!splitNode) return; // Node dimensions const imageInputWidth = 300; const imageInputHeight = 280; const promptWidth = 320; const promptHeight = 220; const nanoBananaWidth = 300; const nanoBananaHeight = 300; const horizontalGap = 40; const verticalGap = 30; // Calculate cluster dimensions // Layout: imageInput on left, nanoBanana on right, prompt below imageInput const clusterWidth = imageInputWidth + horizontalGap + nanoBananaWidth; const clusterHeight = Math.max(imageInputHeight, nanoBananaHeight) + verticalGap + promptHeight; const clusterGap = 60; // Start position to the right of the split node const startX = splitNode.position.x + 350; const startY = splitNode.position.y; const childNodeIds: SplitGridNodeData["childNodeIds"] = []; // Create node clusters for each grid cell for (let i = 0; i < targetCount; i++) { const row = Math.floor(i / cols); const col = i % cols; // Position for this cluster const clusterX = startX + col * (clusterWidth + clusterGap); const clusterY = startY + row * (clusterHeight + clusterGap); // Create imageInput node const imageInputId = addNode("imageInput", { x: clusterX, y: clusterY, }); // Create nanoBanana node (to the right of imageInput) const nanoBananaId = addNode("nanoBanana", { x: clusterX + imageInputWidth + horizontalGap, y: clusterY, }); // Update nanoBanana settings updateNodeData(nanoBananaId, { aspectRatio, resolution, model, useGoogleSearch, }); // Create prompt node (below imageInput) const promptId = addNode("prompt", { x: clusterX, y: clusterY + Math.max(imageInputHeight, nanoBananaHeight) + verticalGap, }); // Update prompt with default text updateNodeData(promptId, { prompt: defaultPrompt }); // Create connections: imageInput -> nanoBanana, prompt -> nanoBanana onConnect({ source: imageInputId, sourceHandle: "image", target: nanoBananaId, targetHandle: "image", }); onConnect({ source: promptId, sourceHandle: "text", target: nanoBananaId, targetHandle: "text", }); // Create reference edge from split node to imageInput (grey dotted line) addEdgeWithType({ source: nodeId, sourceHandle: "reference", target: imageInputId, targetHandle: "reference", }, "reference"); childNodeIds.push({ imageInput: imageInputId, prompt: promptId, nanoBanana: nanoBananaId, }); } // Update split node with configuration updateNodeData(nodeId, { targetCount, defaultPrompt, generateSettings: { aspectRatio, resolution, model, useGoogleSearch, }, childNodeIds, gridRows: rows, gridCols: cols, isConfigured: true, }); onClose(); }, [ nodeId, targetCount, defaultPrompt, aspectRatio, resolution, model, useGoogleSearch, rows, cols, selectedLayoutIndex, getNodeById, addNode, updateNodeData, onConnect, addEdgeWithType, onClose ]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") { onClose(); } }; return (

Split Grid Settings

{/* Layout selector with visual preview */}
{LAYOUT_OPTIONS.map((layout, index) => { const count = layout.rows * layout.cols; const isSelected = selectedLayoutIndex === index; return ( ); })}

Grid will be split into {rows}x{cols} = {targetCount} images

{/* Default prompt */}