From 2ba114931ce5dec4c44a5271a3d8d21d132a35d0 Mon Sep 17 00:00:00 2001 From: Kacper Date: Tue, 30 Dec 2025 02:04:36 +0100 Subject: [PATCH] feat(ui): Add Phase Models settings tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PhaseModelsSection with grouped phase configuration: - Quick Tasks: enhancement, file/image description - Validation Tasks: GitHub issue validation - Generation Tasks: spec, features, backlog, analysis - Add PhaseModelSelector component showing Claude + Cursor models - Add phaseModels state and actions to app-store - Add 'phase-models' navigation item with Workflow icon 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../ui/src/components/views/settings-view.tsx | 3 + .../views/settings-view/config/navigation.ts | 2 + .../settings-view/hooks/use-settings-view.ts | 1 + .../views/settings-view/phase-models/index.ts | 2 + .../phase-models/phase-model-selector.tsx | 109 +++++++++++++ .../phase-models/phase-models-section.tsx | 153 ++++++++++++++++++ apps/ui/src/store/app-store.ts | 56 ++++--- 7 files changed, 303 insertions(+), 23 deletions(-) create mode 100644 apps/ui/src/components/views/settings-view/phase-models/index.ts create mode 100644 apps/ui/src/components/views/settings-view/phase-models/phase-model-selector.tsx create mode 100644 apps/ui/src/components/views/settings-view/phase-models/phase-models-section.tsx diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index 7b3f3213..404d0172 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -9,6 +9,7 @@ import { DeleteProjectDialog } from './settings-view/components/delete-project-d import { SettingsNavigation } from './settings-view/components/settings-navigation'; import { ApiKeysSection } from './settings-view/api-keys/api-keys-section'; import { AIEnhancementSection } from './settings-view/ai-enhancement'; +import { PhaseModelsSection } from './settings-view/phase-models'; import { AppearanceSection } from './settings-view/appearance/appearance-section'; import { TerminalSection } from './settings-view/terminal/terminal-section'; import { AudioSection } from './settings-view/audio/audio-section'; @@ -88,6 +89,8 @@ export function SettingsView() { return ; case 'ai-enhancement': return ; + case 'phase-models': + return ; case 'appearance': return ( void; +} + +export function PhaseModelSelector({ + label, + description, + value, + onChange, +}: PhaseModelSelectorProps) { + const { enabledCursorModels } = useAppStore(); + + // Filter Cursor models to only show enabled ones + const availableCursorModels = CURSOR_MODELS.filter((model) => { + const cursorId = model.id.replace('cursor-', '') as CursorModelId; + return enabledCursorModels.includes(cursorId); + }); + + // Check if current value is a Claude model or Cursor model + const isClaudeModel = (v: string) => ['haiku', 'sonnet', 'opus'].includes(v); + + return ( +
+
+ {/* Label and Description */} +
+

{label}

+

{description}

