import { html } from 'lit'; import { LightElement } from '../lib/base.js'; function emptyTForm() { return { provider_id: '', model_id: '', name: '', language: '', priority: 100 }; } export class ModelsTranscribeSection extends LightElement { static properties = { onback: { attribute: false }, _models: { state: true }, _providers: { state: true }, _modal: { state: true }, _form: { state: true }, _saving: { state: true }, _error: { state: true }, _provider: { state: true }, _remoteModels: { state: true }, _loadingModels: { state: true }, }; constructor() { super(); this.onback = null; this._models = []; this._providers = []; this._modal = null; this._form = emptyTForm(); this._saving = false; this._error = null; this._provider = null; this._remoteModels = null; this._loadingModels = false; } connectedCallback() { super.connectedCallback(); this._load(); } async _load() { try { const [modelsRes, providersRes] = await Promise.all([ fetch('/api/transcribe/models'), fetch('/api/llm/providers'), ]); if (!modelsRes.ok) throw new Error(`models: HTTP ${modelsRes.status}`); if (!providersRes.ok) throw new Error(`providers: HTTP ${providersRes.status}`); this._models = await modelsRes.json(); this._providers = await providersRes.json(); } catch (e) { this._error = e.message; } } // ── Add flow ───────────────────────────────────────────────────────────────── _openAdd() { this._error = null; this._provider = null; this._form = emptyTForm(); this._modal = 'pick-provider'; } async _pickProvider(provider) { this._provider = provider; this._remoteModels = null; this._form = { ...emptyTForm(), provider_id: provider.id }; this._loadingModels = true; this._modal = 'pick-model'; try { const res = await fetch(`/api/transcribe/providers/${provider.id}/models`); this._remoteModels = res.ok ? await res.json() : null; } catch { this._remoteModels = null; } finally { this._loadingModels = false; if (!this._remoteModels || this._remoteModels.length === 0) { this._modal = 'add'; } } } _pickRemoteModel(remote) { this._form = { ...this._form, model_id: remote.id, name: remote.name, }; this._modal = 'add'; } // ── Edit flow ──────────────────────────────────────────────────────────────── async _openEdit(m) { this._error = null; try { const res = await fetch(`/api/transcribe/models/${m.id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const r = await res.json(); this._provider = this._providers.find(p => p.id === r.provider_id) ?? null; this._form = { provider_id: r.provider_id, model_id: r.model_id, name: r.name, language: r.language ?? '', priority: r.priority, }; this._modal = { mode: 'edit', id: r.id, name: r.name }; } catch (e) { this._error = e.message; } } // ── Delete ─────────────────────────────────────────────────────────────────── async _delete(m) { if (!confirm(`Delete transcription model "${m.name}"?`)) return; try { const res = await fetch(`/api/transcribe/models/${m.id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(await res.text()); await this._load(); } catch (e) { this._error = e.message; } } // ── Submit add ─────────────────────────────────────────────────────────────── async _submitAdd(e) { e.preventDefault(); if (this._saving) return; this._saving = true; this._error = null; const f = this._form; try { const res = await fetch('/api/transcribe/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider_id: Number(f.provider_id), model_id: f.model_id, name: f.name || f.model_id, language: f.language || null, priority: Number(f.priority) || 100, }), }); 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; } } // ── Submit edit ────────────────────────────────────────────────────────────── async _submitEdit(e) { e.preventDefault(); if (this._saving) return; this._saving = true; this._error = null; const f = this._form; const id = this._modal.id; try { const res = await fetch(`/api/transcribe/models/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider_id: Number(f.provider_id), model_id: f.model_id, name: f.name || f.model_id, language: f.language || null, priority: Number(f.priority) || 100, }), }); 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; } } _closeModal() { this._modal = null; this._error = null; } // ── Render ─────────────────────────────────────────────────────────────────── _renderRow(m) { const isPlugin = m.from_plugin; return html` ${isPlugin ? html`Plugin` : html`Cloud`} ${m.name} ${isPlugin ? '—' : m.provider_name} ${m.model_id} ${m.language ?? html`auto`} ${isPlugin ? html` ` : html` `} `; } _renderPickProvider() { const tProviders = this._providers.filter(p => Array.isArray(p.supported_types) && p.supported_types.includes('transcribe') ); return html`
{ if (e.target === e.currentTarget) this._closeModal(); }}>
Add Transcription Model — Choose Provider
${this._error ? html`
${this._error}
` : ''}
${tProviders.map(p => html` `)}
`; } _renderPickModel() { const p = this._provider; return html`
{ if (e.target === e.currentTarget) this._closeModal(); }}>
Add Transcription Model ${p?.name}
${this._loadingModels ? html`
Loading models…
` : html`
${(this._remoteModels ?? []).map(m => html` `)}
`}
`; } _renderForm(isEdit = false) { const f = this._form; const p = this._provider; const title = isEdit ? html`Edit ${this._modal.name}` : html`Add Transcription Model ${p?.name}`; return html`
{ if (e.target === e.currentTarget) this._closeModal(); }}>
${title}
${this._error ? html`
${this._error}
` : ''}
isEdit ? this._submitEdit(e) : this._submitAdd(e)}>
this._form = { ...this._form, model_id: e.target.value }} /> ${isEdit ? html`
Model ID cannot be changed after creation.
` : ''}
this._form = { ...this._form, name: e.target.value }} />
this._form = { ...this._form, language: e.target.value }} />
this._form = { ...this._form, priority: e.target.value }} />
`; } render() { const tProviders = this._providers.filter(p => Array.isArray(p.supported_types) && p.supported_types.includes('transcribe') ); const canAdd = tProviders.length > 0; return html`
${this.onback ? html` ` : ''}

Transcription Models

${!canAdd ? html`

No provider supports transcription yet. Add an OpenAI or OpenRouter provider first.

` : ''} ${this._error && !this._modal ? html`
${this._error}
` : ''} ${this._models.length === 0 ? html`

No transcription models configured. ${canAdd ? html`Click Add to add a cloud model.` : ''} Activate the Whisper Local plugin for on-device transcription.

` : html`
${this._models.map(m => this._renderRow(m))}
Source Name Provider Model ID Language
`}
${this._modal === 'pick-provider' ? this._renderPickProvider() : ''} ${this._modal === 'pick-model' ? this._renderPickModel() : ''} ${this._modal === 'add' ? this._renderForm(false) : ''} ${this._modal?.mode === 'edit' ? this._renderForm(true) : ''} `; } }