diff --git a/src/components/nodes/GenerateImageNode.tsx b/src/components/nodes/GenerateImageNode.tsx index 9d097c42..92ac1a68 100644 --- a/src/components/nodes/GenerateImageNode.tsx +++ b/src/components/nodes/GenerateImageNode.tsx @@ -5,7 +5,7 @@ import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore, saveNanoBananaDefaults } from "@/store/workflowStore"; -import { NanoBananaNodeData, AspectRatio, Resolution, ModelType, ProviderType, SelectedModel } from "@/types"; +import { NanoBananaNodeData, AspectRatio, Resolution, ModelType, ProviderType, SelectedModel, ModelInputDef } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; // All 10 aspect ratios supported by both models @@ -210,6 +210,14 @@ export function GenerateImageNode({ id, data, selected }: NodeProps { + updateNodeData(id, { inputSchema: inputs }); + }, + [id, updateNodeData] + ); + // Handle parameters expand/collapse - resize node height const { setNodes } = useReactFlow(); const handleParametersExpandChange = useCallback( @@ -559,6 +567,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps )} diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index d40d496f..96f45816 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -5,7 +5,7 @@ import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore } from "@/store/workflowStore"; -import { GenerateVideoNodeData, ProviderType, SelectedModel } from "@/types"; +import { GenerateVideoNodeData, ProviderType, SelectedModel, ModelInputDef } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; // Video generation capabilities @@ -122,6 +122,14 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { + updateNodeData(id, { inputSchema: inputs }); + }, + [id, updateNodeData] + ); + // Handle parameters expand/collapse - resize node height const { setNodes } = useReactFlow(); const handleParametersExpandChange = useCallback( @@ -325,6 +333,7 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps )} diff --git a/src/components/nodes/ModelParameters.tsx b/src/components/nodes/ModelParameters.tsx index 763b63e8..143bed6d 100644 --- a/src/components/nodes/ModelParameters.tsx +++ b/src/components/nodes/ModelParameters.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect, useCallback } from "react"; -import { ProviderType } from "@/types"; +import { ProviderType, ModelInputDef } from "@/types"; import { ModelParameter } from "@/lib/providers/types"; import { useWorkflowStore } from "@/store/workflowStore"; @@ -11,6 +11,7 @@ interface ModelParametersProps { parameters: Record; onParametersChange: (parameters: Record) => void; onExpandChange?: (expanded: boolean, parameterCount: number) => void; + onInputsLoaded?: (inputs: ModelInputDef[]) => void; } /** @@ -24,6 +25,7 @@ export function ModelParameters({ parameters, onParametersChange, onExpandChange, + onInputsLoaded, }: ModelParametersProps) { const [schema, setSchema] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -35,6 +37,7 @@ export function ModelParameters({ useEffect(() => { if (!modelId || provider === "gemini") { setSchema([]); + onInputsLoaded?.([]); return; } @@ -64,6 +67,11 @@ export function ModelParameters({ const data = await response.json(); setSchema(data.parameters || []); + + // Pass inputs to parent for dynamic handle rendering + if (data.inputs && onInputsLoaded) { + onInputsLoaded(data.inputs); + } } catch (err) { console.error("Failed to fetch model schema:", err); setError(err instanceof Error ? err.message : "Failed to fetch schema"); @@ -74,7 +82,7 @@ export function ModelParameters({ }; fetchSchema(); - }, [modelId, provider, providerSettings]); + }, [modelId, provider, providerSettings, onInputsLoaded]); const handleParameterChange = useCallback( (name: string, value: unknown) => { diff --git a/src/types/index.ts b/src/types/index.ts index c192fddd..37e93424 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -133,6 +133,15 @@ export interface CarouselImageItem { model: ModelType; } +// Model input definition for dynamic handles +export interface ModelInputDef { + name: string; + type: "image" | "text"; + required: boolean; + label: string; + description?: string; +} + // Nano Banana Node Data (Image Generation) export interface NanoBananaNodeData extends BaseNodeData { inputImages: string[]; // Now supports multiple images @@ -146,6 +155,7 @@ export interface NanoBananaNodeData extends BaseNodeData { selectedModel?: SelectedModel; // Multi-provider model selection (optional for backward compat) useGoogleSearch: boolean; // Only available for Nano Banana Pro parameters?: Record; // Model-specific parameters for external providers + inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles status: NodeStatus; error: string | null; imageHistory: CarouselImageItem[]; // Carousel history (IDs only) @@ -161,6 +171,7 @@ export interface GenerateVideoNodeData extends BaseNodeData { outputVideoRef?: string; // External video reference for storage optimization selectedModel?: SelectedModel; // Required for video generation (no legacy fallback) parameters?: Record; // Model-specific parameters + inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles status: NodeStatus; error: string | null; }