+
+ + {/* Model Selection */} +
+ {/* Claude Models */} + {CLAUDE_MODELS.map((model) => { + const isActive = value === model.id; + return ( + + ); + })} + + {/* Divider if there are Cursor models */} + {availableCursorModels.length > 0 && ( +
+ )} + + {/* Cursor Models */} + {availableCursorModels.map((model) => { + const cursorId = model.id.replace('cursor-', '') as CursorModelId; + const isActive = value === cursorId; + return ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/ui/src/components/views/settings-view/phase-models/phase-models-section.tsx b/apps/ui/src/components/views/settings-view/phase-models/phase-models-section.tsx new file mode 100644 index 00000000..136f6fdf --- /dev/null +++ b/apps/ui/src/components/views/settings-view/phase-models/phase-models-section.tsx @@ -0,0 +1,153 @@ +import { Workflow, RotateCcw } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useAppStore } from '@/store/app-store'; +import { Button } from '@/components/ui/button'; +import { PhaseModelSelector } from './phase-model-selector'; +import type { PhaseModelKey } from '@automaker/types'; + +interface PhaseConfig { + key: PhaseModelKey; + label: string; + description: string; +} + +const QUICK_TASKS: PhaseConfig[] = [ + { + key: 'enhancementModel', + label: 'Feature Enhancement', + description: 'Improves feature names and descriptions', + }, + { + key: 'fileDescriptionModel', + label: 'File Descriptions', + description: 'Generates descriptions for context files', + }, + { + key: 'imageDescriptionModel', + label: 'Image Descriptions', + description: 'Analyzes and describes context images', + }, +]; + +const VALIDATION_TASKS: PhaseConfig[] = [ + { + key: 'validationModel', + label: 'GitHub Issue Validation', + description: 'Validates and improves GitHub issues', + }, +]; + +const GENERATION_TASKS: PhaseConfig[] = [ + { + key: 'specGenerationModel', + label: 'App Specification', + description: 'Generates full application specifications', + }, + { + key: 'featureGenerationModel', + label: 'Feature Generation', + description: 'Creates features from specifications', + }, + { + key: 'backlogPlanningModel', + label: 'Backlog Planning', + description: 'Reorganizes and prioritizes backlog', + }, + { + key: 'projectAnalysisModel', + label: 'Project Analysis', + description: 'Analyzes project structure for suggestions', + }, +]; + +function PhaseGroup({ + title, + subtitle, + phases, +}: { + title: string; + subtitle: string; + phases: PhaseConfig[]; +}) { + const { phaseModels, setPhaseModel } = useAppStore(); + + return ( +
+
+

{title}

+

{subtitle}

+
+
+ {phases.map((phase) => ( + setPhaseModel(phase.key, model)} + /> + ))} +
+
+ ); +} + +export function PhaseModelsSection() { + const { resetPhaseModels } = useAppStore(); + + return ( +
+ {/* Header */} +
+
+
+
+ +
+
+

Phase Models

+

+ Configure which AI model to use for each application task +

+
+
+ +
+
+ + {/* Content */} +
+ {/* Quick Tasks */} + + + {/* Validation Tasks */} + + + {/* Generation Tasks */} + +
+
+ ); +} diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index b1771f71..56e95da4 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -8,8 +8,10 @@ import type { PlanningMode, AIProfile, CursorModelId, + PhaseModelConfig, + PhaseModelKey, } from '@automaker/types'; -import { getAllCursorModelIds } from '@automaker/types'; +import { getAllCursorModelIds, DEFAULT_PHASE_MODELS } from '@automaker/types'; // Re-export ThemeMode for convenience export type { ThemeMode }; @@ -480,6 +482,9 @@ export interface AppState { // Validation Model Settings validationModel: AgentModel; // Model used for GitHub issue validation (default: opus) + // Phase Model Settings - per-phase AI model configuration + phaseModels: PhaseModelConfig; + // Cursor CLI Settings (global) enabledCursorModels: CursorModelId[]; // Which Cursor models are available in feature modal cursorDefaultModel: CursorModelId; // Default Cursor model selection @@ -760,6 +765,11 @@ export interface AppActions { // Validation Model actions setValidationModel: (model: AgentModel) => void; + // Phase Model actions + setPhaseModel: (phase: PhaseModelKey, model: AgentModel | CursorModelId) => void; + setPhaseModels: (models: Partial) => void; + resetPhaseModels: () => void; + // Cursor CLI Settings actions setEnabledCursorModels: (models: CursorModelId[]) => void; setCursorDefaultModel: (model: CursorModelId) => void; @@ -904,32 +914,14 @@ const DEFAULT_AI_PROFILES: AIProfile[] = [ }, // Cursor profiles { - id: 'profile-cursor-auto', - name: 'Cursor Auto', - description: 'Let Cursor choose the best model automatically.', + id: 'profile-cursor-refactoring', + name: 'Cursor Refactoring', + description: 'Cursor Composer 1 for refactoring tasks.', provider: 'cursor', - cursorModel: 'auto', + cursorModel: 'composer-1', isBuiltIn: true, icon: 'Sparkles', }, - { - id: 'profile-cursor-fast', - name: 'Cursor Fast', - description: 'Quick responses with GPT-4o Mini via Cursor.', - provider: 'cursor', - cursorModel: 'gpt-4o-mini', - isBuiltIn: true, - icon: 'Zap', - }, - { - id: 'profile-cursor-thinking', - name: 'Cursor Thinking', - description: 'Claude Sonnet 4 with extended thinking via Cursor for complex tasks.', - provider: 'cursor', - cursorModel: 'claude-sonnet-4-thinking', - isBuiltIn: true, - icon: 'Brain', - }, ]; const initialState: AppState = { @@ -968,6 +960,7 @@ const initialState: AppState = { muteDoneSound: false, // Default to sound enabled (not muted) enhancementModel: 'sonnet', // Default to sonnet for feature enhancement validationModel: 'opus', // Default to opus for GitHub issue validation + phaseModels: DEFAULT_PHASE_MODELS, // Phase-specific model configuration enabledCursorModels: getAllCursorModelIds(), // All Cursor models enabled by default cursorDefaultModel: 'auto', // Default to auto selection autoLoadClaudeMd: false, // Default to disabled (user must opt-in) @@ -1596,6 +1589,23 @@ export const useAppStore = create()( // Validation Model actions setValidationModel: (model) => set({ validationModel: model }), + // Phase Model actions + setPhaseModel: (phase, model) => + set((state) => ({ + phaseModels: { + ...state.phaseModels, + [phase]: model, + }, + })), + setPhaseModels: (models) => + set((state) => ({ + phaseModels: { + ...state.phaseModels, + ...models, + }, + })), + resetPhaseModels: () => set({ phaseModels: DEFAULT_PHASE_MODELS }), + // Cursor CLI Settings actions setEnabledCursorModels: (models) => set({ enabledCursorModels: models }), setCursorDefaultModel: (model) => set({ cursorDefaultModel: model }),