From fc157f1d7bb8d7c48cf61162b9610c1be4a507e2 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Sat, 28 Feb 2026 22:30:29 +1300 Subject: [PATCH] fix: add hardcoded Veo video parameters to GenerateVideoNode Veo models (veo-3.1, veo-3.1-fast) showed no user-configurable parameters in the UI. Follows the established pattern from GenerateImageNode where Gemini model parameters are hardcoded in the component rather than fetched via the schema API. Adds aspect ratio, duration, resolution, negative prompt, and seed controls. Co-Authored-By: Claude Opus 4.6 --- src/components/nodes/GenerateVideoNode.tsx | 83 +++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index dbcd2d86..74424fb2 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -18,6 +18,17 @@ import { useVideoBlobUrl } from "@/hooks/useVideoBlobUrl"; // Video generation capabilities const VIDEO_CAPABILITIES: ModelCapability[] = ["text-to-video", "image-to-video"]; +// Hardcoded Veo parameter options (matches getGeminiVideoSchema in models/[modelId]/route.ts) +const VEO_ASPECT_RATIOS = ["16:9", "9:16"] as const; +const VEO_DURATIONS = ["4", "6", "8"] as const; +const VEO_RESOLUTIONS = ["720p", "1080p", "4k"] as const; + +/** Returns true for Gemini-native Veo video models */ +function isVeoModel(modelId: string | undefined): boolean { + if (!modelId) return false; + return modelId.startsWith("veo-"); +} + type GenerateVideoNodeType = Node; export function GenerateVideoNode({ id, data, selected }: NodeProps) { @@ -34,7 +45,6 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { + const current = nodeData.parameters || {}; + // Remove the key if value is empty string (clear optional fields) + if (value === "") { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [key]: _, ...rest } = current; + updateNodeData(id, { parameters: rest }); + } else { + updateNodeData(id, { parameters: { ...current, [key]: value } }); + } + }, + [id, nodeData.parameters, updateNodeData] + ); + // Handle inputs loaded from schema const handleInputsLoaded = useCallback( (inputs: ModelInputDef[]) => { @@ -717,7 +743,58 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps + {/* Aspect ratio + Duration row */} +
+ + + +
+ {/* Negative prompt */} + updateVeoParam("negativePrompt", e.target.value)} + className="w-full text-[10px] py-1 px-1.5 border border-neutral-700 rounded bg-neutral-900/50 focus:outline-none focus:ring-1 focus:ring-neutral-600 text-neutral-300 placeholder:text-neutral-600" + /> + {/* Seed */} + updateVeoParam("seed", e.target.value === "" ? "" : Number(e.target.value))} + min={0} + className="w-full text-[10px] py-1 px-1.5 border border-neutral-700 rounded bg-neutral-900/50 focus:outline-none focus:ring-1 focus:ring-neutral-600 text-neutral-300 placeholder:text-neutral-600" + /> + + ) : nodeData.selectedModel?.modelId ? ( - )} + ) : null}