From 4fd01a26bfabecbb0dc7e969353ca1826f3ff860 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Fri, 27 Feb 2026 06:39:46 +1300 Subject: [PATCH] feat: add nano-banana-2 to model registry, UI, and migration paths - Add Nano Banana 2 entry to GEMINI_IMAGE_MODELS in model registry - Add model to GenerateImageNode dropdown with 512px resolution option - Extend resolution/Google Search gates to support nano-banana-2 - Replace hardcoded model name ternaries with MODEL_DISPLAY_NAMES lookup in workflowStore, nodeDefaults, and GenerateImageNode migration code Co-Authored-By: Claude Opus 4.6 --- src/app/api/models/route.ts | 9 +++++++++ src/components/nodes/GenerateImageNode.tsx | 17 +++++++++-------- src/store/utils/nodeDefaults.ts | 3 ++- src/store/workflowStore.ts | 3 ++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/app/api/models/route.ts b/src/app/api/models/route.ts index cd43b6b9..ad30b686 100644 --- a/src/app/api/models/route.ts +++ b/src/app/api/models/route.ts @@ -366,6 +366,15 @@ const GEMINI_IMAGE_MODELS: ProviderModel[] = [ coverImage: undefined, pricing: { type: "per-run", amount: 0.039, currency: "USD" }, }, + { + id: "nano-banana-2", + name: "Nano Banana 2", + description: "High-efficiency image generation with Gemini 3.1 Flash. Supports resolution control (512/1K/2K/4K), Google Search grounding, and up to 10 reference images.", + provider: "gemini", + capabilities: ["text-to-image", "image-to-image"], + coverImage: undefined, + pricing: { type: "per-run", amount: 0.067, currency: "USD" }, + }, { id: "nano-banana-pro", name: "Nano Banana Pro", diff --git a/src/components/nodes/GenerateImageNode.tsx b/src/components/nodes/GenerateImageNode.tsx index 6fcddc2e..860b07b7 100644 --- a/src/components/nodes/GenerateImageNode.tsx +++ b/src/components/nodes/GenerateImageNode.tsx @@ -7,7 +7,7 @@ import { useCommentNavigation } from "@/hooks/useCommentNavigation"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore, saveNanoBananaDefaults, useProviderApiKeys } from "@/store/workflowStore"; import { deduplicatedFetch } from "@/utils/deduplicatedFetch"; -import { NanoBananaNodeData, AspectRatio, Resolution, ModelType, ProviderType, SelectedModel, ModelInputDef } from "@/types"; +import { NanoBananaNodeData, AspectRatio, Resolution, ModelType, MODEL_DISPLAY_NAMES, ProviderType, SelectedModel, ModelInputDef } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useToast } from "@/components/Toast"; @@ -17,12 +17,13 @@ import { ProviderBadge } from "./ProviderBadge"; // All 10 aspect ratios supported by both models const ASPECT_RATIOS: AspectRatio[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; -// Resolutions only for Nano Banana Pro (gemini-3-pro-image-preview) -const RESOLUTIONS: Resolution[] = ["1K", "2K", "4K"]; +// Resolutions for Nano Banana Pro and Nano Banana 2 +const RESOLUTIONS: Resolution[] = ["512", "1K", "2K", "4K"]; // Hardcoded Gemini image models (always available) const GEMINI_IMAGE_MODELS: { value: ModelType; label: string }[] = [ { value: "nano-banana", label: "Nano Banana" }, + { value: "nano-banana-2", label: "Nano Banana 2" }, { value: "nano-banana-pro", label: "Nano Banana Pro" }, ]; @@ -78,7 +79,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps { if (nodeData.model && !nodeData.selectedModel) { - const displayName = nodeData.model === "nano-banana" ? "Nano Banana" : "Nano Banana Pro"; + const displayName = MODEL_DISPLAY_NAMES[nodeData.model] || nodeData.model; const newSelectedModel: SelectedModel = { provider: "gemini", modelId: nodeData.model, @@ -392,7 +393,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps 1; // Track previous status to detect error transitions @@ -694,7 +695,7 @@ export function GenerateImageNode({ id, data, selected }: NodeProps ))} - {isNanoBananaPro && ( + {supportsResolution && ( { if (nodeDefaults.generateImage?.selectedModel) { selectedModel = nodeDefaults.generateImage.selectedModel; } else { - const modelDisplayName = legacyDefaults.model === "nano-banana" ? "Nano Banana" : "Nano Banana Pro"; + const modelDisplayName = MODEL_DISPLAY_NAMES[legacyDefaults.model] || legacyDefaults.model; selectedModel = { provider: "gemini", modelId: legacyDefaults.model, diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 9d1c9590..58ac2cf1 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -23,6 +23,7 @@ import { ProviderSettings, RecentModel, CanvasNavigationSettings, + MODEL_DISPLAY_NAMES, } from "@/types"; import { useToast } from "@/components/Toast"; import { logger } from "@/utils/logger"; @@ -1491,7 +1492,7 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ if (node.type === "nanoBanana") { const data = node.data as NanoBananaNodeData; if (data.model && !data.selectedModel) { - const displayName = data.model === "nano-banana" ? "Nano Banana" : "Nano Banana Pro"; + const displayName = MODEL_DISPLAY_NAMES[data.model] || data.model; return { ...node, data: {