diff --git a/.planning/phases/28-node-defaults-ui/28-01-PLAN.md b/.planning/phases/28-node-defaults-ui/28-01-PLAN.md new file mode 100644 index 00000000..bda7ba2d --- /dev/null +++ b/.planning/phases/28-node-defaults-ui/28-01-PLAN.md @@ -0,0 +1,270 @@ +--- +phase: 28-node-defaults-ui +plan: 01 +type: execute +--- + + +Create a settings panel for configuring default node preferences per node type. + +Purpose: Users can set their preferred default models for GenerateImage, GenerateVideo, and LLM nodes, so keyboard shortcuts (Shift+G, Shift+L) and quick-add create nodes with their preferred settings. +Output: New "Node Defaults" tab in ProjectSetupModal with model selection UI for each node type. + + + +~/.claude/get-shit-done/workflows/execute-phase.md +~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/27-node-defaults-infrastructure/27-01-SUMMARY.md + +**Key files:** +@src/components/ProjectSetupModal.tsx +@src/store/utils/localStorage.ts +@src/types/nodes.ts +@src/components/modals/ModelSearchDialog.tsx +@src/components/nodes/LLMGenerateNode.tsx + +**Infrastructure from Phase 27:** +- `NodeDefaultsConfig` type: `{ generateImage?: GenerateImageNodeDefaults, generateVideo?: GenerateVideoNodeDefaults, llm?: LLMNodeDefaults }` +- `GenerateImageNodeDefaults`: `{ selectedModel?: { provider, modelId, displayName } }` +- `GenerateVideoNodeDefaults`: `{ selectedModel?: { provider, modelId, displayName } }` +- `LLMNodeDefaults`: `{ provider?: LLMProvider, model?: LLMModelType, temperature?: number, maxTokens?: number }` +- `loadNodeDefaults()` / `saveNodeDefaults()` in localStorage.ts +- `createDefaultNodeData()` already applies these defaults when creating nodes + +**Established patterns:** +- Tab-based UI in ProjectSetupModal (project, providers tabs) +- Local state pattern: load from store on open, save on "Save" button +- Model selection: ModelSearchDialog opens in callback mode via `onModelSelected` +- LLM settings: dropdowns for provider/model, slider for temperature + + + + + + Task 1: Add Node Defaults tab to ProjectSetupModal + src/components/ProjectSetupModal.tsx + +1. Add "Node Defaults" as a third tab after "Providers": + - Tab button: `setActiveTab("nodeDefaults")` + - activeTab state now `"project" | "providers" | "nodeDefaults"` + +2. Add local state for node defaults: + - `const [localNodeDefaults, setLocalNodeDefaults] = useState({});` + - Import `NodeDefaultsConfig, loadNodeDefaults, saveNodeDefaults` from localStorage.ts + - On modal open (in useEffect): `setLocalNodeDefaults(loadNodeDefaults());` + +3. Add state for model selection dialogs: + - `const [showImageModelDialog, setShowImageModelDialog] = useState(false);` + - `const [showVideoModelDialog, setShowVideoModelDialog] = useState(false);` + +4. Add handler for saving node defaults: + ```typescript + const handleSaveNodeDefaults = () => { + saveNodeDefaults(localNodeDefaults); + onClose(); + }; + ``` + +5. Update handleSave to route to handleSaveNodeDefaults when activeTab === "nodeDefaults" + +6. Create Node Defaults tab content (render when activeTab === "nodeDefaults"): + - Three sections: GenerateImage, GenerateVideo, LLM + - Each in a styled container matching provider tab style (p-3 bg-neutral-900 rounded-lg border border-neutral-700) + +**GenerateImage section:** +- Label: "Default Image Model" +- Display current selection or "System default (Gemini nano-banana-pro)" if not set +- "Select Model" button that opens ModelSearchDialog with onModelSelected callback +- "Clear" button (only shown if selection exists) to reset to undefined + +**GenerateVideo section:** +- Label: "Default Video Model" +- Display current selection or "None set (select model on first use)" if not set +- "Select Model" button that opens ModelSearchDialog with initialCapabilityFilter="video" +- "Clear" button to reset + +**LLM section:** +- Label: "Default LLM Settings" +- Provider dropdown (Google, OpenAI) - use same PROVIDERS array as LLMGenerateNode +- Model dropdown (filtered by provider) - use same MODELS object as LLMGenerateNode +- Temperature slider (0-2, step 0.1) with label showing current value +- Display "Using system defaults" text if no overrides set +- "Clear" button to reset all LLM defaults + +7. Handle model selection callbacks: + ```typescript + const handleImageModelSelected = (model: ProviderModel) => { + setLocalNodeDefaults(prev => ({ + ...prev, + generateImage: { + ...prev.generateImage, + selectedModel: { + provider: model.provider, + modelId: model.id, + displayName: model.name, + } + } + })); + setShowImageModelDialog(false); + }; + ``` + Similar for video model. + +8. Import ProviderModel from @/lib/providers/types + +Do NOT add new imports for ModelSearchDialog - it's already used in FloatingActionBar, so check if we need portal or can render inline. Use portal pattern matching existing modal usage. + + npm run build passes, modal opens with three tabs, Node Defaults tab shows three sections + ProjectSetupModal has Node Defaults tab with sections for GenerateImage, GenerateVideo, and LLM defaults + + + + Task 2: Wire up model selection dialogs and LLM controls + src/components/ProjectSetupModal.tsx + +1. Add ModelSearchDialog renders (conditionally when showImageModelDialog/showVideoModelDialog is true): + ```tsx + {showImageModelDialog && ( + setShowImageModelDialog(false)} + onModelSelected={handleImageModelSelected} + initialCapabilityFilter="image" + /> + )} + {showVideoModelDialog && ( + setShowVideoModelDialog(false)} + onModelSelected={handleVideoModelSelected} + initialCapabilityFilter="video" + /> + )} + ``` + +2. Implement LLM settings handlers: + ```typescript + const handleLLMProviderChange = (e: React.ChangeEvent) => { + const newProvider = e.target.value as LLMProvider; + const firstModelForProvider = MODELS[newProvider][0].value; + setLocalNodeDefaults(prev => ({ + ...prev, + llm: { + ...prev.llm, + provider: newProvider, + model: firstModelForProvider, + } + })); + }; + + const handleLLMModelChange = (e: React.ChangeEvent) => { + setLocalNodeDefaults(prev => ({ + ...prev, + llm: { ...prev.llm, model: e.target.value as LLMModelType } + })); + }; + + const handleLLMTemperatureChange = (e: React.ChangeEvent) => { + setLocalNodeDefaults(prev => ({ + ...prev, + llm: { ...prev.llm, temperature: parseFloat(e.target.value) } + })); + }; + ``` + +3. Implement clear handlers: + ```typescript + const handleClearImageDefaults = () => { + setLocalNodeDefaults(prev => { + const { generateImage, ...rest } = prev; + return rest; + }); + }; + // Similar for video and LLM + ``` + +4. Import PROVIDERS and MODELS arrays - copy from LLMGenerateNode.tsx (they're not exported). Place at top of file: + ```typescript + const LLM_PROVIDERS: { value: LLMProvider; label: string }[] = [ + { value: "google", label: "Google" }, + { value: "openai", label: "OpenAI" }, + ]; + + const LLM_MODELS: Record = { + google: [ + { value: "gemini-3-flash-preview", label: "Gemini 3 Flash" }, + { value: "gemini-2.5-flash", label: "Gemini 2.5 Flash" }, + { value: "gemini-3-pro-preview", label: "Gemini 3.0 Pro" }, + ], + openai: [ + { value: "gpt-4.1-mini", label: "GPT-4.1 Mini" }, + { value: "gpt-4.1-nano", label: "GPT-4.1 Nano" }, + ], + }; + ``` + +5. Import types: LLMProvider, LLMModelType, NodeDefaultsConfig from @/types + +6. Complete the UI for each section with proper styling: + - Use flex layout with justify-between for label and controls + - Model display shows provider icon + model name + - Buttons: "Select Model" (primary), "Clear" (text button, neutral-400) + + npm run build passes, can select models from dialogs, LLM dropdowns work, clear buttons reset to defaults + Model selection dialogs open and save selections, LLM controls update local state, clear buttons reset defaults + + + + Node Defaults tab in Project Settings with: + - GenerateImage section: shows selected model or "System default", Select Model and Clear buttons + - GenerateVideo section: shows selected model or "None set", Select Model and Clear buttons + - LLM section: provider dropdown, model dropdown, temperature slider, Clear button + - Changes persist to localStorage and affect new node creation via keyboard shortcuts + + 1. Run: npm run dev + 2. Open Project Settings (gear icon in header) + 3. Click "Node Defaults" tab (third tab) + 4. Verify three sections appear: Image Model, Video Model, LLM Settings + 5. Click "Select Model" for Image - verify ModelSearchDialog opens with image filter + 6. Select a model (e.g., fal.ai flux) - verify it shows in the section + 7. Click "Save" to close modal + 8. Press Shift+G to create GenerateImage node - verify it has the selected model + 9. Re-open settings, go to Node Defaults, click "Clear" on Image defaults + 10. Click "Save", press Shift+G - verify node uses system default (Gemini) + 11. Test LLM defaults: change provider to OpenAI, select GPT-4.1 Mini + 12. Save and press Shift+L - verify LLM node uses OpenAI provider + + Type "approved" to continue, or describe issues to fix + + + + + +Before declaring plan complete: +- [ ] `npm run build` succeeds without TypeScript errors +- [ ] ProjectSetupModal has three tabs: Project, Providers, Node Defaults +- [ ] Can select default image model from ModelSearchDialog +- [ ] Can select default video model from ModelSearchDialog +- [ ] Can configure LLM provider, model, and temperature +- [ ] Clear buttons reset each section to undefined/system defaults +- [ ] Settings persist across modal close/reopen +- [ ] New nodes created via keyboard shortcuts use stored defaults + + + + +- All tasks completed +- TypeScript builds without errors +- Node Defaults UI is functional and persists settings +- User verified functionality via checkpoint + + + +After completion, create `.planning/phases/28-node-defaults-ui/28-01-SUMMARY.md` +