import { useEffect, useMemo, useState } from 'react'; import { LOCALE_LABEL, LOCALES, useI18n } from '../i18n'; import type { Locale } from '../i18n'; import { PROVIDER_ORDER, PROVIDER_PRESETS } from '../providers/presets'; import { AgentIcon } from './AgentIcon'; import type { AgentInfo, AppConfig, ExecMode, ModelProvider } from '../types'; interface Props { initial: AppConfig; agents: AgentInfo[]; daemonLive: boolean; welcome?: boolean; onSave: (cfg: AppConfig) => void; onClose: () => void; onRefreshAgents: () => void; } export function SettingsDialog({ initial, agents, daemonLive, welcome, onSave, onClose, onRefreshAgents, }: Props) { const { t, locale, setLocale } = useI18n(); const [cfg, setCfg] = useState(initial); const [showApiKey, setShowApiKey] = useState(false); // If the daemon goes offline mid-edit, force API mode so the UI doesn't // pretend Local CLI is selectable. useEffect(() => { if (!daemonLive && cfg.mode === 'daemon') { setCfg((c) => ({ ...c, mode: 'api' })); } }, [daemonLive, cfg.mode]); const installedCount = useMemo( () => agents.filter((a) => a.available).length, [agents], ); const setMode = (mode: ExecMode) => setCfg((c) => ({ ...c, mode })); // Switching providers swaps in that provider's defaults, but preserves // any non-empty values the user already typed — they may have a custom // baseUrl (e.g. an OpenRouter URL while staying on the openai provider) // they don't want clobbered. Empty fields fall back to the preset. const setProvider = (provider: ModelProvider) => { setCfg((c) => { if (c.provider === provider) return c; const preset = PROVIDER_PRESETS[provider]; return { ...c, provider, baseUrl: c.baseUrl?.trim() ? c.baseUrl : preset.baseUrl, model: c.model?.trim() ? c.model : preset.defaultModel, }; }); }; const activePreset = PROVIDER_PRESETS[cfg.provider]; const canSave = cfg.mode === 'daemon' ? Boolean(cfg.agentId && agents.find((a) => a.id === cfg.agentId)?.available) : Boolean( cfg.apiKey.trim() && cfg.model.trim() && // Azure has no global default base URL — require the user to // paste their resource endpoint. Other providers ship a usable // default so a blank field falls back to the preset. (cfg.provider === 'azure' ? cfg.baseUrl.trim().length > 0 : true), ); return (
e.stopPropagation()} >
{welcome ? ( <> {t('settings.welcomeKicker')}

{t('settings.welcomeTitle')}

{t('settings.welcomeSubtitle')}

) : ( <> {t('settings.kicker')}

{t('settings.title')}

{t('settings.subtitle')}

)}
{cfg.mode === 'daemon' ? (

{t('settings.codeAgent')}

{t('settings.codeAgentHint')}

{agents.length === 0 ? (
{t('settings.noAgentsDetected')}
) : (
{agents.map((a) => { const active = cfg.agentId === a.id; return ( ); })}
)}
) : (

{t('settings.apiSection')}

{t('settings.providerHint')}

{PROVIDER_ORDER.map((id) => { const preset = PROVIDER_PRESETS[id]; const active = cfg.provider === id; return ( ); })}
{activePreset.needsApiVersion ? ( ) : null}

{t('settings.apiHint')}

{t('settings.proxyHint')}

)}

{t('settings.language')}

{t('settings.languageHint')}

{LOCALES.map((code) => { const active = locale === code; return ( ); })}
); }