diff --git a/src/components/__tests__/GenerationComposer.test.tsx b/src/components/__tests__/GenerationComposer.test.tsx
index 6215587f..687002fb 100644
--- a/src/components/__tests__/GenerationComposer.test.tsx
+++ b/src/components/__tests__/GenerationComposer.test.tsx
@@ -1046,13 +1046,19 @@ describe("GenerationComposer", () => {
expect(data.durationSeconds).toBe(4);
});
- it("shows custom selected video duration in quick settings", () => {
+ it("defaults gateway video dimension quick settings to the first schema option", async () => {
useWorkflowStore.setState({
nodes: [videoNode("vid-1", true, { durationSeconds: 6 })],
});
render();
- expect(screen.getByLabelText("Duration")).toHaveValue("6");
+ expect(screen.getByLabelText("Duration")).toHaveValue("4");
+ await waitFor(() => {
+ expect((useWorkflowStore.getState().nodes[0].data as GenerateVideoNodeData).parameters).toMatchObject({
+ duration: "4",
+ resolution: "720p",
+ });
+ });
});
it("uses the audio model filter and reruns the selected audio node", async () => {
diff --git a/src/components/composer/GenerationComposer.tsx b/src/components/composer/GenerationComposer.tsx
index ca63d6c2..b5289d95 100644
--- a/src/components/composer/GenerationComposer.tsx
+++ b/src/components/composer/GenerationComposer.tsx
@@ -208,10 +208,6 @@ function buildGatewayDimensionSelects(
.map((parameter) => {
const options = getSchemaSelectOptions(parameter);
const value = draft.parameters?.[parameter.name]
- ?? (parameter.name === "ratio" ? draft.aspectRatio : undefined)
- ?? (parameter.name === "resolution" ? draft.resolution : undefined)
- ?? (parameter.name === "duration" ? draft.durationSeconds : undefined)
- ?? parameter.default
?? options[0]?.value;
return {
parameter,
@@ -221,6 +217,24 @@ function buildGatewayDimensionSelects(
});
}
+function applyGatewayDimensionParameterDefaults(
+ schema: ModelParameter[] | undefined,
+ parameters: Record | undefined
+): { parameters: Record; changed: boolean } {
+ const next = { ...(parameters ?? {}) };
+ let changed = false;
+ const nameSet = new Set(GATEWAY_DIMENSION_PARAMETER_NAMES);
+
+ for (const parameter of schema ?? []) {
+ if (!nameSet.has(parameter.name) || !parameter.options || parameter.options.length === 0) continue;
+ if (next[parameter.name] !== undefined && next[parameter.name] !== null && next[parameter.name] !== "") continue;
+ next[parameter.name] = parameter.options[0]?.value;
+ changed = true;
+ }
+
+ return { parameters: next, changed };
+}
+
function getImageCountOptions(schema: ModelParameter[] | undefined): ImageGenerationCount[] {
return getSchemaOptionValues(schema, IMAGE_COUNT_PARAMETER_NAMES)
.map((value) => Number(value))
@@ -943,7 +957,7 @@ function buildInitialDataForNode(nodeType: NodeType, draft: ComposerDraft): Part
model: getLegacyImageModel(draft.selectedModel),
selectedModel: draft.selectedModel,
imageCount: draft.imageCount,
- parameters: {},
+ parameters: draft.parameters ?? {},
inputSchema: undefined,
useGoogleSearch: false,
useImageSearch: false,
@@ -959,7 +973,7 @@ function buildInitialDataForNode(nodeType: NodeType, draft: ComposerDraft): Part
resolution: draft.resolution,
durationSeconds: normalizeVideoDurationSeconds(draft.durationSeconds),
soundEnabled: modelSupportsVideoSound(draft.selectedModel) ? draft.soundEnabled ?? true : undefined,
- parameters: {},
+ parameters: draft.parameters ?? {},
inputSchema: undefined,
status: "idle",
error: null,
@@ -970,7 +984,7 @@ function buildInitialDataForNode(nodeType: NodeType, draft: ComposerDraft): Part
inputImages: draft.inputImages,
inputPrompt: draft.prompt.trim(),
selectedModel: draft.selectedModel,
- parameters: {},
+ parameters: draft.parameters ?? {},
inputSchema: undefined,
status: "idle",
error: null,
@@ -980,7 +994,7 @@ function buildInitialDataForNode(nodeType: NodeType, draft: ComposerDraft): Part
return {
inputPrompt: draft.prompt.trim(),
selectedModel: draft.selectedModel,
- parameters: {},
+ parameters: draft.parameters ?? {},
inputSchema: undefined,
status: "idle",
error: null,
@@ -1553,6 +1567,20 @@ export function GenerationComposer() {
? context.node?.data as NanoBananaNodeData | GenerateVideoNodeData | GenerateAudioNodeData | undefined
: undefined;
const activeParameterSchema = generationNodeData?.parameterSchema ?? [];
+ useEffect(() => {
+ if (context.mode !== "node-edit" || !context.node || activeParameterSchema.length === 0) return;
+ const normalized = applyGatewayDimensionParameterDefaults(activeParameterSchema, draftRef.current.parameters);
+ if (!normalized.changed) return;
+
+ setDraft((current) => {
+ const currentNormalized = applyGatewayDimensionParameterDefaults(activeParameterSchema, current.parameters);
+ if (!currentNormalized.changed) return current;
+ const next = { ...current, parameters: currentNormalized.parameters };
+ draftRef.current = next;
+ return next;
+ });
+ updateNodeData(context.node.id, { parameters: normalized.parameters });
+ }, [activeParameterSchema, context, updateNodeData]);
const gatewayDimensionSelects = buildGatewayDimensionSelects(activeParameterSchema, draft);
const imageCountOptions = getImageCountOptions(activeParameterSchema);
const visibleImageCountOptions = imageCountOptions.length > 0
diff --git a/src/components/nodes/PromptNode.tsx b/src/components/nodes/PromptNode.tsx
index fa8e5a7b..f07fef72 100644
--- a/src/components/nodes/PromptNode.tsx
+++ b/src/components/nodes/PromptNode.tsx
@@ -235,10 +235,14 @@ export function PromptNode({ id, data, selected }: NodeProps) {
setIsEditing(true);
}
}}
- className="flex h-full w-full cursor-text items-center justify-center rounded-lg bg-neutral-800 p-8 text-center outline-none"
+ className={`nodrag nopan nowheel flex h-full w-full cursor-text rounded-lg bg-neutral-800 p-8 outline-none ${
+ promptText
+ ? "items-start justify-start overflow-auto text-left select-text"
+ : "items-center justify-center text-center"
+ }`}
>
{promptText ? (
-