Browse Source

feat(ENHANCE-001): store input schema in node data

- Add ModelInputDef type to node data interfaces
- Update ModelParameters to pass inputs via onInputsLoaded callback
- GenerateImageNode and GenerateVideoNode store input schema

This sets up the foundation for dynamic handle rendering.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
0285d6125c
  1. 11
      src/components/nodes/GenerateImageNode.tsx
  2. 11
      src/components/nodes/GenerateVideoNode.tsx
  3. 12
      src/components/nodes/ModelParameters.tsx
  4. 11
      src/types/index.ts

11
src/components/nodes/GenerateImageNode.tsx

@ -5,7 +5,7 @@ import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode"; import { BaseNode } from "./BaseNode";
import { ModelParameters } from "./ModelParameters"; import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore, saveNanoBananaDefaults } from "@/store/workflowStore"; 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"; import { ProviderModel, ModelCapability } from "@/lib/providers/types";
// All 10 aspect ratios supported by both models // All 10 aspect ratios supported by both models
@ -210,6 +210,14 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
[id, updateNodeData] [id, updateNodeData]
); );
// Handle inputs loaded from schema
const handleInputsLoaded = useCallback(
(inputs: ModelInputDef[]) => {
updateNodeData(id, { inputSchema: inputs });
},
[id, updateNodeData]
);
// Handle parameters expand/collapse - resize node height // Handle parameters expand/collapse - resize node height
const { setNodes } = useReactFlow(); const { setNodes } = useReactFlow();
const handleParametersExpandChange = useCallback( const handleParametersExpandChange = useCallback(
@ -559,6 +567,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
parameters={nodeData.parameters || {}} parameters={nodeData.parameters || {}}
onParametersChange={handleParametersChange} onParametersChange={handleParametersChange}
onExpandChange={handleParametersExpandChange} onExpandChange={handleParametersExpandChange}
onInputsLoaded={handleInputsLoaded}
/> />
)} )}
</> </>

11
src/components/nodes/GenerateVideoNode.tsx

@ -5,7 +5,7 @@ import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode"; import { BaseNode } from "./BaseNode";
import { ModelParameters } from "./ModelParameters"; import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore } from "@/store/workflowStore"; 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"; import { ProviderModel, ModelCapability } from "@/lib/providers/types";
// Video generation capabilities // Video generation capabilities
@ -122,6 +122,14 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVide
[id, updateNodeData] [id, updateNodeData]
); );
// Handle inputs loaded from schema
const handleInputsLoaded = useCallback(
(inputs: ModelInputDef[]) => {
updateNodeData(id, { inputSchema: inputs });
},
[id, updateNodeData]
);
// Handle parameters expand/collapse - resize node height // Handle parameters expand/collapse - resize node height
const { setNodes } = useReactFlow(); const { setNodes } = useReactFlow();
const handleParametersExpandChange = useCallback( const handleParametersExpandChange = useCallback(
@ -325,6 +333,7 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVide
parameters={nodeData.parameters || {}} parameters={nodeData.parameters || {}}
onParametersChange={handleParametersChange} onParametersChange={handleParametersChange}
onExpandChange={handleParametersExpandChange} onExpandChange={handleParametersExpandChange}
onInputsLoaded={handleInputsLoaded}
/> />
)} )}
</div> </div>

12
src/components/nodes/ModelParameters.tsx

@ -1,7 +1,7 @@
"use client"; "use client";
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { ProviderType } from "@/types"; import { ProviderType, ModelInputDef } from "@/types";
import { ModelParameter } from "@/lib/providers/types"; import { ModelParameter } from "@/lib/providers/types";
import { useWorkflowStore } from "@/store/workflowStore"; import { useWorkflowStore } from "@/store/workflowStore";
@ -11,6 +11,7 @@ interface ModelParametersProps {
parameters: Record<string, unknown>; parameters: Record<string, unknown>;
onParametersChange: (parameters: Record<string, unknown>) => void; onParametersChange: (parameters: Record<string, unknown>) => void;
onExpandChange?: (expanded: boolean, parameterCount: number) => void; onExpandChange?: (expanded: boolean, parameterCount: number) => void;
onInputsLoaded?: (inputs: ModelInputDef[]) => void;
} }
/** /**
@ -24,6 +25,7 @@ export function ModelParameters({
parameters, parameters,
onParametersChange, onParametersChange,
onExpandChange, onExpandChange,
onInputsLoaded,
}: ModelParametersProps) { }: ModelParametersProps) {
const [schema, setSchema] = useState<ModelParameter[]>([]); const [schema, setSchema] = useState<ModelParameter[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -35,6 +37,7 @@ export function ModelParameters({
useEffect(() => { useEffect(() => {
if (!modelId || provider === "gemini") { if (!modelId || provider === "gemini") {
setSchema([]); setSchema([]);
onInputsLoaded?.([]);
return; return;
} }
@ -64,6 +67,11 @@ export function ModelParameters({
const data = await response.json(); const data = await response.json();
setSchema(data.parameters || []); setSchema(data.parameters || []);
// Pass inputs to parent for dynamic handle rendering
if (data.inputs && onInputsLoaded) {
onInputsLoaded(data.inputs);
}
} catch (err) { } catch (err) {
console.error("Failed to fetch model schema:", err); console.error("Failed to fetch model schema:", err);
setError(err instanceof Error ? err.message : "Failed to fetch schema"); setError(err instanceof Error ? err.message : "Failed to fetch schema");
@ -74,7 +82,7 @@ export function ModelParameters({
}; };
fetchSchema(); fetchSchema();
}, [modelId, provider, providerSettings]); }, [modelId, provider, providerSettings, onInputsLoaded]);
const handleParameterChange = useCallback( const handleParameterChange = useCallback(
(name: string, value: unknown) => { (name: string, value: unknown) => {

11
src/types/index.ts

@ -133,6 +133,15 @@ export interface CarouselImageItem {
model: ModelType; 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) // Nano Banana Node Data (Image Generation)
export interface NanoBananaNodeData extends BaseNodeData { export interface NanoBananaNodeData extends BaseNodeData {
inputImages: string[]; // Now supports multiple images 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) selectedModel?: SelectedModel; // Multi-provider model selection (optional for backward compat)
useGoogleSearch: boolean; // Only available for Nano Banana Pro useGoogleSearch: boolean; // Only available for Nano Banana Pro
parameters?: Record<string, unknown>; // Model-specific parameters for external providers parameters?: Record<string, unknown>; // Model-specific parameters for external providers
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
imageHistory: CarouselImageItem[]; // Carousel history (IDs only) imageHistory: CarouselImageItem[]; // Carousel history (IDs only)
@ -161,6 +171,7 @@ export interface GenerateVideoNodeData extends BaseNodeData {
outputVideoRef?: string; // External video reference for storage optimization outputVideoRef?: string; // External video reference for storage optimization
selectedModel?: SelectedModel; // Required for video generation (no legacy fallback) selectedModel?: SelectedModel; // Required for video generation (no legacy fallback)
parameters?: Record<string, unknown>; // Model-specific parameters parameters?: Record<string, unknown>; // Model-specific parameters
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
} }

Loading…
Cancel
Save