import { useState } from 'react'; import { useAppStore } from '@/store/app-store'; import { Button } from '@/components/ui/button'; import { Workflow, RotateCcw, Globe, Check, Replace, Sparkles } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { Project } from '@/lib/electron'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; import { ProjectBulkReplaceDialog } from './project-bulk-replace-dialog'; import type { PhaseModelKey, PhaseModelEntry } from '@automaker/types'; import { DEFAULT_PHASE_MODELS, DEFAULT_GLOBAL_SETTINGS } from '@automaker/types'; interface ProjectModelsSectionProps { project: Project; } 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', }, { key: 'commitMessageModel', label: 'Commit Messages', description: 'Generates git commit messages from diffs', }, ]; 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', }, { key: 'suggestionsModel', label: 'AI Suggestions', description: 'Model for feature, refactoring, security, and performance suggestions', }, ]; const MEMORY_TASKS: PhaseConfig[] = [ { key: 'memoryExtractionModel', label: 'Memory Extraction', description: 'Extracts learnings from completed agent sessions', }, ]; const ALL_PHASES = [...QUICK_TASKS, ...VALIDATION_TASKS, ...GENERATION_TASKS, ...MEMORY_TASKS]; /** * Default feature model override section for per-project settings. */ function FeatureDefaultModelOverrideSection({ project }: { project: Project }) { const { defaultFeatureModel: globalDefaultFeatureModel, setProjectDefaultFeatureModel, claudeCompatibleProviders, } = useAppStore(); const globalValue: PhaseModelEntry = globalDefaultFeatureModel ?? DEFAULT_GLOBAL_SETTINGS.defaultFeatureModel; const projectOverride = project.defaultFeatureModel; const hasOverride = !!projectOverride; const effectiveValue = projectOverride || globalValue; // Get display name for a model const getModelDisplayName = (entry: PhaseModelEntry): string => { if (entry.providerId) { const provider = (claudeCompatibleProviders || []).find((p) => p.id === entry.providerId); if (provider) { const model = provider.models?.find((m) => m.id === entry.model); if (model) { return `${model.displayName} (${provider.name})`; } } } // Default to model ID for built-in models (both short aliases and canonical IDs) const modelMap: Record = { haiku: 'Claude Haiku', sonnet: 'Claude Sonnet', opus: 'Claude Opus', 'claude-haiku': 'Claude Haiku', 'claude-sonnet': 'Claude Sonnet', 'claude-opus': 'Claude Opus', }; return modelMap[entry.model] || entry.model; }; const handleClearOverride = () => { setProjectDefaultFeatureModel(project.id, null); }; const handleSetOverride = (entry: PhaseModelEntry) => { setProjectDefaultFeatureModel(project.id, entry); }; return (

Feature Defaults

Default model for new feature cards in this project

Default Feature Model

{hasOverride ? ( Override ) : ( Global )}

Model and thinking level used when creating new feature cards

{hasOverride && (

Using: {getModelDisplayName(effectiveValue)}

)} {!hasOverride && (

Using global: {getModelDisplayName(globalValue)}

)}
{hasOverride && ( )}
); } function PhaseOverrideItem({ phase, project, globalValue, projectOverride, }: { phase: PhaseConfig; project: Project; globalValue: PhaseModelEntry; projectOverride?: PhaseModelEntry; }) { const { setProjectPhaseModelOverride, claudeCompatibleProviders } = useAppStore(); const hasOverride = !!projectOverride; const effectiveValue = projectOverride || globalValue; // Get display name for a model const getModelDisplayName = (entry: PhaseModelEntry): string => { if (entry.providerId) { const provider = (claudeCompatibleProviders || []).find((p) => p.id === entry.providerId); if (provider) { const model = provider.models?.find((m) => m.id === entry.model); if (model) { return `${model.displayName} (${provider.name})`; } } } // Default to model ID for built-in models (both short aliases and canonical IDs) const modelMap: Record = { haiku: 'Claude Haiku', sonnet: 'Claude Sonnet', opus: 'Claude Opus', 'claude-haiku': 'Claude Haiku', 'claude-sonnet': 'Claude Sonnet', 'claude-opus': 'Claude Opus', }; return modelMap[entry.model] || entry.model; }; const handleClearOverride = () => { setProjectPhaseModelOverride(project.id, phase.key, null); }; const handleSetOverride = (entry: PhaseModelEntry) => { setProjectPhaseModelOverride(project.id, phase.key, entry); }; return (

{phase.label}

{hasOverride ? ( Override ) : ( Global )}

{phase.description}

{hasOverride && (

Using: {getModelDisplayName(effectiveValue)}

)} {!hasOverride && (

Using global: {getModelDisplayName(globalValue)}

)}
{hasOverride && ( )}
); } function PhaseGroup({ title, subtitle, phases, project, }: { title: string; subtitle: string; phases: PhaseConfig[]; project: Project; }) { const { phaseModels } = useAppStore(); const projectOverrides = project.phaseModelOverrides || {}; return (

{title}

{subtitle}

{phases.map((phase) => ( ))}
); } export function ProjectModelsSection({ project }: ProjectModelsSectionProps) { const { clearAllProjectPhaseModelOverrides, disabledProviders, claudeCompatibleProviders } = useAppStore(); const [showBulkReplace, setShowBulkReplace] = useState(false); // Count how many overrides are set (including defaultFeatureModel) const phaseOverrideCount = Object.keys(project.phaseModelOverrides || {}).length; const hasDefaultFeatureModelOverride = !!project.defaultFeatureModel; const overrideCount = phaseOverrideCount + (hasDefaultFeatureModelOverride ? 1 : 0); // Check if Claude is available const isClaudeDisabled = disabledProviders.includes('claude'); // Check if there are any enabled ClaudeCompatibleProviders const hasEnabledProviders = claudeCompatibleProviders && claudeCompatibleProviders.some((p) => p.enabled !== false); if (isClaudeDisabled) { return (

Claude not configured

Enable Claude in global settings to configure per-project model overrides.

); } const handleClearAll = () => { clearAllProjectPhaseModelOverrides(project.id); }; return (
{/* Header */}

Model Overrides

Override AI models for this project only

{hasEnabledProviders && ( )} {overrideCount > 0 && ( )}
{/* Bulk Replace Dialog */} {/* Info Banner */}
Per-Phase Overrides
Override specific phases to use different models for this project. Phases without overrides use the global settings.
{/* Content */}
{/* Feature Defaults */} {/* Quick Tasks */} {/* Validation Tasks */} {/* Generation Tasks */} {/* Memory Tasks */}
); }