import { html, nothing } from 'lit'; import { LightElement } from '../lib/base.js'; import { t } from '../lib/i18n.js'; export class ConfigPage extends LightElement { static properties = { _open: { state: true }, _properties: { state: true }, _values: { state: true }, // { [key]: string } _saving: { state: true }, // Set _saved: { state: true }, // Set (brief flash) _error: { state: true }, _debugMode: { state: true }, _debugLoading: { state: true }, }; constructor() { super(); this._open = false; this._properties = []; this._values = {}; this._saving = new Set(); this._saved = new Set(); this._error = null; this._debugMode = false; this._debugLoading = true; } connectedCallback() { super.connectedCallback(); this.__onLocaleChanged = () => this.requestUpdate(); window.addEventListener('locale-changed', this.__onLocaleChanged); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === 'config'; this.style.display = this._open ? 'flex' : 'none'; if (this._open) { this._load(); this._loadDebugMode(); } }); } disconnectedCallback() { window.removeEventListener('locale-changed', this.__onLocaleChanged); super.disconnectedCallback(); } async _loadDebugMode() { try { const res = await fetch('/api/dev/debug_mode'); if (!res.ok) throw new Error(); const data = await res.json(); this._debugMode = data.enabled; } catch { // ignore, keep current value } finally { this._debugLoading = false; } } async _toggleDebugMode() { const next = !this._debugMode; this._debugMode = next; try { const res = await fetch('/api/dev/debug_mode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: next }), }); if (!res.ok) throw new Error(); window.dispatchEvent(new CustomEvent('debug-mode-change', { detail: { enabled: next } })); } catch { this._debugMode = !next; } } async _load() { this._error = null; try { const res = await fetch('/api/config'); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); this._properties = data.sets ?? []; const vals = {}; for (const s of this._properties) for (const p of s.properties) vals[p.key] = p.value ?? ''; this._values = vals; } catch (e) { this._error = e.message; } } _setValue(key, val) { this._values = { ...this._values, [key]: val }; } async _save(prop) { const key = prop.key; const value = this._values[key] ?? ''; this._saving = new Set([...this._saving, key]); this.requestUpdate(); try { const res = await fetch(`/api/config/${encodeURIComponent(key)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value }), }); if (!res.ok) throw new Error(await res.text()); this._saved = new Set([...this._saved, key]); setTimeout(() => { this._saved = new Set([...this._saved].filter(k => k !== key)); }, 1500); } catch (e) { alert(t('config.error_save', { name: prop.name, msg: e.message })); } finally { this._saving = new Set([...this._saving].filter(k => k !== key)); } } _renderInput(prop) { const val = this._values[prop.key] ?? ''; if (prop.property_type === 'bool') { const effective = val !== '' ? val : (prop.default_value ?? 'true'); const checked = effective !== 'false'; return html` { this._setValue(prop.key, e.target.checked ? 'true' : 'false'); this._save(prop); }} /> ${checked ? 'Enabled' : 'Disabled'} `; } if (prop.property_type === 'int') { return html` this._setValue(prop.key, e.target.value)} />`; } if (prop.property_type === 'security_group') { const groups = prop.options ?? []; return html` this._setValue(prop.key, e.target.value)}> — default — ${groups.map(g => html` ${g.name}`)} `; } return html` this._setValue(prop.key, e.target.value)} />`; } _renderSet(set) { return html` ${set.name} ${set.description} ${set.properties.map(p => this._renderRow(p))} `; } _renderRow(prop) { const saving = this._saving.has(prop.key); const saved = this._saved.has(prop.key); return html` ${prop.name} ${prop.description} ${this._renderInput(prop)} ${prop.property_type !== 'bool' ? html` this._save(prop)}> ${saving ? html`` : saved ? 'Saved' : 'Save'} ` : nothing} `; } render() { return html` ${t('config.title')} ${this._error ? html` ${this._error}` : nothing} ${this._properties.length === 0 && !this._error ? html` ${t('config.loading')}` : nothing} ${this._properties.map(s => this._renderSet(s))} ${t('config.developer')} ${t('config.debug')} ${t('config.debug.desc')} this._toggleDebugMode()} /> ${this._debugMode ? 'Enabled' : 'Disabled'} `; } }
${t('config.loading')}