import { useEffect, useState } from 'react'; import { type MoaModelSettingsSnapshot, type MoaReferenceStatus, type MoaSettingsSnapshot, checkMoaModel, fetchMoaSettings, updateMoaSettings, } from './api'; import { type Messages } from './i18n'; import { SettingsSaveBar, SettingsSectionHeading } from './SettingsPanelChrome'; export function MoaSettingsPanel({ t }: { t: Messages }) { const [config, setConfig] = useState(null); const [draft, setDraft] = useState(null); const [apiKeys, setApiKeys] = useState>({}); const [busy, setBusy] = useState(false); const [checking, setChecking] = useState(null); const [error, setError] = useState(null); const [savedAt, setSavedAt] = useState(null); const section = t.settings.sections.moa; useEffect(() => { let cancelled = false; setBusy(true); fetchMoaSettings() .then((next) => { if (cancelled) return; setConfig(next); setDraft(next); setError(null); }) .catch((err) => { if (cancelled) return; setError(err instanceof Error ? err.message : String(err)); }) .finally(() => { if (!cancelled) setBusy(false); }); return () => { cancelled = true; }; }, []); function setModel(name: string, patch: Partial) { setDraft((prev) => prev ? { ...prev, models: prev.models.map((model) => model.name === name ? { ...model, ...patch } : model, ), } : prev, ); } function updateStatus(name: string, status: MoaReferenceStatus) { const update = (prev: MoaSettingsSnapshot | null) => prev ? { ...prev, models: prev.models.map((model) => model.name === name ? { ...model, lastStatus: status } : model, ), } : prev; setConfig(update); setDraft(update); } async function save() { if (!draft) return; setBusy(true); setError(null); try { const next = await updateMoaSettings({ enabled: draft.enabled, models: draft.models.map((model) => ({ name: model.name, enabled: model.enabled, model: model.model, baseUrl: model.baseUrl, apiFormat: model.apiFormat, apiKey: apiKeys[model.name]?.trim() || undefined, })), }); setConfig(next); setDraft(next); setApiKeys({}); setSavedAt(Date.now()); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } } async function testModel(name: string) { setChecking(name); setError(null); try { updateStatus(name, await checkMoaModel(name)); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setChecking(null); } } const dirty = draft !== null && config !== null && (JSON.stringify(draft) !== JSON.stringify(config) || Object.values(apiKeys).some((value) => value.trim())); return (
{error ?

{error}

: null} {!draft ? (

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

) : ( setApiKeys((prev) => ({ ...prev, [name]: value })) } onModelChange={setModel} onSave={save} onTest={testModel} onToggle={() => setDraft((prev) => prev ? { ...prev, enabled: !prev.enabled } : prev, ) } savedAt={savedAt} t={t} /> )}
); } function MoaSettingsContent({ apiKeys, busy, checking, dirty, draft, onApiKeyChange, onModelChange, onSave, onTest, onToggle, savedAt, t, }: { apiKeys: Record; busy: boolean; checking: string | null; dirty: boolean; draft: MoaSettingsSnapshot; onApiKeyChange: (name: string, value: string) => void; onModelChange: ( name: string, patch: Partial, ) => void; onSave: () => Promise; onTest: (name: string) => Promise; onToggle: () => void; savedAt: number | null; t: Messages; }) { return ( <>
    {draft.models.map((model) => ( ))}
void onSave()} savedHint={t.settings.common.savedRestartHint} savingLabel={t.settings.common.saving} showSavedHint={savedAt !== null && !dirty} /> ); } function formatMoaStatus( status: MoaReferenceStatus | null, t: Messages, ): string { if (!status) return t.settings.moa.notTested; const at = new Date(status.checkedAt); const checkedAt = Number.isNaN(at.getTime()) ? status.checkedAt : at.toLocaleString('ko-KR'); if (status.ok) { return `${t.settings.moa.statusOk} · ${checkedAt}`; } return `${t.settings.moa.statusFail} · ${checkedAt} · ${status.error ?? 'unknown error'}`; } function MoaMasterToggle({ busy, enabled, onToggle, t, }: { busy: boolean; enabled: boolean; onToggle: () => void; t: Messages; }) { return ( ); } function MoaModelRow({ apiKeyValue, busy, checking, dirty, model, onApiKeyChange, onModelChange, onTest, t, }: { apiKeyValue: string; busy: boolean; checking: string | null; dirty: boolean; model: MoaModelSettingsSnapshot; onApiKeyChange: (name: string, value: string) => void; onModelChange: ( name: string, patch: Partial, ) => void; onTest: (name: string) => Promise; t: Messages; }) { return (
  • {formatMoaStatus(model.lastStatus, t)}
  • ); }