From aee0ba4cc5d4acddaaef61d8114e55c485c29a78 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sat, 20 Dec 2025 13:38:38 +0100 Subject: [PATCH] feat: add customizable phase configuration in app settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow users to customize the model and thinking level for each phase (Spec Creation, Planning, Coding, QA Review) when using the Auto profile. These settings are persisted in app settings and used as defaults when creating new tasks. Changes: - Add customPhaseModels and customPhaseThinking to AppSettings type - Update AgentProfileSettings to show editable phase configuration when Auto profile is selected - Update TaskCreationWizard to initialize from custom settings - Phase config can still be overridden per-task in task creation wizard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../main/ipc-handlers/settings-handlers.ts | 22 ++- .../components/TaskCreationWizard.tsx | 23 +-- .../settings/AgentProfileSettings.tsx | 180 +++++++++++++++++- auto-claude-ui/src/shared/types/settings.ts | 5 + 4 files changed, 206 insertions(+), 24 deletions(-) diff --git a/auto-claude-ui/src/main/ipc-handlers/settings-handlers.ts b/auto-claude-ui/src/main/ipc-handlers/settings-handlers.ts index 30f06a7d..25b1a763 100644 --- a/auto-claude-ui/src/main/ipc-handlers/settings-handlers.ts +++ b/auto-claude-ui/src/main/ipc-handlers/settings-handlers.ts @@ -94,7 +94,7 @@ export function registerSettingsHandlers( ipcMain.handle( IPC_CHANNELS.SETTINGS_GET, async (): Promise> => { - let settings = { ...DEFAULT_APP_SETTINGS }; + let settings: AppSettings = { ...DEFAULT_APP_SETTINGS }; let needsSave = false; if (existsSync(settingsPath)) { @@ -106,12 +106,15 @@ export function registerSettingsHandlers( } } - // Migration: Reset agent profile to 'auto' for existing users (one-time) - // This ensures all users get the optimized 'auto' profile as the default - const settingsAny = settings as Record; - if (!settingsAny._migratedAgentProfileToAuto) { - settings.selectedAgentProfile = 'auto'; - settingsAny._migratedAgentProfileToAuto = true; + // Migration: Set agent profile to 'auto' for users who haven't made a selection (one-time) + // This ensures new users get the optimized 'auto' profile as the default + // while preserving existing user preferences + if (!settings._migratedAgentProfileToAuto) { + // Only set 'auto' if user hasn't made a selection yet + if (!settings.selectedAgentProfile) { + settings.selectedAgentProfile = 'auto'; + } + settings._migratedAgentProfileToAuto = true; needsSave = true; } @@ -127,8 +130,9 @@ export function registerSettingsHandlers( if (needsSave) { try { writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); - } catch { - // Ignore write errors during migration + } catch (error) { + console.error('[SETTINGS_GET] Failed to persist migration:', error); + // Continue anyway - settings will be migrated in-memory for this session } } diff --git a/auto-claude-ui/src/renderer/components/TaskCreationWizard.tsx b/auto-claude-ui/src/renderer/components/TaskCreationWizard.tsx index 6aaa57d9..58b4c449 100644 --- a/auto-claude-ui/src/renderer/components/TaskCreationWizard.tsx +++ b/auto-claude-ui/src/renderer/components/TaskCreationWizard.tsx @@ -102,11 +102,12 @@ export function TaskCreationWizard({ const [model, setModel] = useState(selectedProfile.model); const [thinkingLevel, setThinkingLevel] = useState(selectedProfile.thinkingLevel); // Auto profile - per-phase configuration + // Use custom settings from app settings if available, otherwise fall back to defaults const [phaseModels, setPhaseModels] = useState( - selectedProfile.phaseModels || DEFAULT_PHASE_MODELS + settings.customPhaseModels || selectedProfile.phaseModels || DEFAULT_PHASE_MODELS ); const [phaseThinking, setPhaseThinking] = useState( - selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING + settings.customPhaseThinking || selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING ); // Image attachments @@ -143,8 +144,8 @@ export function TaskCreationWizard({ setProfileId(draft.profileId || settings.selectedAgentProfile || 'auto'); setModel(draft.model || selectedProfile.model); setThinkingLevel(draft.thinkingLevel || selectedProfile.thinkingLevel); - setPhaseModels(draft.phaseModels || selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); - setPhaseThinking(draft.phaseThinking || selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); + setPhaseModels(draft.phaseModels || settings.customPhaseModels || selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); + setPhaseThinking(draft.phaseThinking || settings.customPhaseThinking || selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); setImages(draft.images); setReferencedFiles(draft.referencedFiles ?? []); setRequireReviewBeforeCoding(draft.requireReviewBeforeCoding ?? false); @@ -159,15 +160,15 @@ export function TaskCreationWizard({ } // Note: Referenced Files section is always visible, no need to expand } else { - // No draft - initialize from selected profile + // No draft - initialize from selected profile and custom settings setProfileId(settings.selectedAgentProfile || 'auto'); setModel(selectedProfile.model); setThinkingLevel(selectedProfile.thinkingLevel); - setPhaseModels(selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); - setPhaseThinking(selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); + setPhaseModels(settings.customPhaseModels || selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); + setPhaseThinking(settings.customPhaseThinking || selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); } } - }, [open, projectId, settings.selectedAgentProfile, selectedProfile.model, selectedProfile.thinkingLevel]); + }, [open, projectId, settings.selectedAgentProfile, settings.customPhaseModels, settings.customPhaseThinking, selectedProfile.model, selectedProfile.thinkingLevel]); // Fetch branches and project default branch when dialog opens useEffect(() => { @@ -542,12 +543,12 @@ export function TaskCreationWizard({ setPriority(''); setComplexity(''); setImpact(''); - // Reset to selected profile defaults + // Reset to selected profile defaults and custom settings setProfileId(settings.selectedAgentProfile || 'auto'); setModel(selectedProfile.model); setThinkingLevel(selectedProfile.thinkingLevel); - setPhaseModels(selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); - setPhaseThinking(selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); + setPhaseModels(settings.customPhaseModels || selectedProfile.phaseModels || DEFAULT_PHASE_MODELS); + setPhaseThinking(settings.customPhaseThinking || selectedProfile.phaseThinking || DEFAULT_PHASE_THINKING); setImages([]); setReferencedFiles([]); setRequireReviewBeforeCoding(false); diff --git a/auto-claude-ui/src/renderer/components/settings/AgentProfileSettings.tsx b/auto-claude-ui/src/renderer/components/settings/AgentProfileSettings.tsx index eb119815..08a51f32 100644 --- a/auto-claude-ui/src/renderer/components/settings/AgentProfileSettings.tsx +++ b/auto-claude-ui/src/renderer/components/settings/AgentProfileSettings.tsx @@ -1,9 +1,25 @@ -import { Brain, Scale, Zap, Check, Sparkles } from 'lucide-react'; +import { useState } from 'react'; +import { Brain, Scale, Zap, Check, Sparkles, ChevronDown, ChevronUp, RotateCcw } from 'lucide-react'; import { cn } from '../../lib/utils'; -import { DEFAULT_AGENT_PROFILES, AVAILABLE_MODELS, THINKING_LEVELS } from '../../../shared/constants'; +import { + DEFAULT_AGENT_PROFILES, + AVAILABLE_MODELS, + THINKING_LEVELS, + DEFAULT_PHASE_MODELS, + DEFAULT_PHASE_THINKING +} from '../../../shared/constants'; import { useSettingsStore, saveSettings } from '../../stores/settings-store'; import { SettingsSection } from './SettingsSection'; -import type { AgentProfile } from '../../../shared/types/settings'; +import { Label } from '../ui/label'; +import { Button } from '../ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '../ui/select'; +import type { AgentProfile, PhaseModelConfig, PhaseThinkingConfig, ModelTypeShort, ThinkingLevel } from '../../../shared/types/settings'; /** * Icon mapping for agent profile icons @@ -15,6 +31,13 @@ const iconMap: Record = { Sparkles }; +const PHASE_LABELS: Record = { + spec: { label: 'Spec Creation', description: 'Discovery, requirements, context gathering' }, + planning: { label: 'Planning', description: 'Implementation planning and architecture' }, + coding: { label: 'Coding', description: 'Actual code implementation' }, + qa: { label: 'QA Review', description: 'Quality assurance and validation' } +}; + /** * Agent Profile Settings component * Displays preset agent profiles for quick model/thinking level configuration @@ -23,9 +46,40 @@ const iconMap: Record = { export function AgentProfileSettings() { const settings = useSettingsStore((state) => state.settings); const selectedProfileId = settings.selectedAgentProfile || 'auto'; + const [showPhaseConfig, setShowPhaseConfig] = useState(selectedProfileId === 'auto'); + + // Get current phase config from settings or defaults + const currentPhaseModels: PhaseModelConfig = settings.customPhaseModels || DEFAULT_PHASE_MODELS; + const currentPhaseThinking: PhaseThinkingConfig = settings.customPhaseThinking || DEFAULT_PHASE_THINKING; const handleSelectProfile = async (profileId: string) => { - await saveSettings({ selectedAgentProfile: profileId }); + const success = await saveSettings({ selectedAgentProfile: profileId }); + if (!success) { + // Log error for debugging - in future could show user toast notification + console.error('Failed to save agent profile selection'); + return; + } + // Auto-expand phase config when Auto profile is selected + if (profileId === 'auto') { + setShowPhaseConfig(true); + } + }; + + const handlePhaseModelChange = async (phase: keyof PhaseModelConfig, value: ModelTypeShort) => { + const newPhaseModels = { ...currentPhaseModels, [phase]: value }; + await saveSettings({ customPhaseModels: newPhaseModels }); + }; + + const handlePhaseThinkingChange = async (phase: keyof PhaseThinkingConfig, value: ThinkingLevel) => { + const newPhaseThinking = { ...currentPhaseThinking, [phase]: value }; + await saveSettings({ customPhaseThinking: newPhaseThinking }); + }; + + const handleResetToDefaults = async () => { + await saveSettings({ + customPhaseModels: DEFAULT_PHASE_MODELS, + customPhaseThinking: DEFAULT_PHASE_THINKING + }); }; /** @@ -44,6 +98,18 @@ export function AgentProfileSettings() { return level?.label || thinkingValue; }; + /** + * Check if current config differs from defaults + */ + const hasCustomConfig = (): boolean => { + const phases: Array = ['spec', 'planning', 'coding', 'qa']; + return phases.some( + phase => + currentPhaseModels[phase] !== DEFAULT_PHASE_MODELS[phase] || + currentPhaseThinking[phase] !== DEFAULT_PHASE_THINKING[phase] + ); + }; + /** * Render a single profile card */ @@ -126,6 +192,112 @@ export function AgentProfileSettings() {
{DEFAULT_AGENT_PROFILES.map(renderProfileCard)}
+ + {/* Phase Configuration (only for Auto profile) */} + {selectedProfileId === 'auto' && ( +
+ {/* Header - Collapsible */} + + + {/* Phase Configuration Content */} + {showPhaseConfig && ( +
+ {/* Reset button */} + {hasCustomConfig() && ( +
+ +
+ )} + + {/* Phase Configuration Grid */} +
+ {(Object.keys(PHASE_LABELS) as Array).map((phase) => ( +
+
+ + + {PHASE_LABELS[phase].description} + +
+
+ {/* Model Select */} +
+ + +
+ {/* Thinking Level Select */} +
+ + +
+
+
+ ))} +
+ + {/* Info note */} +

+ These settings will be used as defaults when creating new tasks with the Auto profile. + You can override them per-task in the task creation wizard. +

+
+ )} +
+ )} ); diff --git a/auto-claude-ui/src/shared/types/settings.ts b/auto-claude-ui/src/shared/types/settings.ts index 0ff41b41..2f06f569 100644 --- a/auto-claude-ui/src/shared/types/settings.ts +++ b/auto-claude-ui/src/shared/types/settings.ts @@ -82,10 +82,15 @@ export interface AppSettings { onboardingCompleted?: boolean; // Selected agent profile for preset model/thinking configurations selectedAgentProfile?: string; + // Custom phase configuration for Auto profile (overrides defaults) + customPhaseModels?: PhaseModelConfig; + customPhaseThinking?: PhaseThinkingConfig; // Changelog preferences changelogFormat?: ChangelogFormat; changelogAudience?: ChangelogAudience; changelogEmojiLevel?: ChangelogEmojiLevel; + // Migration flags (internal use) + _migratedAgentProfileToAuto?: boolean; } // Auto-Claude Source Environment Configuration (for auto-claude repo .env)