import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Brain, AlertTriangle } from 'lucide-react'; import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon'; import { cn } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import { useSetupStore } from '@/store/setup-store'; import { getModelProvider } from '@automaker/types'; import type { ModelProvider, CursorModelId } from '@automaker/types'; import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants'; import { useEffect } from 'react'; import { Spinner } from '@/components/ui/spinner'; interface ModelSelectorProps { selectedModel: string; // Can be ModelAlias or "cursor-{id}" onModelSelect: (model: string) => void; testIdPrefix?: string; } export function ModelSelector({ selectedModel, onModelSelect, testIdPrefix = 'model-select', }: ModelSelectorProps) { const { enabledCursorModels, cursorDefaultModel, codexModels, codexModelsLoading, codexModelsError, fetchCodexModels, disabledProviders, } = useAppStore(); const { cursorCliStatus, codexCliStatus } = useSetupStore(); const selectedProvider = getModelProvider(selectedModel); // Check if Cursor CLI is available const isCursorAvailable = cursorCliStatus?.installed && cursorCliStatus?.auth?.authenticated; // Check if Codex CLI is available // @ts-expect-error - codexCliStatus uses CliStatus type but should use CodexCliStatus which has auth const isCodexAvailable = codexCliStatus?.installed && codexCliStatus?.auth?.authenticated; // Fetch Codex models on mount useEffect(() => { if (isCodexAvailable && codexModels.length === 0 && !codexModelsLoading) { fetchCodexModels(); } }, [isCodexAvailable, codexModels.length, codexModelsLoading, fetchCodexModels]); // Transform codex models from store to ModelOption format const dynamicCodexModels: ModelOption[] = codexModels.map((model) => { // Infer badge based on tier let badge: string | undefined; if (model.tier === 'premium') badge = 'Premium'; else if (model.tier === 'basic') badge = 'Speed'; else if (model.tier === 'standard') badge = 'Balanced'; return { id: model.id, label: model.label, description: model.description, badge, provider: 'codex' as ModelProvider, hasThinking: model.hasThinking, }; }); // Filter Cursor models based on enabled models from global settings const filteredCursorModels = CURSOR_MODELS.filter((model) => { // enabledCursorModels stores CursorModelIds which may or may not have "cursor-" prefix // (e.g., 'auto', 'sonnet-4.5' without prefix, but 'cursor-gpt-5.2' with prefix) // CURSOR_MODELS always has the "cursor-" prefix added in model-constants.ts // Check both the full ID (for GPT models) and the unprefixed version (for non-GPT models) const unprefixedId = model.id.startsWith('cursor-') ? model.id.slice(7) : model.id; return ( enabledCursorModels.includes(model.id as CursorModelId) || enabledCursorModels.includes(unprefixedId as CursorModelId) ); }); const handleProviderChange = (provider: ModelProvider) => { if (provider === 'cursor' && selectedProvider !== 'cursor') { // Switch to Cursor's default model (from global settings) // cursorDefaultModel is now canonical (e.g., 'cursor-auto'), so use directly onModelSelect(cursorDefaultModel); } else if (provider === 'codex' && selectedProvider !== 'codex') { // Switch to Codex's default model (use isDefault flag from dynamic models) const defaultModel = codexModels.find((m) => m.isDefault); const defaultModelId = defaultModel?.id || codexModels[0]?.id || 'codex-gpt-5.2-codex'; onModelSelect(defaultModelId); } else if (provider === 'claude' && selectedProvider !== 'claude') { // Switch to Claude's default model (canonical format) onModelSelect('claude-sonnet'); } }; // Check which providers are disabled const isClaudeDisabled = disabledProviders.includes('claude'); const isCursorDisabled = disabledProviders.includes('cursor'); const isCodexDisabled = disabledProviders.includes('codex'); // Count available providers const availableProviders = [ !isClaudeDisabled && 'claude', !isCursorDisabled && 'cursor', !isCodexDisabled && 'codex', ].filter(Boolean) as ModelProvider[]; return (