Browse Source

fix: make Veo negative prompt a connectable text handle

Instead of an inline text field, negative prompt is now a proper text
handle on the node that can receive connections from prompt/LLM nodes.
Updates the API route to merge dynamicInputs.negative_prompt into the
Veo parameters, and aligns the model schema definition accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
90f4a21ad3
  1. 10
      src/app/api/generate/route.ts
  2. 14
      src/app/api/models/[modelId]/route.ts
  3. 23
      src/components/nodes/GenerateVideoNode.tsx

10
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) {

14
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<string, ExtractedSchema> = {
"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" },
],
},

23
src/components/nodes/GenerateVideoNode.tsx

@ -114,6 +114,21 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVide
fetchModels();
}, [fetchModels]);
// Set inputSchema for Veo models (hardcoded, not fetched via ModelParameters)
const selectedModelId = nodeData.selectedModel?.modelId;
useEffect(() => {
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<HTMLSelectElement>) => {
@ -776,14 +791,6 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVide
))}
</select>
</div>
{/* Negative prompt */}
<input
type="text"
placeholder="Negative prompt..."
value={(nodeData.parameters?.negativePrompt as string) || ""}
onChange={(e) => 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 */}
<input
type="number"

Loading…
Cancel
Save