/** * Admin > AI > Providers */ const { html } = window; const { useState, useEffect, useCallback } = hooks; export default function ProvidersSection() { const [configs, setConfigs] = useState([]); const [providerTypes, setProviderTypes] = useState([]); const [loading, setLoading] = useState(true); const [editing, setEditing] = useState(null); // null | 'new' | config object const load = useCallback(async () => { try { const [c, t] = await Promise.all([ sw.api.admin.configs.list(), sw.api.admin.providers.types(), ]); setConfigs(c || []); setProviderTypes(t || []); } catch (e) { sw.toast(e.message, 'error'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, []); async function saveProvider(e) { e.preventDefault(); const form = e.target; const data = { name: form.pname.value.trim(), provider: form.provider.value, endpoint: form.endpoint.value.trim(), api_key: form.api_key.value || undefined, is_private: form.is_private.checked, }; try { if (editing === 'new') { await sw.api.admin.configs.create(data); sw.toast('Provider created', 'success'); } else { await sw.api.admin.configs.update(editing.id, data); sw.toast('Provider updated', 'success'); } setEditing(null); load(); } catch (e) { sw.toast(e.message, 'error'); } } async function syncModels(id) { try { const result = await sw.api.admin.models.fetch(); sw.toast(`Synced: ${result.added || 0} added, ${result.updated || 0} updated`, 'success'); } catch (e) { sw.toast(e.message, 'error'); } } async function testProvider(cfg) { sw.toast(`Testing ${cfg.name}\u2026`, 'info'); try { const result = await sw.api.admin.models.fetch(); sw.toast(`${cfg.name}: connection OK`, 'success'); } catch (e) { sw.toast(`${cfg.name}: connection failed \u2014 ${e.message}`, 'error'); } } async function deleteProvider(id) { const ok = await sw.confirm('Delete this provider config?', true); if (!ok) return; try { await sw.api.admin.configs.del(id); sw.toast('Provider deleted', 'success'); load(); } catch (e) { sw.toast(e.message, 'error'); } } if (loading) return html`