import { useState } from 'react' import { Loader2, AlertCircle, Check, Moon, Sun, Eye, EyeOff, ShieldCheck } from 'lucide-react' import { useSettings, useUpdateSettings, useAvailableModels, useAvailableProviders } from '../hooks/useProjects' import { useTheme, THEMES } from '../hooks/useTheme' import type { ProviderInfo } from '../lib/types' import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Switch } from '@/components/ui/switch' import { Label } from '@/components/ui/label' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' interface SettingsModalProps { isOpen: boolean onClose: () => void } const PROVIDER_INFO_TEXT: Record = { claude: 'Default provider. Uses your Claude CLI credentials.', kimi: 'Get an API key at kimi.com', glm: 'Get an API key at open.bigmodel.cn', ollama: 'Run models locally. Install from ollama.com', custom: 'Connect to any OpenAI-compatible API endpoint.', } export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { const { data: settings, isLoading, isError, refetch } = useSettings() const { data: modelsData } = useAvailableModels() const { data: providersData } = useAvailableProviders() const updateSettings = useUpdateSettings() const { theme, setTheme, darkMode, toggleDarkMode } = useTheme() const [showAuthToken, setShowAuthToken] = useState(false) const [authTokenInput, setAuthTokenInput] = useState('') const [customModelInput, setCustomModelInput] = useState('') const [customBaseUrlInput, setCustomBaseUrlInput] = useState('') const handleYoloToggle = () => { if (settings && !updateSettings.isPending) { updateSettings.mutate({ yolo_mode: !settings.yolo_mode }) } } const handleModelChange = (modelId: string) => { if (!updateSettings.isPending) { updateSettings.mutate({ api_model: modelId }) } } const handleTestingRatioChange = (ratio: number) => { if (!updateSettings.isPending) { updateSettings.mutate({ testing_agent_ratio: ratio }) } } const handleBatchSizeChange = (size: number) => { if (!updateSettings.isPending) { updateSettings.mutate({ batch_size: size }) } } const handleProviderChange = (providerId: string) => { if (!updateSettings.isPending) { updateSettings.mutate({ api_provider: providerId }) // Reset local state setAuthTokenInput('') setShowAuthToken(false) setCustomModelInput('') setCustomBaseUrlInput('') } } const handleSaveAuthToken = () => { if (authTokenInput.trim() && !updateSettings.isPending) { updateSettings.mutate({ api_auth_token: authTokenInput.trim() }) setAuthTokenInput('') setShowAuthToken(false) } } const handleSaveCustomBaseUrl = () => { if (customBaseUrlInput.trim() && !updateSettings.isPending) { updateSettings.mutate({ api_base_url: customBaseUrlInput.trim() }) } } const handleSaveCustomModel = () => { if (customModelInput.trim() && !updateSettings.isPending) { updateSettings.mutate({ api_model: customModelInput.trim() }) setCustomModelInput('') } } const providers = providersData?.providers ?? [] const models = modelsData?.models ?? [] const isSaving = updateSettings.isPending const currentProvider = settings?.api_provider ?? 'claude' const currentProviderInfo: ProviderInfo | undefined = providers.find(p => p.id === currentProvider) const isAlternativeProvider = currentProvider !== 'claude' const showAuthField = isAlternativeProvider && currentProviderInfo?.requires_auth const showBaseUrlField = currentProvider === 'custom' const showCustomModelInput = currentProvider === 'custom' || currentProvider === 'ollama' return ( !open && onClose()}> Settings {isSaving && } {/* Loading State */} {isLoading && (
Loading settings...
)} {/* Error State */} {isError && ( Failed to load settings )} {/* Settings Content */} {settings && !isLoading && (
{/* Theme Selection */}
{THEMES.map((themeOption) => ( ))}
{/* Dark Mode Toggle */}

Switch between light and dark appearance


{/* API Provider Selection */}
{providers.map((provider) => ( ))}

{PROVIDER_INFO_TEXT[currentProvider] ?? ''}

{/* Auth Token Field */} {showAuthField && (
{settings.api_has_auth_token && !authTokenInput && (
Configured
)} {(!settings.api_has_auth_token || authTokenInput) && (
setAuthTokenInput(e.target.value)} placeholder="Enter API key..." className="w-full py-1.5 px-3 pe-9 text-sm border rounded-md bg-background" />
)}
)} {/* Custom Base URL Field */} {showBaseUrlField && (
setCustomBaseUrlInput(e.target.value)} placeholder="https://api.example.com/v1" className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background" />
)}
{/* Model Selection */}
{models.length > 0 && (
{models.map((model) => ( ))}
)} {/* Custom model input for Ollama/Custom */} {showCustomModelInput && (
setCustomModelInput(e.target.value)} placeholder="Custom model name..." className="flex-1 py-1.5 px-3 text-sm border rounded-md bg-background" onKeyDown={(e) => e.key === 'Enter' && handleSaveCustomModel()} />
)}

{/* YOLO Mode Toggle */}

Skip testing for rapid prototyping

{/* Headless Browser Toggle */}

Run browser without visible window (saves CPU)

updateSettings.mutate({ playwright_headless: !settings.playwright_headless })} disabled={isSaving} />
{/* Regression Agents */}

Number of regression testing agents (0 = disabled)

{[0, 1, 2, 3].map((ratio) => ( ))}
{/* Features per Agent */}

Number of features assigned to each coding agent

{[1, 2, 3].map((size) => ( ))}
{/* Update Error */} {updateSettings.isError && ( Failed to save settings. Please try again. )}
)}
) }