Browse Source

docs(03): create phase plan

Phase 03: Generate Node Refactor
- 3 plans created
- 9 total tasks defined
- Ready for execution
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
1bf6520d86
  1. 143
      .planning/phases/03-generate-node-refactor/03-01-PLAN.md
  2. 165
      .planning/phases/03-generate-node-refactor/03-02-PLAN.md
  3. 154
      .planning/phases/03-generate-node-refactor/03-03-PLAN.md

143
.planning/phases/03-generate-node-refactor/03-01-PLAN.md

@ -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>

165
.planning/phases/03-generate-node-refactor/03-02-PLAN.md

@ -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>

154
.planning/phases/03-generate-node-refactor/03-03-PLAN.md

@ -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…
Cancel
Save