/** * GMIIE Voice Sync — hydrates read-aloud scripts from /api/gmiie/voice/scripts. * When articles change, voice text changes automatically (same 8h pipeline edition). */ (function (global) { var scripts = {}; var meta = { generated_at: null, mode: 'pending' }; var loaded = false; function applyScript(key, entry) { if (!entry || !entry.text) return; scripts[key] = entry; } function syncFromPayload(data) { if (!data) return; meta.generated_at = data.generated_at || null; meta.mode = data.mode || 'live'; meta.edition_id = data.edition_id || null; var map = data.scripts || {}; Object.keys(map).forEach(function (k) { applyScript(k, map[k]); }); loaded = true; global.GMIIE_VOICE = scripts; global.VOICE = Object.fromEntries( Object.entries(scripts).map(function (pair) { return [pair[0], pair[1].text]; }) ); try { global.dispatchEvent( new CustomEvent('gmiie-voice-updated', { detail: { scripts: scripts, meta: meta } }) ); } catch (_) {} } function fetchScripts() { return fetch('/api/gmiie/voice/scripts', { cache: 'no-store', signal: AbortSignal.timeout(12000) }) .then(function (r) { return r.ok ? r.json() : null; }) .then(syncFromPayload) .catch(function () { meta.mode = 'static-fallback'; }); } function speakKey(key, btn) { var entry = scripts[key]; var text = entry && entry.text; if (!text) { var fallback = global.VOICE && global.VOICE[key]; text = fallback; } if (!text || !global.GrokVoice) return; var voiceId = (entry && entry.voice_id) || 'daniel'; var authorName = (entry && entry.author_name) || 'GMIIE Intelligence Desk'; global.GrokVoice.speak(text, btn || null, { voice_id: voiceId, context: { type: 'intelligence_article', section: key, author_id: entry && entry.author_id, author_name: authorName, article_id: entry && entry.article_id, title: entry && entry.title, }, }); } global.vbSpeak = function (key, btn) { if (!loaded) { fetchScripts().finally(function () { speakKey(key, btn); }); return; } speakKey(key, btn); }; global.GMIIEVoiceSync = { refresh: fetchScripts, speak: speakKey, getScripts: function () { return scripts; }, getMeta: function () { return meta; }, }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fetchScripts); } else { fetchScripts(); } })(typeof window !== 'undefined' ? window : globalThis);