This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/surfaces/team-admin/providers.js
gobha b7746c3004 Changeset 0.37.14 (#226)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-23 16:47:48 +00:00

124 lines
5.8 KiB
JavaScript

/**
* Team Admin > Providers
*/
const { html } = window;
const { useState, useEffect, useCallback } = hooks;
export default function ProvidersSection({ teamId }) {
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.teams.providers(teamId),
sw.api.admin.providers.types().catch(() => ({ types: [] })),
]);
setConfigs(c.data || []);
setProviderTypes(t || []);
} catch (e) { sw.toast(e.message, 'error'); }
finally { setLoading(false); }
}, [teamId]);
useEffect(() => { load(); }, [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,
};
try {
if (editing === 'new') {
await sw.api.teams.createProvider(teamId, data);
sw.toast('Provider created', 'success');
} else {
await sw.api.teams.updateProvider(teamId, editing.id, data);
sw.toast('Provider updated', 'success');
}
setEditing(null);
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
async function syncModels(pid) {
try {
const result = await sw.api.teams.providerModels(teamId, pid);
sw.toast(`Models synced: ${result.added || result.count || 0}`, 'success');
} catch (e) { sw.toast(e.message, 'error'); }
}
async function deleteProvider(pid) {
const ok = await sw.confirm('Delete this provider config?', true);
if (!ok) return;
try {
await sw.api.teams.deleteProvider(teamId, pid);
sw.toast('Provider deleted', 'success');
load();
} catch (e) { sw.toast(e.message, 'error'); }
}
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
return html`
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${() => setEditing('new')}>+ Add</button>
</div>
${editing && html`
<form class="admin-inline-form" style="margin-bottom:16px;padding:12px;border:1px solid var(--border);border-radius:var(--radius);" onSubmit=${saveProvider}>
<h4 style="margin:0 0 12px;">${editing === 'new' ? 'Add Provider' : 'Edit Provider'}</h4>
<div class="form-row">
<div class="form-group"><label>Name</label>
<input name="pname" placeholder="My OpenAI" value=${editing !== 'new' ? editing.name || '' : ''} />
</div>
<div class="form-group"><label>Type</label>
<select name="provider">
${providerTypes.map(t => html`<option key=${t.name} value=${t.name} selected=${editing !== 'new' && editing.provider === t.name}>${t.display_name || t.name}</option>`)}
</select>
</div>
</div>
<div class="form-row">
<div class="form-group" style="flex:2;"><label>Endpoint</label>
<input name="endpoint" placeholder="https://api.openai.com/v1" value=${editing !== 'new' ? editing.endpoint || '' : ''} />
</div>
<div class="form-group" style="flex:1;"><label>API Key</label>
<input name="api_key" type="password" placeholder="${editing !== 'new' ? '(unchanged)' : 'sk-...'}" autocomplete="off" />
</div>
</div>
<div class="form-row" style="margin-top:12px;gap:8px;">
<button type="submit" class="btn-small btn-primary">${editing === 'new' ? 'Create' : 'Save'}</button>
<button type="button" class="btn-small" onClick=${() => setEditing(null)}>Cancel</button>
</div>
</form>
`}
<div class="provider-cards">
${configs.length === 0 && html`<div class="empty-hint">No provider configs</div>`}
${configs.map(c => html`
<div class="provider-card" key=${c.id}>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<strong>${c.name}</strong>
<span class="badge">${c.provider}</span>
${c.has_key ? html`<span class="badge badge-active">key set</span>` : html`<span class="badge badge-inactive">no key</span>`}
</div>
<div class="text-muted" style="font-size:12px;margin-bottom:8px;word-break:break-all;">${c.endpoint || '(default)'}</div>
<div style="display:flex;gap:6px;">
<button class="btn-small" onClick=${() => setEditing(c)}>Edit</button>
<button class="btn-small" onClick=${() => syncModels(c.id)}>Sync Models</button>
<button class="btn-small btn-danger" onClick=${() => deleteProvider(c.id)}>Delete</button>
</div>
</div>
`)}
</div>
</div>
`;
}