import { useState, useEffect } from 'react' import { useSettingsStore } from '../stores/settingsStore' import { useProviderStore } from '../stores/providerStore' import { useUIStore } from '../stores/uiStore' import { Modal } from '../components/shared/Modal' import { Input } from '../components/shared/Input' import { Button } from '../components/shared/Button' import type { PermissionMode, EffortLevel, ModelInfo } from '../types/settings' import type { Provider, ProviderModel, UpdateProviderInput, ProviderTestResult } from '../types/provider' type SettingsTab = 'providers' | 'model' | 'permissions' | 'general' export function Settings() { const [activeTab, setActiveTab] = useState('providers') const setActiveView = useUIStore((s) => s.setActiveView) return (
{/* Header */}

Settings

{/* Tab navigation */}
setActiveTab('providers')} /> setActiveTab('model')} /> setActiveTab('permissions')} /> setActiveTab('general')} />
{/* Tab content */}
{activeTab === 'providers' && } {activeTab === 'model' && } {activeTab === 'permissions' && } {activeTab === 'general' && }
) } function TabButton({ icon, label, active, onClick }: { icon: string; label: string; active: boolean; onClick: () => void }) { return ( ) } // ─── Provider Settings ────────────────────────────────────── function ProviderSettings() { const { providers, isLoading, fetchProviders, deleteProvider, activateProvider, testProvider } = useProviderStore() const fetchSettings = useSettingsStore((s) => s.fetchAll) const [editingProvider, setEditingProvider] = useState(null) const [showCreateModal, setShowCreateModal] = useState(false) const [activatingProvider, setActivatingProvider] = useState(null) const [testResults, setTestResults] = useState>({}) useEffect(() => { fetchProviders() }, [fetchProviders]) const handleDelete = async (provider: Provider) => { if (provider.isActive) return if (!window.confirm(`Delete provider "${provider.name}"? This cannot be undone.`)) return try { await deleteProvider(provider.id) } catch (err) { console.error('Failed to delete provider:', err) } } const handleTest = async (provider: Provider) => { setTestResults((r) => ({ ...r, [provider.id]: { loading: true } })) try { const result = await testProvider(provider.id) setTestResults((r) => ({ ...r, [provider.id]: { loading: false, result } })) } catch { setTestResults((r) => ({ ...r, [provider.id]: { loading: false, result: { success: false, latencyMs: 0, error: 'Request failed' } } })) } } const handleActivate = async (providerId: string, modelId: string) => { await activateProvider(providerId, modelId) await fetchSettings() setActivatingProvider(null) } return (

Providers

Manage API providers for model access.

