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