import { html } from 'lit'; import { LightElement } from '../lib/base.js'; function emptyForm(firstTypeId = '') { return { name: '', type: firstTypeId, api_key: '', base_url: '', description: '' }; } export class LlmProvidersPage extends LightElement { static properties = { _open: { state: true }, _providers: { state: true }, _providerTypes: { state: true }, _modelCounts: { state: true }, _modal: { state: true }, _saving: { state: true }, _error: { state: true }, _form: { state: true }, }; constructor() { super(); this._open = false; this._providers = []; this._providerTypes = []; this._modelCounts = {}; this._modal = null; this._saving = false; this._error = null; this._form = emptyForm(); } _typeMeta(typeId) { return this._providerTypes.find(t => t.type_id === typeId) ?? { display_name: typeId, color: '#888', icon: 'bi-box', fields: [] }; } connectedCallback() { super.connectedCallback(); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === 'providers'; this.style.display = this._open ? 'flex' : 'none'; if (this._open) this._load(); }); } async _load() { try { const [typesRes, provRes, modelsRes] = await Promise.all([ fetch('/api/llm/providers/types'), fetch('/api/llm/providers'), fetch('/api/llm/models'), ]); if (!typesRes.ok) throw new Error(`Provider types: HTTP ${typesRes.status}`); if (!provRes.ok) throw new Error(`Providers: HTTP ${provRes.status}`); if (!modelsRes.ok) throw new Error(`Models: HTTP ${modelsRes.status}`); const providerTypes = await typesRes.json(); const providers = await provRes.json(); const models = await modelsRes.json(); const counts = {}; for (const m of models) { const pid = String(m.provider_id); counts[pid] = (counts[pid] || 0) + 1; } this._providerTypes = providerTypes; this._providers = providers; this._modelCounts = counts; // Set default form type to first available provider type if (!this._form.type && providerTypes.length > 0) { this._form = { ...this._form, type: providerTypes[0].type_id }; } } catch (e) { this._error = e.message; } } // ── CRUD ────────────────────────────────────────────────────────────────── _openAdd() { this._error = null; this._form = emptyForm(this._providerTypes[0]?.type_id ?? ''); this._modal = { mode: 'add' }; } async _openEdit(provider) { this._error = null; try { const res = await fetch(`/api/llm/providers/${provider.id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const record = await res.json(); this._form = { name: record.name, type: record.type, api_key: record.api_key ?? '', base_url: record.base_url ?? '', description: record.description ?? '', }; this._modal = { mode: 'edit', id: record.id }; } catch (e) { this._error = e.message; } } async _delete(provider) { if (!confirm(`Delete provider "${provider.name}"? All associated models will be deleted too.`)) return; try { const res = await fetch(`/api/llm/providers/${provider.id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(await res.text()); await this._load(); } catch (e) { this._error = e.message; } } async _onSubmit(e) { e.preventDefault(); if (this._saving) return; this._saving = true; this._error = null; const f = this._form; const meta = this._typeMeta(f.type); const needsBaseUrl = meta.fields.some(field => field.key === 'base_url'); const payload = { name: f.name, type: f.type, api_key: f.api_key || null, base_url: needsBaseUrl ? (f.base_url || null) : null, description: f.description || null, }; const isEdit = this._modal?.mode === 'edit'; const url = isEdit ? `/api/llm/providers/${this._modal.id}` : '/api/llm/providers'; try { const res = await fetch(url, { method: isEdit ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!res.ok) throw new Error(await res.text()); this._modal = null; await this._load(); } catch (err) { this._error = err.message; } finally { this._saving = false; } } _setField(field, value) { this._form = { ...this._form, [field]: value }; } _closeModal() { this._modal = null; this._error = null; } // ── Render helpers ──────────────────────────────────────────────────────── _renderCard(p) { const meta = this._typeMeta(p.type); const color = meta.color; const icon = meta.icon; const label = meta.display_name; const count = this._modelCounts[String(p.id)]; const hasKey = Boolean(p.api_key); const needsUrl = meta.fields.some(f => f.key === 'base_url'); return html`
No providers configured yet.