{isLoading && providers.length === 0 ? (
) : providers.length === 0 ? (
dns

No providers configured

) : (
{providers.map((provider) => { const test = testResults[provider.id] return (
{/* Status dot */} {/* Info */}
{provider.name} {provider.isActive && ( ACTIVE )}
{provider.baseUrl} · {provider.models.length} model{provider.models.length !== 1 ? 's' : ''}
{/* Test result inline */} {test && !test.loading && test.result && (
{test.result.success ? `Connected (${test.result.latencyMs}ms)` : `Failed: ${test.result.error}`}
)}
{/* Actions */}
{!provider.isActive && ( )} {!provider.isActive && ( )}
) })}
)} {/* Create Modal */} setShowCreateModal(false)} mode="create" /> {/* Edit Modal */} {editingProvider && ( setEditingProvider(null)} mode="edit" provider={editingProvider} /> )} {/* Activate — model picker */} {activatingProvider && ( setActivatingProvider(null)} title={`Activate ${activatingProvider.name}`} width={420}>

Select a model to use with this provider:

{activatingProvider.models.map((m) => ( ))}
)}
) } // ─── Provider Form Modal ────────────────────────────────────── type ProviderFormProps = { open: boolean onClose: () => void mode: 'create' | 'edit' provider?: Provider } function ProviderFormModal({ open, onClose, mode, provider }: ProviderFormProps) { const { createProvider, updateProvider, testConfig } = useProviderStore() const fetchSettings = useSettingsStore((s) => s.fetchAll) const [name, setName] = useState(provider?.name ?? '') const [baseUrl, setBaseUrl] = useState(provider?.baseUrl ?? '') const [apiKey, setApiKey] = useState('') const [notes, setNotes] = useState(provider?.notes ?? '') const [models, setModels] = useState(provider?.models ?? [{ id: '', name: '', description: '', context: '' }]) const [isSubmitting, setIsSubmitting] = useState(false) const [testResult, setTestResult] = useState(null) const [isTesting, setIsTesting] = useState(false) const canSubmit = name.trim() && baseUrl.trim() && (mode === 'edit' || apiKey.trim()) && models.some((m) => m.id.trim() && m.name.trim()) const handleSubmit = async () => { if (!canSubmit) return setIsSubmitting(true) try { const validModels = models.filter((m) => m.id.trim() && m.name.trim()) if (mode === 'create') { await createProvider({ name: name.trim(), baseUrl: baseUrl.trim(), apiKey: apiKey.trim(), models: validModels, notes: notes.trim() || undefined, }) } else if (provider) { const input: UpdateProviderInput = { name: name.trim(), baseUrl: baseUrl.trim(), models: validModels, notes: notes.trim() || undefined, } if (apiKey.trim()) input.apiKey = apiKey.trim() await updateProvider(provider.id, input) if (provider.isActive) await fetchSettings() } onClose() } catch (err) { console.error('Failed to save provider:', err) } finally { setIsSubmitting(false) } } const handleTest = async () => { const firstModel = models.find((m) => m.id.trim()) if (!baseUrl.trim() || !apiKey.trim() || !firstModel) return setIsTesting(true) setTestResult(null) try { const result = await testConfig({ baseUrl: baseUrl.trim(), apiKey: apiKey.trim(), modelId: firstModel.id.trim() }) setTestResult(result) } catch { setTestResult({ success: false, latencyMs: 0, error: 'Request failed' }) } finally { setIsTesting(false) } } const addModel = () => setModels([...models, { id: '', name: '', description: '', context: '' }]) const removeModel = (index: number) => setModels(models.filter((_, i) => i !== index)) const updateModel = (index: number, field: keyof ProviderModel, value: string) => { setModels(models.map((m, i) => i === index ? { ...m, [field]: value } : m)) } return ( } >
setName(e.target.value)} placeholder="e.g. OpenRouter, Anthropic Official" /> setBaseUrl(e.target.value)} placeholder="https://api.anthropic.com" /> setApiKey(e.target.value)} placeholder={mode === 'edit' ? '****' : 'sk-ant-...'} /> setNotes(e.target.value)} placeholder="Optional notes..." /> {/* Models */}
{models.map((m, i) => (
updateModel(i, 'id', e.target.value)} placeholder="Model ID *" className="h-8 px-2 text-xs rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] outline-none focus:border-[var(--color-border-focus)]" /> updateModel(i, 'name', e.target.value)} placeholder="Display name *" className="h-8 px-2 text-xs rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] outline-none focus:border-[var(--color-border-focus)]" /> updateModel(i, 'description', e.target.value)} placeholder="Description" className="h-8 px-2 text-xs rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] outline-none focus:border-[var(--color-border-focus)]" /> updateModel(i, 'context', e.target.value)} placeholder="Context (e.g. 200k)" className="h-8 px-2 text-xs rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] outline-none focus:border-[var(--color-border-focus)]" />
{models.length > 1 && ( )}
))}
{/* Test connection */}
{testResult && ( {testResult.success ? `Connected (${testResult.latencyMs}ms)` : `Failed: ${testResult.error}`} )}
) } // ─── Model Settings ────────────────────────────────────── function ModelSettings() { const { availableModels, currentModel, effortLevel, activeProviderName, setModel, setEffort, fetchAll } = useSettingsStore() const [customModelId, setCustomModelId] = useState('') const [showCustom, setShowCustom] = useState(false) useEffect(() => { fetchAll() }, [fetchAll]) const handleSelectModel = async (model: ModelInfo) => { await setModel(model.id) } const handleCustomModel = async () => { const id = customModelId.trim() if (!id) return await setModel(id) setShowCustom(false) setCustomModelId('') } const MODEL_ICONS: Record = { 'opus': 'diamond', 'sonnet': 'auto_awesome', 'haiku': 'bolt', } as const const getModelIcon = (id: string) => { if (id.includes('opus')) return MODEL_ICONS['opus'] if (id.includes('sonnet')) return MODEL_ICONS['sonnet'] if (id.includes('haiku')) return MODEL_ICONS['haiku'] return 'smart_toy' } const EFFORT_LABELS: Record = { low: 'Low', medium: 'Medium', high: 'High', max: 'Max', } return (

Model

Choose the AI model for new conversations. {activeProviderName && ( (via {activeProviderName}) )}

{availableModels.map((model) => { const isSelected = currentModel?.id === model.id return ( ) })} {/* Custom model */} {showCustom ? (
setCustomModelId(e.target.value)} placeholder="Enter model ID (e.g. claude-sonnet-4-6-20250514)" className="flex-1 text-sm bg-transparent outline-none text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)]" onKeyDown={(e) => e.key === 'Enter' && handleCustomModel()} />
) : ( )}
{/* Effort level */}

Effort Level

Controls how much computation the model uses.

{(['low', 'medium', 'high', 'max'] as EffortLevel[]).map((level) => ( ))}
) } // ─── Permission Settings ────────────────────────────────────── function PermissionSettings() { const { permissionMode, setPermissionMode } = useSettingsStore() const MODES: Array<{ mode: PermissionMode; icon: string; label: string; desc: string }> = [ { mode: 'default', icon: 'verified_user', label: 'Ask permissions', desc: 'Ask before executing tools' }, { mode: 'acceptEdits', icon: 'edit_note', label: 'Accept edits', desc: 'Auto-approve file edits, ask for others' }, { mode: 'plan', icon: 'architecture', label: 'Plan mode', desc: 'Think and plan without executing' }, { mode: 'bypassPermissions', icon: 'bolt', label: 'Bypass all', desc: 'Skip all permission checks (dangerous)' }, ] return (

Permission Mode

Controls how tool execution permissions are handled.

{MODES.map(({ mode, icon, label, desc }) => { const isSelected = permissionMode === mode return ( ) })}
) } // ─── General Settings ────────────────────────────────────── function GeneralSettings() { return (

General

API configuration is now managed through the Providers tab.

) }