// Shared voice-library cache. The backend index is authoritative; this layer // prevents multiple frontend panels from starting duplicate scans in one tab. (function installVoiceLibraryCache() { const KEY = 'ttsvc_voice_library_cache_v2'; const MAX_AGE_MS = 30_000; let inFlight = null; function read() { try { const value = JSON.parse(sessionStorage.getItem(KEY) || 'null'); if (!value || !Array.isArray(value.voices) || !Number.isFinite(value.savedAt)) return null; return Date.now() - value.savedAt <= MAX_AGE_MS ? value.voices : null; } catch (_) { return null; } } function write(voices) { try { sessionStorage.setItem(KEY, JSON.stringify({ savedAt: Date.now(), voices })); } catch (_) {} } function invalidate() { try { sessionStorage.removeItem(KEY); } catch (_) {} } async function fetchVoices(options = {}) { const refresh = options === true || options.refresh === true; if (!refresh) { const cached = read(); if (cached) return cached; } if (inFlight) return inFlight; inFlight = fetch('/api/voices' + (refresh ? '?refresh=1' : ''), { cache: 'no-store' }) .then(async response => { if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.detail || response.statusText); } const voices = await response.json(); if (!Array.isArray(voices)) throw new Error('Voice library response was not an array'); write(voices); return voices; }) .finally(() => { inFlight = null; }); return inFlight; } window.fetchVoiceLibrary = fetchVoices; window.invalidateVoiceLibraryCache = invalidate; })();