/** * GMIIE Query Console — shared Fetch / Copy curl / Explain / Listen helpers. * Used by query.html and global-governance.html. */ (function (global) { const DEFAULTS = { askEndpoint: '/api/gmiie/ask', explainQuestion: 'Explain this GMIIE API response for an institutional reader: what matters, which rings/desks, and what to watch next.', }; function $(id) { return document.getElementById(id); } function buildUrl(active, getParam) { const base = location.origin; let path = active.path; const params = new URLSearchParams(); (active.params || []).forEach(function (key) { const val = getParam(key); if (val != null && val !== '') params.set(key, val); }); if (active.params && active.params.includes('jurisdiction') && !params.has('tz')) { params.set('tz', Intl.DateTimeFormat().resolvedOptions().timeZone); } const q = params.toString(); return base + path + (q ? '?' + q : ''); } function buildCurl(active, url) { let curl = 'curl -s "' + url + '"'; if (active.method === 'POST') { const body = JSON.stringify(active.body || {}); curl = 'curl -s -X POST "' + url + '" -H "Content-Type: application/json" -d \'' + body.replace(/'/g, "'\\''") + "'"; } return curl; } function createConsole(options) { const cfg = { ...DEFAULTS, ...(options || {}) }; const endpoints = cfg.endpoints || []; let active = endpoints[0] || null; let lastJson = null; let lastUrl = ''; const listEl = $(cfg.listId || 'ep-list'); const activeEl = $(cfg.activeEndpointId || 'active-endpoint'); const statusEl = $(cfg.statusId || 'req-status'); const jsonEl = $(cfg.jsonId || 'json-out'); const curlEl = $(cfg.curlId || 'curl-box'); const explainEl = $(cfg.explainId || 'explain-out'); function setActive(ep) { active = ep; if (listEl) { listEl.querySelectorAll('.ep-btn').forEach(function (el, i) { el.classList.toggle('active', endpoints[i] === ep); }); } if (activeEl && ep) activeEl.textContent = ep.method + ' ' + ep.path; refreshCurl(); if (typeof cfg.onActiveChange === 'function') cfg.onActiveChange(ep); } function refreshCurl() { if (!active) return ''; lastUrl = buildUrl(active, cfg.getParam || function () { return ''; }); const curl = buildCurl(active, lastUrl); if (curlEl) curlEl.textContent = curl; return curl; } function filterJson(data) { if (typeof cfg.filterJson === 'function') return cfg.filterJson(data); return data; } async function fetchData() { if (!active) return null; if (statusEl) statusEl.textContent = 'Fetching…'; refreshCurl(); try { const opts = { signal: AbortSignal.timeout(15000) }; if (active.method === 'POST') { opts.method = 'POST'; opts.headers = { 'Content-Type': 'application/json' }; opts.body = JSON.stringify(active.body || {}); } const r = await fetch(lastUrl || buildUrl(active, cfg.getParam || function () { return ''; }), opts); const data = r.ok ? await r.json() : { error: r.status, statusText: r.statusText }; lastJson = filterJson(data); if (jsonEl) jsonEl.textContent = JSON.stringify(lastJson, null, 2); if (statusEl) statusEl.textContent = r.ok ? 'OK · ' + new Date().toISOString() : 'Error ' + r.status; if (typeof cfg.onFetch === 'function') cfg.onFetch(lastJson, r.ok); return lastJson; } catch (e) { lastJson = { error: e.message }; if (jsonEl) jsonEl.textContent = JSON.stringify(lastJson, null, 2); if (statusEl) statusEl.textContent = 'Failed'; return lastJson; } } async function explain(extraContext) { if (!explainEl) return; explainEl.style.display = 'block'; explainEl.textContent = 'Calling /api/gmiie/ask…'; const question = cfg.explainQuestion || DEFAULTS.explainQuestion; const context = { page: cfg.page || document.body.getAttribute('data-gmiie-page') || 'query-console', endpoint: active?.path, payload: lastJson, ...(extraContext || {}), }; try { const r = await fetch(cfg.askEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, context }), }); const d = r.ok ? await r.json() : null; explainEl.textContent = d?.answer || 'Explain unavailable — inspect JSON above.'; } catch (e) { explainEl.textContent = 'Explain failed: ' + e.message; } } function listen(textOverride) { const text = textOverride || (lastJson ? 'Global governance desk summary. ' + JSON.stringify(lastJson).slice(0, 4000) : ''); const ctx = { page: cfg.page || 'global-governance', lang: cfg.getParam ? cfg.getParam('lang') : 'en', jurisdiction: cfg.getParam ? cfg.getParam('jurisdiction') : null, type: 'legislative', }; if (global.GrokVoice) { global.GrokVoice.speak(text, null, { voice_id: cfg.voiceId || 'daniel', endpoint: cfg.ttsEndpoint || '/api/tts', provider: 'auto', smart: true, context: ctx, }); return; } const u = new SpeechSynthesisUtterance(String(text).slice(0, 5000)); u.rate = 0.87; global.speechSynthesis?.speak(u); } if (listEl) { endpoints.forEach(function (ep) { const b = document.createElement('button'); b.type = 'button'; b.className = 'ep-btn' + (ep === active ? ' active' : ''); b.innerHTML = '' + ep.method + '' + ep.label; b.addEventListener('click', function () { setActive(ep); }); listEl.appendChild(b); }); } if ($(cfg.fetchBtnId || 'btn-fetch')) { $(cfg.fetchBtnId || 'btn-fetch').addEventListener('click', fetchData); } if ($(cfg.copyJsonBtnId || 'btn-copy-json')) { $(cfg.copyJsonBtnId || 'btn-copy-json').addEventListener('click', function () { if (lastJson) navigator.clipboard.writeText(JSON.stringify(lastJson, null, 2)); }); } if ($(cfg.copyCurlBtnId || 'btn-copy-curl')) { $(cfg.copyCurlBtnId || 'btn-copy-curl').addEventListener('click', function () { navigator.clipboard.writeText(refreshCurl()); }); } if ($(cfg.explainBtnId || 'btn-explain')) { $(cfg.explainBtnId || 'btn-explain').addEventListener('click', function () { explain(cfg.explainContext); }); } if ($(cfg.listenBtnId || 'btn-listen')) { $(cfg.listenBtnId || 'btn-listen').addEventListener('click', function () { listen(cfg.listenText); }); } if (active) setActive(active); if (cfg.autoFetch !== false && active) fetchData(); return { setActive, fetchData, explain, listen, refreshCurl, getLastJson: function () { return lastJson; }, getActive: function () { return active; }, }; } global.GMIIEQueryConsole = { create: createConsole, buildUrl, buildCurl }; })(window);