Persist engine URLs and custom cards server-side (survive browser restarts)
Engine URL inputs (Ollama, vLLM, faster-whisper, etc.), custom engine cards, and the refinement/conversation LLM URLs were stored only in localStorage and lost on browser data clear. All four are now synced to settings.json via _patchSettings() with localStorage as fast initial fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
916123f3b4
commit
239bf0dad6
10
server.py
10
server.py
@ -326,6 +326,8 @@ _SETTINGS_KEYS = {
|
|||||||
"captures_default_voice",
|
"captures_default_voice",
|
||||||
"client_voice_bindings",
|
"client_voice_bindings",
|
||||||
"llm_url",
|
"llm_url",
|
||||||
|
# Browser-persistent UI state
|
||||||
|
"engine_local_urls", "custom_engine_cards", "refine_llm_url", "conv_llm_url",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -488,6 +490,10 @@ def _normalize_settings(s: dict) -> dict:
|
|||||||
s["output_dir"] = str(scan_dir / "active_voices")
|
s["output_dir"] = str(scan_dir / "active_voices")
|
||||||
if s.get("tts_stream_mode") not in {"auto", "streaming", "buffered"}:
|
if s.get("tts_stream_mode") not in {"auto", "streaming", "buffered"}:
|
||||||
s["tts_stream_mode"] = "auto"
|
s["tts_stream_mode"] = "auto"
|
||||||
|
if not isinstance(s.get("engine_local_urls"), dict):
|
||||||
|
s["engine_local_urls"] = {}
|
||||||
|
if not isinstance(s.get("custom_engine_cards"), list):
|
||||||
|
s["custom_engine_cards"] = []
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
@ -532,6 +538,10 @@ def _load_settings() -> dict:
|
|||||||
"captures_default_voice": "",
|
"captures_default_voice": "",
|
||||||
"client_voice_bindings": {},
|
"client_voice_bindings": {},
|
||||||
"llm_url": "http://localhost:11434/v1",
|
"llm_url": "http://localhost:11434/v1",
|
||||||
|
"engine_local_urls": {},
|
||||||
|
"custom_engine_cards": [],
|
||||||
|
"refine_llm_url": "",
|
||||||
|
"conv_llm_url": "",
|
||||||
}
|
}
|
||||||
if CONFIG_FILE.exists():
|
if CONFIG_FILE.exists():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1955,6 +1955,23 @@ async function refreshTtsBackendAvailability(selected = '') {
|
|||||||
return _ttsBackends;
|
return _ttsBackends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _patchSettings(patch) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch) });
|
||||||
|
} catch (e) { console.warn('[settings] patch failed', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
let _engineUrlSaveTimer = null;
|
||||||
|
function _saveEngineLocalUrls() {
|
||||||
|
clearTimeout(_engineUrlSaveTimer);
|
||||||
|
_engineUrlSaveTimer = setTimeout(() => {
|
||||||
|
const urls = {};
|
||||||
|
document.querySelectorAll('[data-llm-local-key]').forEach(i => { if (i.value) urls[i.dataset.llmLocalKey] = i.value; });
|
||||||
|
_patchSettings({ engine_local_urls: urls });
|
||||||
|
if (_appSettings) _appSettings.engine_local_urls = urls;
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
const s = await fetch('/api/settings').then(r => r.json());
|
const s = await fetch('/api/settings').then(r => r.json());
|
||||||
$('s-whisper-url').value = s.whisper_url || '';
|
$('s-whisper-url').value = s.whisper_url || '';
|
||||||
@ -1968,6 +1985,19 @@ async function loadSettings() {
|
|||||||
$('s-xtts-url').value = s.xtts_url || '';
|
$('s-xtts-url').value = s.xtts_url || '';
|
||||||
const llmUrlEl = $('s-llm-url'); if (llmUrlEl) llmUrlEl.value = s.llm_url || '';
|
const llmUrlEl = $('s-llm-url'); if (llmUrlEl) llmUrlEl.value = s.llm_url || '';
|
||||||
_appSettings = s;
|
_appSettings = s;
|
||||||
|
|
||||||
|
// Restore engine URL inputs from server (overrides localStorage fallback)
|
||||||
|
const savedEngineUrls = s.engine_local_urls || {};
|
||||||
|
document.querySelectorAll('[data-llm-local-key]').forEach(inp => {
|
||||||
|
const v = savedEngineUrls[inp.dataset.llmLocalKey];
|
||||||
|
if (v) inp.value = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore LLM URLs for refinement and conversation panels
|
||||||
|
const refineInp = $('refine-llm-url');
|
||||||
|
if (refineInp && s.refine_llm_url) refineInp.value = s.refine_llm_url;
|
||||||
|
const convInp = $('conv-llm-url');
|
||||||
|
if (convInp && s.conv_llm_url) convInp.value = s.conv_llm_url;
|
||||||
$('s-tts-stream-url').value = s.tts_stream_url || '';
|
$('s-tts-stream-url').value = s.tts_stream_url || '';
|
||||||
$('s-customvoice-url').value = s.customvoice_url || 'http://host.docker.internal:8022';
|
$('s-customvoice-url').value = s.customvoice_url || 'http://host.docker.internal:8022';
|
||||||
$('s-nvidia-router-url').value = s.nvidia_router_url || 'http://host.docker.internal:8090';
|
$('s-nvidia-router-url').value = s.nvidia_router_url || 'http://host.docker.internal:8090';
|
||||||
@ -6842,11 +6872,15 @@ loadLocalContainers();
|
|||||||
|
|
||||||
// ── Custom engine card storage helpers ────────────────────────────────────
|
// ── Custom engine card storage helpers ────────────────────────────────────
|
||||||
function loadCustomEngineCards() {
|
function loadCustomEngineCards() {
|
||||||
|
if (_appSettings && Array.isArray(_appSettings.custom_engine_cards) && _appSettings.custom_engine_cards.length)
|
||||||
|
return _appSettings.custom_engine_cards;
|
||||||
try { return JSON.parse(localStorage.getItem('engines-custom-cards') || '[]'); }
|
try { return JSON.parse(localStorage.getItem('engines-custom-cards') || '[]'); }
|
||||||
catch (e) { return []; }
|
catch (e) { return []; }
|
||||||
}
|
}
|
||||||
function saveCustomEngineCards(cards) {
|
function saveCustomEngineCards(cards) {
|
||||||
localStorage.setItem('engines-custom-cards', JSON.stringify(cards));
|
localStorage.setItem('engines-custom-cards', JSON.stringify(cards));
|
||||||
|
if (_appSettings) _appSettings.custom_engine_cards = cards;
|
||||||
|
_patchSettings({ custom_engine_cards: cards });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings key per docker container name; role fallback for custom cards
|
// Settings key per docker container name; role fallback for custom cards
|
||||||
@ -7303,9 +7337,9 @@ document.querySelectorAll('.dc-refresh-btn').forEach(b => b.addEventListener('cl
|
|||||||
if (!card) return;
|
if (!card) return;
|
||||||
const btn = card.querySelector('.llm-local-ping');
|
const btn = card.querySelector('.llm-local-ping');
|
||||||
|
|
||||||
const savedUrl = localStorage.getItem('llm-local-url-' + key);
|
const savedUrl = (_appSettings && _appSettings.engine_local_urls && _appSettings.engine_local_urls[key]) || localStorage.getItem('llm-local-url-' + key);
|
||||||
if (savedUrl) inp.value = savedUrl;
|
if (savedUrl) inp.value = savedUrl;
|
||||||
inp.addEventListener('input', () => { localStorage.setItem('llm-local-url-' + key, inp.value); });
|
inp.addEventListener('input', () => { localStorage.setItem('llm-local-url-' + key, inp.value); _saveEngineLocalUrls(); });
|
||||||
|
|
||||||
if (localStorage.getItem('llm-local-con-' + key) === '1') applyCardState(card, key, true, false);
|
if (localStorage.getItem('llm-local-con-' + key) === '1') applyCardState(card, key, true, false);
|
||||||
|
|
||||||
@ -7979,10 +8013,15 @@ let _refineOriginal = null;
|
|||||||
(function initLlmRefinement() {
|
(function initLlmRefinement() {
|
||||||
const inp = $('refine-llm-url');
|
const inp = $('refine-llm-url');
|
||||||
if (!inp) return;
|
if (!inp) return;
|
||||||
const saved = localStorage.getItem('refine-llm-url');
|
// loadSettings() will overwrite with the server value; localStorage is the fast initial fallback
|
||||||
|
const saved = (_appSettings && _appSettings.refine_llm_url) || localStorage.getItem('refine-llm-url');
|
||||||
if (saved) inp.value = saved;
|
if (saved) inp.value = saved;
|
||||||
else inp.value = localStorage.getItem('llm-local-url-ollama') || 'http://localhost:11434/v1';
|
else inp.value = (_appSettings && _appSettings.engine_local_urls && _appSettings.engine_local_urls['ollama']) || localStorage.getItem('llm-local-url-ollama') || 'http://localhost:11434/v1';
|
||||||
inp.addEventListener('input', () => localStorage.setItem('refine-llm-url', inp.value));
|
inp.addEventListener('input', () => {
|
||||||
|
localStorage.setItem('refine-llm-url', inp.value);
|
||||||
|
_patchSettings({ refine_llm_url: inp.value });
|
||||||
|
if (_appSettings) _appSettings.refine_llm_url = inp.value;
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function updateRefineButtonState() {
|
function updateRefineButtonState() {
|
||||||
@ -8149,6 +8188,13 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
|
|||||||
const turnHistory = $('conv-turn-history');
|
const turnHistory = $('conv-turn-history');
|
||||||
if (!chatWindow || !micBtn) return;
|
if (!chatWindow || !micBtn) return;
|
||||||
|
|
||||||
|
// Restore conv LLM URL from server settings
|
||||||
|
if (llmUrlInp && _appSettings && _appSettings.conv_llm_url) llmUrlInp.value = _appSettings.conv_llm_url;
|
||||||
|
if (llmUrlInp) llmUrlInp.addEventListener('input', () => {
|
||||||
|
_patchSettings({ conv_llm_url: llmUrlInp.value });
|
||||||
|
if (_appSettings) _appSettings.conv_llm_url = llmUrlInp.value;
|
||||||
|
});
|
||||||
|
|
||||||
let mediaRecorder = null;
|
let mediaRecorder = null;
|
||||||
let recChunks = [];
|
let recChunks = [];
|
||||||
let recTimerInterval = null;
|
let recTimerInterval = null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user