- Keep existing ModelType for backward compatibility
export interface SelectedModel {
provider: ProviderType;
2. Update NanoBananaNodeData interface:
modelId: string;
- Add optional `selectedModel?: SelectedModel` field
displayName: string;
- Keep existing `model: ModelType` field for Gemini backward compatibility
}
```
3. Add "gemini" to ProviderType union if not already present (it should be the Gemini provider identifier)
Update NanoBananaNodeData to add optional selectedModel field (keep existing model field for Gemini backward compatibility).
WHY: NanoBananaNodeData needs to store either legacy Gemini model selection OR new multi-provider selection. The optional selectedModel field enables graceful migration.
Add "gemini" to ProviderType if not present.
</action>
</action>
<verify>npx tsc --noEmit passes</verify>
<verify>npx tsc --noEmit</verify>
<done>Types compile, SelectedModel interface exists, NanoBananaNodeData has selectedModel field</done>
<done>SelectedModel type exists, NanoBananaNodeData has selectedModel field, types compile</done>
</task>
</task>
<tasktype="auto">
<tasktype="auto">
<name>Task 2: Rename component to GenerateImageNode with model selector UI</name>
<name>Task 2: Create GenerateImageNode with provider/model selector</name>
- For gemini: hardcoded Nano Banana / Nano Banana Pro
- Provider dropdown (gemini, replicate, fal) - only show enabled providers
- For replicate/fal: fetch from /api/models?provider=X&capabilities=text-to-image,image-to-image
- Model dropdown (fetched from /api/models for selected provider)
5. Filter to image capabilities only (text-to-image, image-to-image)
- **Filter to image capabilities only**: text-to-image, image-to-image
6. Keep aspect ratio/resolution controls for Gemini, hide for others
- For Gemini, show existing hardcoded models (Nano Banana, Nano Banana Pro)
7. Update index.ts exports:
- For other providers, fetch models dynamically with capability filter
4. When provider/model changes:
- Update node data with selectedModel: { provider, modelId, displayName }
- For Gemini, also update legacy model field for backward compatibility
5. Keep existing aspect ratio/resolution controls for Gemini models
- Hide them for non-Gemini models (parameters will come from provider schema later)
6. Update index.ts to export GenerateImageNode and alias as NanoBananaNode:
```typescript
```typescript
export { GenerateImageNode } from "./GenerateImageNode";
export { GenerateImageNode } from "./GenerateImageNode";
export { GenerateImageNode as NanoBananaNode } from "./GenerateImageNode";
export { GenerateImageNode as NanoBananaNode } from "./GenerateImageNode";
```
```
WHY: The component rename reflects its image-specific purpose. Video generation will be a separate GenerateVideoNode in Phase 6. The alias maintains backward compatibility.
</action>
</action>
<verify>npm run build succeeds, component renders in browser</verify>
<verify>npm run build</verify>
<done>GenerateImageNode.tsx exists, shows provider selector, model dropdown shows only image-capable models</done>
<done>GenerateImageNode renders with provider/model dropdowns, shows only image models</done>
</task>
</task>
<tasktype="auto">
<tasktype="auto">
<name>Task 3: Update store and canvas references</name>
<name>Task 3: Update store and canvas references</name>
Add provider-specific execution logic to the generate API route for image generation.
Implement provider-specific image generation in API route.
Purpose: Enable the generate endpoint to route requests to the correct provider (Gemini, Replicate, fal.ai) based on the selected model, filtering to image outputs only.
Purpose: Route generation requests to correct provider (Gemini, Replicate, fal.ai) based on selected model.
Output: Updated /api/generate route that dispatches to provider-specific image generation logic.
Output: Working generate endpoint that dispatches to provider implementations.
Note: Video generation will use a separate endpoint in Phase 6.
</objective>
</objective>
<execution_context>
<execution_context>
@ -20,156 +18,82 @@ Note: Video generation will use a separate endpoint in Phase 6.
- Poll GET /predictions/{id} until status is "succeeded" or "failed"
- Convert output URL to base64 data URL
- Return in GenerationOutput format with type: "image"
4. Filter to image models only:
- Only accept models with text-to-image or image-to-image capabilities
- Return error if video model passed (video generation is Phase 6)
WHY: Replicate requires polling for async predictions. The generate() method encapsulates this complexity. Image-only filter keeps this endpoint focused.
</action>
</action>
<verify>TypeScript compiles, generate() method exists and doesn't throw immediately</verify>
<verify>TypeScript compiles</verify>
<done>Replicate provider has working generate() method that calls prediction API for image models</done>
<done>Replicate generate() calls prediction API and returns image</done>
</task>
</task>
<tasktype="auto">
<tasktype="auto">
<name>Task 2: Implement generate() method in fal.ai provider</name>
- Accept GenerationInput with model, prompt, images, parameters
1. POST to https://fal.run/{modelId} with { prompt, ...parameters }
- Call fal.ai's run endpoint: POST https://fal.run/{model.id}
2. Use "Key {apiKey}" auth header
- Include prompt and parameters in request body
3. Extract image URL from response, convert to base64
- fal.ai is synchronous (waits for result)
4. Return GenerationOutput with type: "image"
2. Handle auth:
- Use "Key {apiKey}" header format (already established)
- API key optional but rate limited without it
3. Parse response:
- Extract image URL from response
- Convert to base64 data URL for consistency
- Handle images array output format
4. Filter to image models only:
- Only accept models with text-to-image or image-to-image capabilities
- Return error if video model passed (video generation is Phase 6)
WHY: fal.ai's synchronous API is simpler than Replicate. The generate() method normalizes the response format. Image-only filter keeps this endpoint focused.