diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index 36de2ccb..29d15a54 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -480,13 +480,21 @@ export async function POST(request: NextRequest) { // Check if this is a Veo video model request if (selectedModel?.modelId?.startsWith("veo-")) { + // Merge negative prompt from dynamic inputs (connected handle) into parameters + const veoParams = { ...(parameters || {}) }; + if (dynamicInputs?.negative_prompt) { + const neg = Array.isArray(dynamicInputs.negative_prompt) + ? dynamicInputs.negative_prompt[0] + : dynamicInputs.negative_prompt; + if (neg) veoParams.negativePrompt = neg; + } const result = await generateWithGeminiVideo( requestId, geminiApiKey, selectedModel.modelId, resolvedPrompt || "", images || [], - parameters || {}, + veoParams, ); if (!result.success) { diff --git a/src/app/api/models/[modelId]/route.ts b/src/app/api/models/[modelId]/route.ts index ce6aa9ec..0973dde1 100644 --- a/src/app/api/models/[modelId]/route.ts +++ b/src/app/api/models/[modelId]/route.ts @@ -900,30 +900,34 @@ function getGeminiVideoSchema(modelId: string): ExtractedSchema | null { { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16"], default: "16:9" }, { name: "durationSeconds", type: "string", description: "Video duration in seconds", enum: ["4", "6", "8"], default: "8" }, { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p", "4k"], default: "720p" }, - { name: "negativePrompt", type: "string", description: "What to avoid in the generated video" }, { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, ]; + const textInputs: ModelInput[] = [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "negative_prompt", type: "text", required: false, label: "Neg. Prompt" }, + ]; + const schemas: Record = { "veo-3.1/text-to-video": { parameters: commonParams, - inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + inputs: textInputs, }, "veo-3.1/image-to-video": { parameters: commonParams, inputs: [ - { name: "prompt", type: "text", required: true, label: "Prompt" }, + ...textInputs, { name: "image", type: "image", required: true, label: "Image" }, ], }, "veo-3.1-fast/text-to-video": { parameters: commonParams, - inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + inputs: textInputs, }, "veo-3.1-fast/image-to-video": { parameters: commonParams, inputs: [ - { name: "prompt", type: "text", required: true, label: "Prompt" }, + ...textInputs, { name: "image", type: "image", required: true, label: "Image" }, ], }, diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index 74424fb2..43ce04c1 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -114,6 +114,21 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { + if (!isVeoModel(selectedModelId)) return; + const isI2V = selectedModelId!.includes("image-to-video"); + const inputs: ModelInputDef[] = [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "negative_prompt", type: "text", required: false, label: "Neg. Prompt" }, + ]; + if (isI2V) { + inputs.unshift({ name: "image", type: "image", required: true, label: "Image" }); + } + updateNodeData(id, { inputSchema: inputs }); + }, [id, selectedModelId, updateNodeData]); + // Handle provider change const handleProviderChange = useCallback( (e: React.ChangeEvent) => { @@ -776,14 +791,6 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps - {/* 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 */}