Changeset 0.37.7 (#219)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
123
src/js/sw/surfaces/team-admin/providers.js
Normal file
123
src/js/sw/surfaces/team-admin/providers.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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(Array.isArray(c) ? c : c.data || []);
|
||||
setProviderTypes(t.types || t.data || []);
|
||||
} 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>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user