Browse Source
Phase 03: Generate Node Refactor - 3 plans created - 9 total tasks defined - Ready for executionhandoff-20260429-1057
3 changed files with 462 additions and 0 deletions
@ -0,0 +1,143 @@ |
|||||
|
--- |
||||
|
phase: 03-generate-node-refactor |
||||
|
plan: 01 |
||||
|
type: execute |
||||
|
--- |
||||
|
|
||||
|
<objective> |
||||
|
Rename NanoBanana node to Generate and add multi-provider model selector. |
||||
|
|
||||
|
Purpose: Transform the hardcoded Gemini-only node into a generic generation node that can use any provider's models. |
||||
|
Output: Generate node component with provider/model dropdown, updated types, store functions renamed. |
||||
|
</objective> |
||||
|
|
||||
|
<execution_context> |
||||
|
~/.claude/get-shit-done/workflows/execute-phase.md |
||||
|
~/.claude/get-shit-done/templates/summary.md |
||||
|
</execution_context> |
||||
|
|
||||
|
<context> |
||||
|
@.planning/PROJECT.md |
||||
|
@.planning/ROADMAP.md |
||||
|
@.planning/STATE.md |
||||
|
|
||||
|
# Prior phase context: |
||||
|
@.planning/phases/01-provider-infrastructure/01-02-SUMMARY.md |
||||
|
@.planning/phases/02-model-discovery/02-03-SUMMARY.md |
||||
|
|
||||
|
# Key source files: |
||||
|
@src/components/nodes/NanoBananaNode.tsx |
||||
|
@src/types/index.ts |
||||
|
@src/store/workflowStore.ts |
||||
|
@src/lib/providers/types.ts |
||||
|
|
||||
|
**Tech stack available:** Zustand, React, ProviderModel interface, /api/models endpoint |
||||
|
**Established patterns:** Provider self-registration, localStorage settings persistence |
||||
|
**Constraining decisions:** |
||||
|
- Keep nanoBanana node type internally for backward compatibility |
||||
|
- Gemini models use internal names (nano-banana, nano-banana-pro) |
||||
|
</context> |
||||
|
|
||||
|
<tasks> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 1: Update types for multi-provider model selection</name> |
||||
|
<files>src/types/index.ts</files> |
||||
|
<action> |
||||
|
1. Add new types for selected model: |
||||
|
- `SelectedModel` interface with { provider: ProviderType, modelId: string, displayName: string } |
||||
|
- Keep existing ModelType for backward compatibility |
||||
|
|
||||
|
2. Update NanoBananaNodeData interface: |
||||
|
- Add optional `selectedModel?: SelectedModel` field |
||||
|
- 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) |
||||
|
|
||||
|
WHY: NanoBananaNodeData needs to store either legacy Gemini model selection OR new multi-provider selection. The optional selectedModel field enables graceful migration. |
||||
|
</action> |
||||
|
<verify>npx tsc --noEmit passes</verify> |
||||
|
<done>Types compile, SelectedModel interface exists, NanoBananaNodeData has selectedModel field</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 2: Rename component to GenerateNode with model selector UI</name> |
||||
|
<files>src/components/nodes/NanoBananaNode.tsx, src/components/nodes/index.ts</files> |
||||
|
<action> |
||||
|
1. Rename file from NanoBananaNode.tsx to GenerateNode.tsx |
||||
|
|
||||
|
2. Rename component from NanoBananaNode to GenerateNode (keep function export name) |
||||
|
|
||||
|
3. Add model selector UI that shows: |
||||
|
- Provider dropdown (gemini, replicate, fal) - only show enabled providers |
||||
|
- Model dropdown (fetched from /api/models for selected provider) |
||||
|
- For Gemini, show existing hardcoded models (Nano Banana, Nano Banana Pro) |
||||
|
- For other providers, fetch models dynamically |
||||
|
|
||||
|
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 GenerateNode and alias it as NanoBananaNode for backward compatibility: |
||||
|
```typescript |
||||
|
export { GenerateNode } from "./GenerateNode"; |
||||
|
export { GenerateNode as NanoBananaNode } from "./GenerateNode"; |
||||
|
``` |
||||
|
|
||||
|
WHY: The component rename reflects its new multi-provider purpose. The alias maintains backward compatibility with existing imports. |
||||
|
</action> |
||||
|
<verify>npm run build succeeds, component renders in browser</verify> |
||||
|
<done>GenerateNode.tsx exists, shows provider selector, model dropdown fetches from API for non-Gemini providers</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 3: Update store and canvas references</name> |
||||
|
<files>src/store/workflowStore.ts, src/components/WorkflowCanvas.tsx</files> |
||||
|
<action> |
||||
|
1. In workflowStore.ts: |
||||
|
- Rename saveNanoBananaDefaults to saveGenerateDefaults (keep old name as alias) |
||||
|
- Rename loadNanoBananaDefaults to loadGenerateDefaults (keep old name as alias) |
||||
|
- Update localStorage key comment but keep actual key for migration |
||||
|
- Update createDefaultNodeData for "nanoBanana" case to include default selectedModel for Gemini |
||||
|
|
||||
|
2. In WorkflowCanvas.tsx: |
||||
|
- Update nodeTypes registration to use GenerateNode |
||||
|
- Keep "nanoBanana" as the type key (internal type name unchanged) |
||||
|
- Update minimap color mapping if it references "nanoBanana" |
||||
|
|
||||
|
3. In any file using NanoBananaNode import: |
||||
|
- Update import to use GenerateNode |
||||
|
- Files: ConnectionDropMenu.tsx, FloatingActionBar.tsx, etc. |
||||
|
|
||||
|
WHY: Internal references update to new names while keeping the node type string "nanoBanana" for workflow file compatibility. |
||||
|
</action> |
||||
|
<verify>npm run build succeeds, existing workflows load correctly</verify> |
||||
|
<done>All imports updated, store functions renamed with aliases, workflows still load</done> |
||||
|
</task> |
||||
|
|
||||
|
</tasks> |
||||
|
|
||||
|
<verification> |
||||
|
Before declaring plan complete: |
||||
|
- [ ] `npm run build` succeeds without errors |
||||
|
- [ ] `npx tsc --noEmit` passes type checking |
||||
|
- [ ] GenerateNode renders with provider dropdown |
||||
|
- [ ] Gemini models show in model selector |
||||
|
- [ ] Existing workflows with nanoBanana nodes still load |
||||
|
</verification> |
||||
|
|
||||
|
<success_criteria> |
||||
|
|
||||
|
- All tasks completed |
||||
|
- All verification checks pass |
||||
|
- GenerateNode component exists with multi-provider UI |
||||
|
- Types support both legacy Gemini and new multi-provider selection |
||||
|
- Backward compatibility maintained for existing workflows |
||||
|
</success_criteria> |
||||
|
|
||||
|
<output> |
||||
|
After completion, create `.planning/phases/03-generate-node-refactor/03-01-SUMMARY.md` |
||||
|
</output> |
||||
@ -0,0 +1,165 @@ |
|||||
|
--- |
||||
|
phase: 03-generate-node-refactor |
||||
|
plan: 02 |
||||
|
type: execute |
||||
|
--- |
||||
|
|
||||
|
<objective> |
||||
|
Add provider-specific execution logic to the generate API route. |
||||
|
|
||||
|
Purpose: Enable the generate endpoint to route requests to the correct provider (Gemini, Replicate, fal.ai) based on the selected model. |
||||
|
Output: Updated /api/generate route that dispatches to provider-specific generation logic. |
||||
|
</objective> |
||||
|
|
||||
|
<execution_context> |
||||
|
~/.claude/get-shit-done/workflows/execute-phase.md |
||||
|
~/.claude/get-shit-done/templates/summary.md |
||||
|
</execution_context> |
||||
|
|
||||
|
<context> |
||||
|
@.planning/PROJECT.md |
||||
|
@.planning/ROADMAP.md |
||||
|
@.planning/STATE.md |
||||
|
|
||||
|
# Prior plan context: |
||||
|
@.planning/phases/03-generate-node-refactor/03-01-SUMMARY.md |
||||
|
|
||||
|
# Key source files: |
||||
|
@src/app/api/generate/route.ts |
||||
|
@src/lib/providers/types.ts |
||||
|
@src/lib/providers/replicate.ts |
||||
|
@src/lib/providers/fal.ts |
||||
|
@src/lib/providers/index.ts |
||||
|
|
||||
|
**Tech stack available:** ProviderInterface with generate() method, provider registry |
||||
|
**Established patterns:** API routes return { success, data/error }, provider self-registration |
||||
|
**Constraining decisions:** |
||||
|
- Gemini generation logic already works, preserve it |
||||
|
- Provider implementations have generate() method defined in ProviderInterface |
||||
|
</context> |
||||
|
|
||||
|
<tasks> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 1: Implement generate() method in Replicate provider</name> |
||||
|
<files>src/lib/providers/replicate.ts</files> |
||||
|
<action> |
||||
|
1. Implement the generate() method that currently throws "not implemented": |
||||
|
- Accept GenerationInput with model, prompt, images, parameters |
||||
|
- Call Replicate's prediction API: POST https://api.replicate.com/v1/predictions |
||||
|
- Handle input format: { version: model.id, input: { prompt, image?, ...parameters } } |
||||
|
- Poll for completion (Replicate is async) or use webhook pattern |
||||
|
- Return GenerationOutput with image/video data |
||||
|
|
||||
|
2. Handle image input: |
||||
|
- If images provided, check if Replicate model expects URLs |
||||
|
- For now, skip image input (Phase 5 adds image URL server) |
||||
|
- Log warning if images provided but skipped |
||||
|
|
||||
|
3. Fetch output: |
||||
|
- Poll GET /predictions/{id} until status is "succeeded" or "failed" |
||||
|
- Convert output URL to base64 data URL |
||||
|
- Return in GenerationOutput format |
||||
|
|
||||
|
WHY: Replicate requires polling for async predictions. The generate() method encapsulates this complexity. |
||||
|
</action> |
||||
|
<verify>TypeScript compiles, generate() method exists and doesn't throw immediately</verify> |
||||
|
<done>Replicate provider has working generate() method that calls prediction API</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 2: Implement generate() method in fal.ai provider</name> |
||||
|
<files>src/lib/providers/fal.ts</files> |
||||
|
<action> |
||||
|
1. Implement the generate() method: |
||||
|
- Accept GenerationInput with model, prompt, images, parameters |
||||
|
- Call fal.ai's run endpoint: POST https://fal.run/{model.id} |
||||
|
- Include prompt and parameters in request body |
||||
|
- fal.ai is synchronous (waits for result) |
||||
|
|
||||
|
2. Handle auth: |
||||
|
- Use "Key {apiKey}" header format (already established) |
||||
|
- API key optional but rate limited without it |
||||
|
|
||||
|
3. Parse response: |
||||
|
- Extract image/video URL from response |
||||
|
- Convert to base64 data URL for consistency |
||||
|
- Handle different output formats (images array, video URL, etc.) |
||||
|
|
||||
|
WHY: fal.ai's synchronous API is simpler than Replicate. The generate() method normalizes the response format. |
||||
|
</action> |
||||
|
<verify>TypeScript compiles, generate() method exists</verify> |
||||
|
<done>fal.ai provider has working generate() method that calls fal.run endpoint</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 3: Update generate API route to dispatch by provider</name> |
||||
|
<files>src/app/api/generate/route.ts</files> |
||||
|
<action> |
||||
|
1. Update request type to accept either: |
||||
|
- Legacy format: { model: ModelType, prompt, images, aspectRatio, resolution, useGoogleSearch } |
||||
|
- New format: { selectedModel: SelectedModel, prompt, images, parameters } |
||||
|
|
||||
|
2. Add provider dispatch logic: |
||||
|
```typescript |
||||
|
if (body.selectedModel) { |
||||
|
// New multi-provider path |
||||
|
const { provider, modelId } = body.selectedModel; |
||||
|
if (provider === "gemini") { |
||||
|
// Use existing Gemini logic |
||||
|
} else { |
||||
|
// Get provider and call generate() |
||||
|
const providerImpl = getProvider(provider); |
||||
|
const model = await providerImpl.getModel(modelId); |
||||
|
const result = await providerImpl.generate({ |
||||
|
model, |
||||
|
prompt: body.prompt, |
||||
|
images: body.images, |
||||
|
parameters: body.parameters |
||||
|
}); |
||||
|
return NextResponse.json(result); |
||||
|
} |
||||
|
} else { |
||||
|
// Legacy Gemini path (existing code) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
3. Extract Gemini-specific logic into helper function for clarity |
||||
|
|
||||
|
4. Ensure API key handling: |
||||
|
- For Gemini: use GEMINI_API_KEY from env |
||||
|
- For Replicate/fal: get from provider.getApiKey() (localStorage via client header) |
||||
|
|
||||
|
5. Add client headers for API keys: |
||||
|
- Accept X-Replicate-API-Key and X-Fal-API-Key headers |
||||
|
- Pass to provider implementations |
||||
|
|
||||
|
WHY: The route becomes a dispatcher that preserves existing Gemini behavior while enabling new providers. |
||||
|
</action> |
||||
|
<verify>npm run build succeeds, existing Gemini generation still works</verify> |
||||
|
<done>Generate route dispatches to correct provider, Gemini still works as before</done> |
||||
|
</task> |
||||
|
|
||||
|
</tasks> |
||||
|
|
||||
|
<verification> |
||||
|
Before declaring plan complete: |
||||
|
- [ ] `npm run build` succeeds without errors |
||||
|
- [ ] Existing Gemini generation works (test with existing workflow) |
||||
|
- [ ] Replicate provider generate() method implemented |
||||
|
- [ ] fal.ai provider generate() method implemented |
||||
|
- [ ] API route accepts both legacy and new request formats |
||||
|
</verification> |
||||
|
|
||||
|
<success_criteria> |
||||
|
|
||||
|
- All tasks completed |
||||
|
- All verification checks pass |
||||
|
- Generate API routes to correct provider based on selectedModel |
||||
|
- Gemini generation unchanged for backward compatibility |
||||
|
- Replicate and fal.ai providers have generate() implementations |
||||
|
</success_criteria> |
||||
|
|
||||
|
<output> |
||||
|
After completion, create `.planning/phases/03-generate-node-refactor/03-02-SUMMARY.md` |
||||
|
</output> |
||||
@ -0,0 +1,154 @@ |
|||||
|
--- |
||||
|
phase: 03-generate-node-refactor |
||||
|
plan: 03 |
||||
|
type: execute |
||||
|
--- |
||||
|
|
||||
|
<objective> |
||||
|
Ensure backward compatibility for existing workflows containing nanoBanana nodes. |
||||
|
|
||||
|
Purpose: Existing saved workflows must load and execute correctly without modification. |
||||
|
Output: Migration logic, verified backward compatibility, updated workflow execution. |
||||
|
</objective> |
||||
|
|
||||
|
<execution_context> |
||||
|
~/.claude/get-shit-done/workflows/execute-phase.md |
||||
|
~/.claude/get-shit-done/templates/summary.md |
||||
|
</execution_context> |
||||
|
|
||||
|
<context> |
||||
|
@.planning/PROJECT.md |
||||
|
@.planning/ROADMAP.md |
||||
|
@.planning/STATE.md |
||||
|
|
||||
|
# Prior plan context: |
||||
|
@.planning/phases/03-generate-node-refactor/03-01-SUMMARY.md |
||||
|
@.planning/phases/03-generate-node-refactor/03-02-SUMMARY.md |
||||
|
|
||||
|
# Key source files: |
||||
|
@src/store/workflowStore.ts |
||||
|
@src/types/index.ts |
||||
|
@src/components/nodes/GenerateNode.tsx |
||||
|
|
||||
|
**Tech stack available:** Zustand, workflow JSON persistence |
||||
|
**Established patterns:** Workflow load/save in workflowStore |
||||
|
**Constraining decisions:** |
||||
|
- Node type "nanoBanana" must remain unchanged in workflow files |
||||
|
- Legacy ModelType ("nano-banana", "nano-banana-pro") must still work |
||||
|
</context> |
||||
|
|
||||
|
<tasks> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 1: Add migration logic for loaded workflows</name> |
||||
|
<files>src/store/workflowStore.ts</files> |
||||
|
<action> |
||||
|
1. In loadWorkflow() or where workflow JSON is parsed, add migration: |
||||
|
- When loading a nanoBanana node without selectedModel field |
||||
|
- Automatically set selectedModel based on legacy model field: |
||||
|
```typescript |
||||
|
if (node.type === "nanoBanana" && node.data.model && !node.data.selectedModel) { |
||||
|
node.data.selectedModel = { |
||||
|
provider: "gemini", |
||||
|
modelId: node.data.model, |
||||
|
displayName: node.data.model === "nano-banana" ? "Nano Banana" : "Nano Banana Pro" |
||||
|
}; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Keep legacy model field synced for saves: |
||||
|
- When selectedModel is Gemini, also update the model field |
||||
|
- This ensures old versions can still read the workflow |
||||
|
|
||||
|
3. Add version field to workflow if not present: |
||||
|
- Set workflowVersion in saved JSON for future migrations |
||||
|
- Not strictly necessary but good practice |
||||
|
|
||||
|
WHY: Migration on load allows old workflows to work without manual editing. Syncing both fields maintains bidirectional compatibility. |
||||
|
</action> |
||||
|
<verify>Load an existing workflow file, check node data has both model and selectedModel</verify> |
||||
|
<done>Old workflows load with selectedModel populated, new saves include both fields</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 2: Update executeWorkflow for multi-provider generation</name> |
||||
|
<files>src/store/workflowStore.ts</files> |
||||
|
<action> |
||||
|
1. In executeWorkflow()'s nanoBanana case: |
||||
|
- Check if node has selectedModel |
||||
|
- If selectedModel exists and provider !== "gemini", pass API keys in headers: |
||||
|
```typescript |
||||
|
const headers: Record<string, string> = { |
||||
|
"Content-Type": "application/json" |
||||
|
}; |
||||
|
if (nodeData.selectedModel?.provider === "replicate") { |
||||
|
const settings = get().providerSettings; |
||||
|
const replicateConfig = settings.providers.find(p => p.id === "replicate"); |
||||
|
if (replicateConfig?.apiKey) { |
||||
|
headers["X-Replicate-API-Key"] = replicateConfig.apiKey; |
||||
|
} |
||||
|
} |
||||
|
// Similar for fal |
||||
|
``` |
||||
|
|
||||
|
2. Build request payload: |
||||
|
- Include selectedModel in request body for new providers |
||||
|
- Keep existing fields for Gemini backward compatibility |
||||
|
|
||||
|
3. Handle response format: |
||||
|
- Provider generate() returns GenerationOutput with outputs array |
||||
|
- Extract first image/video output as outputImage |
||||
|
- For video outputs, store separately (Phase 6 handles video display) |
||||
|
|
||||
|
WHY: The store's execution logic needs to pass provider API keys (stored client-side) to the server route. |
||||
|
</action> |
||||
|
<verify>Execute workflow with Gemini model, verify generation still works</verify> |
||||
|
<done>Workflow execution handles both legacy and new model selection formats</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 3: Update GenerateNode to handle legacy data</name> |
||||
|
<files>src/components/nodes/GenerateNode.tsx</files> |
||||
|
<action> |
||||
|
1. When component mounts or data changes: |
||||
|
- If model exists but not selectedModel, derive selectedModel from model |
||||
|
- Display the correct provider/model in dropdowns |
||||
|
|
||||
|
2. Ensure UI state reflects actual node data: |
||||
|
- Provider dropdown shows "gemini" for legacy nodes |
||||
|
- Model dropdown shows correct Gemini model name |
||||
|
|
||||
|
3. When saving defaults, save in both formats: |
||||
|
- saveGenerateDefaults should save both model and selectedModel |
||||
|
- Ensures new nodes get correct defaults |
||||
|
|
||||
|
WHY: The UI must correctly display legacy nodes that were migrated on load. |
||||
|
</action> |
||||
|
<verify>Open workflow with old nanoBanana node, verify dropdowns show correct selection</verify> |
||||
|
<done>GenerateNode displays correct provider/model for both legacy and new nodes</done> |
||||
|
</task> |
||||
|
|
||||
|
</tasks> |
||||
|
|
||||
|
<verification> |
||||
|
Before declaring plan complete: |
||||
|
- [ ] `npm run build` succeeds without errors |
||||
|
- [ ] Load existing workflow with nanoBanana nodes - nodes display correctly |
||||
|
- [ ] Execute existing workflow - Gemini generation works |
||||
|
- [ ] Create new Generate node - defaults are correct |
||||
|
- [ ] Save and reload workflow - data persists correctly |
||||
|
</verification> |
||||
|
|
||||
|
<success_criteria> |
||||
|
|
||||
|
- All tasks completed |
||||
|
- All verification checks pass |
||||
|
- Existing workflows load and execute without errors |
||||
|
- Migration logic populates selectedModel from legacy model field |
||||
|
- Both old and new format nodes work in same workflow |
||||
|
- Phase 3 complete |
||||
|
</success_criteria> |
||||
|
|
||||
|
<output> |
||||
|
After completion, create `.planning/phases/03-generate-node-refactor/03-03-SUMMARY.md` |
||||
|
</output> |
||||
Loading…
Reference in new issue