// @ts-nocheck 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 type { ModelAlias } from '@/store/app-store'; import { useAppStore } from '@/store/app-store'; import { useSetupStore } from '@/store/setup-store'; import { getModelProvider, PROVIDER_PREFIXES, stripProviderPrefix } from '@automaker/types'; import type { ModelProvider } 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 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 any) || enabledCursorModels.includes(unprefixedId as any) ); }); 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 (
{/* Provider Selection */} {availableProviders.length > 1 && (
{!isClaudeDisabled && ( )} {!isCursorDisabled && ( )} {!isCodexDisabled && ( )}
)} {/* Claude Models */} {selectedProvider === 'claude' && !isClaudeDisabled && (
Native SDK
{CLAUDE_MODELS.map((option) => { const isSelected = selectedModel === option.id; const shortName = option.label.replace('Claude ', ''); return ( ); })}
)} {/* Cursor Models */} {selectedProvider === 'cursor' && !isCursorDisabled && (
{/* Warning when Cursor CLI is not available */} {!isCursorAvailable && (
Cursor CLI is not installed or authenticated. Configure it in Settings → AI Providers.
)}
CLI
{filteredCursorModels.length === 0 ? (
No Cursor models enabled. Enable models in Settings → AI Providers.
) : ( filteredCursorModels.map((option) => { const isSelected = selectedModel === option.id; return ( ); }) )}
)} {/* Codex Models */} {selectedProvider === 'codex' && !isCodexDisabled && (
{/* Warning when Codex CLI is not available */} {!isCodexAvailable && (
Codex CLI is not installed or authenticated. Configure it in Settings → AI Providers.
)}
CLI
{/* Loading state */} {codexModelsLoading && dynamicCodexModels.length === 0 && (
Loading models...
)} {/* Error state */} {codexModelsError && !codexModelsLoading && (
Failed to load Codex models
)} {/* Model list */} {!codexModelsLoading && !codexModelsError && dynamicCodexModels.length === 0 && (
No Codex models available
)} {!codexModelsLoading && dynamicCodexModels.length > 0 && (
{dynamicCodexModels.map((option) => { const isSelected = selectedModel === option.id; return ( ); })}
)}
)}
); }