Browse Source
Phase 01: Provider Infrastructure - 2 plans created - 5 total tasks defined - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>handoff-20260429-1057
2 changed files with 388 additions and 0 deletions
@ -0,0 +1,188 @@ |
|||||
|
--- |
||||
|
phase: 01-provider-infrastructure |
||||
|
plan: 01 |
||||
|
type: execute |
||||
|
--- |
||||
|
|
||||
|
<objective> |
||||
|
Add provider settings UI enabling users to configure Replicate and fal.ai API keys in ProjectSetupModal. |
||||
|
|
||||
|
Purpose: Establish the foundation for multi-provider support by allowing users to enable/disable providers and securely store API keys. |
||||
|
Output: Provider settings tab in ProjectSetupModal with API key inputs for Replicate and fal.ai, persisted to localStorage. |
||||
|
</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 |
||||
|
|
||||
|
**Key files:** |
||||
|
@src/types/index.ts |
||||
|
@src/store/workflowStore.ts |
||||
|
@src/components/ProjectSetupModal.tsx |
||||
|
|
||||
|
**Established patterns:** |
||||
|
- Types defined in `src/types/index.ts` with PascalCase interfaces |
||||
|
- Settings persisted via localStorage with `node-banana-*` key prefix |
||||
|
- Zustand store pattern in `workflowStore.ts` with inline actions |
||||
|
- Modal UI pattern from existing ProjectSetupModal (neutral-* Tailwind colors, consistent spacing) |
||||
|
|
||||
|
**Constraints:** |
||||
|
- Gemini remains default, Replicate/fal.ai are optional add-ons |
||||
|
- API keys stored in localStorage (client-side only, no server storage) |
||||
|
- Follow existing naming conventions: `node-banana-provider-settings` |
||||
|
</context> |
||||
|
|
||||
|
<tasks> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 1: Add provider types to types/index.ts</name> |
||||
|
<files>src/types/index.ts</files> |
||||
|
<action> |
||||
|
Add provider-related types at the end of the file: |
||||
|
|
||||
|
1. `ProviderType` union: `"gemini" | "replicate" | "fal"` |
||||
|
2. `ProviderConfig` interface with fields: |
||||
|
- `id: ProviderType` |
||||
|
- `name: string` (display name) |
||||
|
- `enabled: boolean` |
||||
|
- `apiKey: string | null` |
||||
|
- `apiKeyEnvVar?: string` (for Gemini which uses env var) |
||||
|
3. `ProviderSettings` interface with field: |
||||
|
- `providers: Record<ProviderType, ProviderConfig>` |
||||
|
|
||||
|
Keep existing types unchanged. Add new types after `NodeGroup` interface. |
||||
|
</action> |
||||
|
<verify>npm run build passes with no TypeScript errors</verify> |
||||
|
<done>ProviderType, ProviderConfig, and ProviderSettings types exist and are exported</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 2: Add provider settings state to workflowStore</name> |
||||
|
<files>src/store/workflowStore.ts</files> |
||||
|
<action> |
||||
|
Add provider settings management to the Zustand store: |
||||
|
|
||||
|
1. Add storage key constant: `PROVIDER_SETTINGS_KEY = "node-banana-provider-settings"` |
||||
|
|
||||
|
2. Add helper functions (after existing localStorage helpers around line 310): |
||||
|
- `getProviderSettings(): ProviderSettings` - loads from localStorage, returns defaults if not found |
||||
|
- `saveProviderSettings(settings: ProviderSettings): void` - saves to localStorage |
||||
|
|
||||
|
3. Default provider settings (Gemini enabled by default with env var, others disabled): |
||||
|
```typescript |
||||
|
const defaultProviderSettings: ProviderSettings = { |
||||
|
providers: { |
||||
|
gemini: { id: "gemini", name: "Google Gemini", enabled: true, apiKey: null, apiKeyEnvVar: "GEMINI_API_KEY" }, |
||||
|
replicate: { id: "replicate", name: "Replicate", enabled: false, apiKey: null }, |
||||
|
fal: { id: "fal", name: "fal.ai", enabled: false, apiKey: null }, |
||||
|
} |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
4. Add to store state interface: |
||||
|
- `providerSettings: ProviderSettings` |
||||
|
|
||||
|
5. Add store actions: |
||||
|
- `updateProviderSettings: (settings: ProviderSettings) => void` - updates state and saves to localStorage |
||||
|
- `updateProviderApiKey: (providerId: ProviderType, apiKey: string | null) => void` - updates single provider key |
||||
|
- `toggleProvider: (providerId: ProviderType, enabled: boolean) => void` - enables/disables provider |
||||
|
|
||||
|
6. Initialize `providerSettings` in create() using `getProviderSettings()` |
||||
|
|
||||
|
Import ProviderType and ProviderSettings from "@/types". |
||||
|
</action> |
||||
|
<verify>npm run build passes, store exports new actions</verify> |
||||
|
<done>Provider settings state and actions exist in workflowStore, persisted to localStorage</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 3: Add Providers tab to ProjectSetupModal</name> |
||||
|
<files>src/components/ProjectSetupModal.tsx</files> |
||||
|
<action> |
||||
|
Extend ProjectSetupModal with a tabbed interface and Providers settings: |
||||
|
|
||||
|
1. Add tab state: `const [activeTab, setActiveTab] = useState<"project" | "providers">("project")` |
||||
|
|
||||
|
2. Import provider state from store: |
||||
|
```typescript |
||||
|
const { providerSettings, updateProviderApiKey, toggleProvider } = useWorkflowStore(); |
||||
|
``` |
||||
|
|
||||
|
3. Add local state for provider form (to avoid saving on every keystroke): |
||||
|
```typescript |
||||
|
const [localProviders, setLocalProviders] = useState(providerSettings); |
||||
|
``` |
||||
|
Sync localProviders when modal opens (in useEffect). |
||||
|
|
||||
|
4. Add tab bar below the title: |
||||
|
```tsx |
||||
|
<div className="flex gap-4 border-b border-neutral-700 mb-4"> |
||||
|
<button |
||||
|
onClick={() => setActiveTab("project")} |
||||
|
className={`pb-2 text-sm ${activeTab === "project" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} |
||||
|
> |
||||
|
Project |
||||
|
</button> |
||||
|
<button |
||||
|
onClick={() => setActiveTab("providers")} |
||||
|
className={`pb-2 text-sm ${activeTab === "providers" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} |
||||
|
> |
||||
|
Providers |
||||
|
</button> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
5. Wrap existing form content in `{activeTab === "project" && (...)}` block. |
||||
|
|
||||
|
6. Add Providers tab content `{activeTab === "providers" && (...)}`: |
||||
|
- Show each provider (Gemini, Replicate, fal.ai) in a card-like section |
||||
|
- For Gemini: Show "Configured via GEMINI_API_KEY environment variable" (no input field, just info) |
||||
|
- For Replicate and fal.ai: |
||||
|
- Toggle switch to enable/disable |
||||
|
- API key input field (type="password", only shown when enabled) |
||||
|
- Show/hide password toggle button |
||||
|
- Use consistent neutral-* Tailwind classes matching existing UI |
||||
|
|
||||
|
7. Update handleSave to also save provider settings when on providers tab: |
||||
|
- Call `updateProviderApiKey` for each changed provider |
||||
|
- Call `toggleProvider` for each changed enabled state |
||||
|
|
||||
|
8. When in "providers" tab and user clicks Save, save provider settings and close modal (no directory validation needed). |
||||
|
|
||||
|
Follow existing modal patterns: neutral-800 background, neutral-700 borders, consistent button styling. |
||||
|
</action> |
||||
|
<verify>npm run dev, open Project Settings, switch to Providers tab, toggle Replicate on, enter API key, save, reload page, verify settings persisted</verify> |
||||
|
<done>ProjectSetupModal has Providers tab with API key inputs for Replicate and fal.ai, settings persist across page reloads</done> |
||||
|
</task> |
||||
|
|
||||
|
</tasks> |
||||
|
|
||||
|
<verification> |
||||
|
Before declaring plan complete: |
||||
|
- [ ] `npm run build` succeeds without errors |
||||
|
- [ ] `npm run lint` passes |
||||
|
- [ ] Provider types exported from src/types/index.ts |
||||
|
- [ ] Provider settings load/save via localStorage |
||||
|
- [ ] ProjectSetupModal shows Project and Providers tabs |
||||
|
- [ ] Replicate/fal.ai can be enabled with API keys |
||||
|
- [ ] Settings persist after page reload |
||||
|
</verification> |
||||
|
|
||||
|
<success_criteria> |
||||
|
|
||||
|
- All tasks completed |
||||
|
- All verification checks pass |
||||
|
- No TypeScript errors |
||||
|
- Users can configure Replicate and fal.ai API keys in settings |
||||
|
- Settings persist to localStorage |
||||
|
</success_criteria> |
||||
|
|
||||
|
<output> |
||||
|
After completion, create `.planning/phases/01-provider-infrastructure/01-01-SUMMARY.md` |
||||
|
</output> |
||||
@ -0,0 +1,200 @@ |
|||||
|
--- |
||||
|
phase: 01-provider-infrastructure |
||||
|
plan: 02 |
||||
|
type: execute |
||||
|
--- |
||||
|
|
||||
|
<objective> |
||||
|
Create provider abstraction layer defining how providers expose model discovery and generation capabilities. |
||||
|
|
||||
|
Purpose: Establish the interface contract that all providers (Gemini, Replicate, fal.ai) will implement, enabling uniform access to different AI services. |
||||
|
Output: Provider abstraction interfaces and types in src/lib/providers/ ready for implementation in Phase 2. |
||||
|
</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:** |
||||
|
Plan 01-01 added ProviderType, ProviderConfig, and ProviderSettings types. |
||||
|
|
||||
|
**Key files:** |
||||
|
@src/types/index.ts |
||||
|
@src/app/api/generate/route.ts |
||||
|
|
||||
|
**Established patterns:** |
||||
|
- Feature libraries in `src/lib/{feature}/` (see quickstart/) |
||||
|
- Types defined with PascalCase interfaces |
||||
|
- Prefer fetch over SDKs per PROJECT.md constraints |
||||
|
|
||||
|
**Constraints:** |
||||
|
- No hardcoded schemas - model parameters fetched at runtime |
||||
|
- Fetch over SDKs for bundle size |
||||
|
- Dynamic model discovery for future-proofing |
||||
|
</context> |
||||
|
|
||||
|
<tasks> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 1: Create provider abstraction interfaces</name> |
||||
|
<files>src/lib/providers/types.ts</files> |
||||
|
<action> |
||||
|
Create `src/lib/providers/` directory and `types.ts` with provider abstractions: |
||||
|
|
||||
|
1. `ProviderModel` interface - represents a model from any provider: |
||||
|
```typescript |
||||
|
interface ProviderModel { |
||||
|
id: string; // Provider-specific model ID (e.g., "stability-ai/sdxl") |
||||
|
name: string; // Display name |
||||
|
description: string | null; // Model description |
||||
|
provider: ProviderType; // Which provider this is from |
||||
|
capabilities: ModelCapability[]; // What it can do |
||||
|
coverImage?: string; // Thumbnail URL |
||||
|
pricing?: { // Optional pricing info |
||||
|
type: "per-run" | "per-second"; |
||||
|
amount: number; |
||||
|
currency: string; |
||||
|
}; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. `ModelCapability` union: |
||||
|
```typescript |
||||
|
type ModelCapability = "text-to-image" | "image-to-image" | "text-to-video" | "image-to-video"; |
||||
|
``` |
||||
|
|
||||
|
3. `GenerationInput` interface - unified input for all providers: |
||||
|
```typescript |
||||
|
interface GenerationInput { |
||||
|
model: ProviderModel; |
||||
|
prompt: string; |
||||
|
images?: string[]; // Base64 data URLs or HTTP URLs |
||||
|
parameters?: Record<string, unknown>; // Model-specific params |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
4. `GenerationOutput` interface: |
||||
|
```typescript |
||||
|
interface GenerationOutput { |
||||
|
success: boolean; |
||||
|
outputs?: Array<{ |
||||
|
type: "image" | "video"; |
||||
|
data: string; // Base64 data URL |
||||
|
url?: string; // Original URL if applicable |
||||
|
}>; |
||||
|
error?: string; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
5. `ProviderInterface` - contract all providers implement: |
||||
|
```typescript |
||||
|
interface ProviderInterface { |
||||
|
id: ProviderType; |
||||
|
name: string; |
||||
|
|
||||
|
// Model discovery |
||||
|
listModels(): Promise<ProviderModel[]>; |
||||
|
getModel(modelId: string): Promise<ProviderModel | null>; |
||||
|
searchModels(query: string): Promise<ProviderModel[]>; |
||||
|
|
||||
|
// Generation |
||||
|
generate(input: GenerationInput): Promise<GenerationOutput>; |
||||
|
|
||||
|
// Utilities |
||||
|
isConfigured(): boolean; // Check if API key is set |
||||
|
getApiKey(): string | null; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Import ProviderType from "@/types". |
||||
|
Export all interfaces and types. |
||||
|
</action> |
||||
|
<verify>npm run build passes, types can be imported from "@/lib/providers/types"</verify> |
||||
|
<done>Provider abstraction interfaces defined in src/lib/providers/types.ts</done> |
||||
|
</task> |
||||
|
|
||||
|
<task type="auto"> |
||||
|
<name>Task 2: Create provider registry and factory</name> |
||||
|
<files>src/lib/providers/index.ts</files> |
||||
|
<action> |
||||
|
Create provider registry that will be populated by specific provider implementations in Phase 2: |
||||
|
|
||||
|
1. Create `src/lib/providers/index.ts` with: |
||||
|
|
||||
|
2. Export all types from types.ts: |
||||
|
```typescript |
||||
|
export * from "./types"; |
||||
|
``` |
||||
|
|
||||
|
3. Provider registry (empty for now, filled in Phase 2): |
||||
|
```typescript |
||||
|
import { ProviderType } from "@/types"; |
||||
|
import { ProviderInterface } from "./types"; |
||||
|
|
||||
|
// Registry populated by provider implementations |
||||
|
const providerRegistry: Partial<Record<ProviderType, ProviderInterface>> = {}; |
||||
|
|
||||
|
export function registerProvider(provider: ProviderInterface): void { |
||||
|
providerRegistry[provider.id] = provider; |
||||
|
} |
||||
|
|
||||
|
export function getProvider(id: ProviderType): ProviderInterface | undefined { |
||||
|
return providerRegistry[id]; |
||||
|
} |
||||
|
|
||||
|
export function getConfiguredProviders(): ProviderInterface[] { |
||||
|
return Object.values(providerRegistry).filter( |
||||
|
(p): p is ProviderInterface => p !== undefined && p.isConfigured() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function getAllProviders(): ProviderInterface[] { |
||||
|
return Object.values(providerRegistry).filter( |
||||
|
(p): p is ProviderInterface => p !== undefined |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
4. Add JSDoc comments explaining: |
||||
|
- Registry will be populated when provider modules are imported |
||||
|
- Phase 2 will add Replicate and fal.ai implementations |
||||
|
- Gemini implementation is special-cased in existing generate route for now |
||||
|
|
||||
|
This creates the foundation without implementing actual providers yet. |
||||
|
</action> |
||||
|
<verify>npm run build passes, registry functions can be imported from "@/lib/providers"</verify> |
||||
|
<done>Provider registry and factory functions exist in src/lib/providers/index.ts</done> |
||||
|
</task> |
||||
|
|
||||
|
</tasks> |
||||
|
|
||||
|
<verification> |
||||
|
Before declaring plan complete: |
||||
|
- [ ] `npm run build` succeeds without errors |
||||
|
- [ ] `npm run lint` passes |
||||
|
- [ ] src/lib/providers/types.ts exists with all interfaces |
||||
|
- [ ] src/lib/providers/index.ts exports types and registry functions |
||||
|
- [ ] Types can be imported: `import { ProviderModel, ProviderInterface } from "@/lib/providers"` |
||||
|
</verification> |
||||
|
|
||||
|
<success_criteria> |
||||
|
|
||||
|
- All tasks completed |
||||
|
- All verification checks pass |
||||
|
- No TypeScript errors |
||||
|
- Provider abstraction layer ready for Phase 2 implementations |
||||
|
- Phase 1 complete |
||||
|
</success_criteria> |
||||
|
|
||||
|
<output> |
||||
|
After completion, create `.planning/phases/01-provider-infrastructure/01-02-SUMMARY.md` |
||||
|
|
||||
|
Include in summary: |
||||
|
- Phase 1 complete, ready for Phase 2: Model Discovery |
||||
|
</output> |
||||
Loading…
Reference in new issue