import { useEffect, useState } from 'react'; import { type ClaudeAccountSummary, type CodexAccountSummary, type CodexFeatureSnapshot, type FastModeSnapshot, type ModelConfigSnapshot, type ModelRoleConfig, addClaudeAccount, deleteAccount, fetchAccounts, fetchCodexFeatures, fetchFastMode, fetchModelConfig, refreshAllCodexAccounts as refreshAllCodexAccountsApi, refreshCodexAccount as refreshCodexAccountApi, setCurrentCodexAccount as setCurrentCodexAccountApi, updateCodexFeatures, updateFastMode, updateModels, } from './api'; import { formatDateTime, formatJwtCacheExpiry, formatLiveStatusBadge, formatUsageBadge, } from './codexAccountBadges'; import { type Locale, type Messages } from './i18n'; import { MoaSettingsPanel } from './MoaSettingsPanel'; import { RuntimeInventorySettings } from './RuntimeInventorySettings'; import { ModelRoleFields, hasUnsupportedModelEffort, type ModelRole, } from './SettingsModelFields'; import { GeneralSettings, SettingsApplyCard, SettingsCard, SettingsNav, SettingsSaveBar, SettingsSectionHeading, type SettingsSectionId, } from './SettingsPanelChrome'; type AccountProvider = 'claude' | 'codex'; interface AccountData { claude: ClaudeAccountSummary[]; codex: CodexAccountSummary[]; codexCurrentIndex?: number; } export interface SettingsPanelProps { locale: Locale; nickname: string; onLocaleChange: (locale: Locale) => void; onNicknameChange: (next: string) => void; onRestartStack: () => void; t: Messages; } export function SettingsPanel({ locale, nickname, onLocaleChange, onNicknameChange, onRestartStack, t, }: SettingsPanelProps) { const [activeSection, setActiveSection] = useState('settings-general'); return (
); } function ModelSettings({ t }: { t: Messages }) { const [config, setConfig] = useState(null); const [draft, setDraft] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [savedAt, setSavedAt] = useState(null); const section = t.settings.sections.models; useEffect(() => { let cancelled = false; setBusy(true); fetchModelConfig() .then((c) => { if (cancelled) return; setConfig(c); setDraft(c); setError(null); }) .catch((err) => { if (cancelled) return; setError(err instanceof Error ? err.message : String(err)); }) .finally(() => { if (!cancelled) setBusy(false); }); return () => { cancelled = true; }; }, []); async function save() { if (!draft || !config) return; if (hasUnsupportedModelEffort(draft)) { setError(t.settings.models.effortSaveBlocked); return; } setBusy(true); setError(null); try { const next = await updateModels({ owner: draft.owner, reviewer: draft.reviewer, arbiter: draft.arbiter, }); setConfig(next); setDraft(next); setSavedAt(Date.now()); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } function setRole(role: ModelRole, patch: Partial) { setDraft((prev) => prev ? { ...prev, [role]: { ...prev[role], ...patch }, } : prev, ); } const dirty = draft !== null && config !== null && JSON.stringify(draft) !== JSON.stringify(config); const effortBlocked = draft !== null && hasUnsupportedModelEffort(draft); return (
{error ?

{error}

: null} {!draft ? (

{busy ? t.settings.common.loading : t.settings.models.empty}

) : ( <> setRole(role, patch)} t={t} /> void save()} saveDisabled={effortBlocked} savedHint={t.settings.common.savedRestartHint} savingLabel={t.settings.common.saving} showSavedHint={savedAt !== null && !dirty} /> )}
); } function CodexRuntimeSettings({ t }: { t: Messages }) { const section = t.settings.sections.codex; return (
); } function FastModeSettings({ t }: { t: Messages }) { const [state, setState] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; fetchFastMode() .then((s) => { if (cancelled) return; setState(s); setError(null); }) .catch((err) => { if (cancelled) return; setError(err instanceof Error ? err.message : String(err)); }); return () => { cancelled = true; }; }, []); async function toggle(provider: keyof FastModeSnapshot) { if (!state) return; const next = !state[provider]; const optimistic = { ...state, [provider]: next }; setState(optimistic); setBusy(true); setError(null); try { const fresh = await updateFastMode({ [provider]: next }); setState(fresh); } catch (err) { setState(state); setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } return ( {error ?

{error}

: null} {!state ? (

{t.settings.common.loading}

) : (
{t.settings.codex.fastModeApplyHint}
)}
); } function CodexFeatureSettings({ t }: { t: Messages }) { const [state, setState] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; fetchCodexFeatures() .then((snapshot) => { if (cancelled) return; setState(snapshot); setError(null); }) .catch((err) => { if (cancelled) return; setError(err instanceof Error ? err.message : String(err)); }); return () => { cancelled = true; }; }, []); async function toggleGoals() { if (!state) return; const previous = state; const optimistic = { ...state, goals: !state.goals }; setState(optimistic); setBusy(true); setError(null); try { const fresh = await updateCodexFeatures({ goals: optimistic.goals }); setState(fresh); } catch (err) { setState(previous); setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } return ( {error ?

{error}

: null} {!state ? (

{t.settings.common.loading}

) : ( <> {t.settings.codex.goalApplyHint} )}
); } function AccountSettings({ t }: { t: Messages }) { const [data, setData] = useState(null); const [busy, setBusy] = useState(false); const [perRowBusy, setPerRowBusy] = useState(null); const [error, setError] = useState(null); const [tokenInput, setTokenInput] = useState(''); const section = t.settings.sections.accounts; async function refresh() { setBusy(true); setError(null); try { const fresh = await fetchAccounts(); setData(fresh); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } useEffect(() => { void refresh(); }, []); async function handleDelete(provider: AccountProvider, index: number) { if ( !window.confirm( t.settings.accounts.deleteConfirm .replace('{provider}', provider) .replace('{index}', String(index)), ) ) { return; } setBusy(true); setError(null); try { await deleteAccount(provider, index); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } async function handleAdd() { const token = tokenInput.trim(); if (!token) return; setBusy(true); setError(null); try { await addClaudeAccount(token); setTokenInput(''); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } async function handleCodexRefresh(index: number) { setPerRowBusy(`refresh:${index}`); setError(null); try { await refreshCodexAccountApi(index); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setPerRowBusy(null); } } async function handleRefreshAllCodex() { setBusy(true); setError(null); try { const result = await refreshAllCodexAccountsApi(); await refresh(); if (result.failed.length > 0) { setError( t.settings.accounts.refreshFailed.replace( '{indexes}', result.failed.map((f) => `#${f.index}`).join(', '), ), ); } } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } async function handleSwitchCodex(index: number) { setPerRowBusy(`switch:${index}`); setError(null); try { await setCurrentCodexAccountApi(index); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setPerRowBusy(null); } } return (
{error ?

{error}

: null}
void handleAdd()} onDelete={(index) => void handleDelete('claude', index)} onTokenInputChange={setTokenInput} t={t} tokenInput={tokenInput} /> void handleDelete('codex', index)} onRefresh={(index) => void handleCodexRefresh(index)} onRefreshAll={() => void handleRefreshAllCodex()} onSwitch={(index) => void handleSwitchCodex(index)} perRowBusy={perRowBusy} t={t} />
); } function ClaudeAccounts({ busy, data, onAdd, onDelete, onTokenInputChange, tokenInput, t, }: { busy: boolean; data: AccountData | null; onAdd: () => void; onDelete: (index: number) => void; onTokenInputChange: (value: string) => void; tokenInput: string; t: Messages; }) { return ( {!data ? (

{busy ? t.settings.common.loading : t.settings.common.none}

) : data.claude.length === 0 ? (

{t.settings.accounts.noAccounts}

) : (
    {data.claude.map((acc) => (
  • #{acc.index}
    {acc.subscriptionType ?? 'unknown'} {acc.rateLimitTier ?? 'claude-code'} {' ยท '} {t.settings.accounts.autoRefresh}
    {acc.index === 0 ? ( {t.settings.accounts.primaryAccount} ) : null}
    {acc.index > 0 ? (
    ) : null}
  • ))}
)}