| |
|
|
| async function api(name, data) { |
| const res = await fetch(`/gradio_api/call/${name}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ data }), |
| }); |
| if (!res.ok) throw new Error(`API ${name} failed: ${res.status}`); |
| const { event_id } = await res.json(); |
| const stream = await fetch(`/gradio_api/call/${name}/${event_id}`); |
| const reader = stream.body.getReader(); |
| const dec = new TextDecoder(); |
| let buf = ''; |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| buf += dec.decode(value, { stream: true }); |
| } |
| const lines = buf.trim().split('\n').filter(Boolean); |
| const last = JSON.parse(lines[lines.length - 1].replace(/^data:\s*/, '')); |
| |
| return Array.isArray(last) ? last[0] : last; |
| } |
|
|
| const state = { sessionId: null, playerName: '', busy: false }; |
|
|
| function $(id) { return document.getElementById(id); } |
|
|
| function renderChat(chat) { |
| const log = $('chat-log'); |
| log.innerHTML = ''; |
| (chat || []).forEach((m) => { |
| const div = document.createElement('div'); |
| div.className = `chat-msg ${m.role === 'user' ? 'user' : 'bot'}`; |
| if (m.role === 'user') { |
| div.textContent = m.content; |
| } else { |
| div.innerHTML = m.content; |
| } |
| log.appendChild(div); |
| }); |
| log.scrollTop = log.scrollHeight; |
| } |
|
|
| function renderModels(models, selected) { |
| const row = $('model-row'); |
| row.innerHTML = ''; |
| (models || []).forEach((m) => { |
| const label = document.createElement('label'); |
| const input = document.createElement('input'); |
| input.type = 'radio'; |
| input.name = 'model'; |
| input.value = m.id; |
| input.checked = m.id === selected; |
| input.addEventListener('change', async () => { |
| if (!state.sessionId || !input.checked) return; |
| const out = await api('set_model', [state.sessionId, m.id]); |
| $('hud').innerHTML = out.hud_html || ''; |
| }); |
| label.append(input, document.createTextNode(m.label)); |
| row.appendChild(label); |
| }); |
| } |
|
|
| function setBusy(on) { |
| state.busy = on; |
| $('send-btn').disabled = on; |
| $('guess-btn').disabled = on; |
| } |
|
|
| async function apply(out) { |
| if (out.error) { $('result-area').textContent = out.error; return; } |
| state.sessionId = out.session_id || state.sessionId; |
| if (out.hud_html) $('hud').innerHTML = out.hud_html; |
| if (out.chat) renderChat(out.chat); |
| if (out.result_html !== undefined) $('result-area').innerHTML = out.result_html || ''; |
| if (out.hud_html && (out.hud_html.includes('flash-win') || out.hud_html.includes('scorecard'))) { |
| if (window.dojoConfetti) window.dojoConfetti(); |
| if (window.dojoChime) window.dojoChime(); |
| } |
| if (out.hud_html && out.hud_html.includes('flash-block') && window.dojoBuzz) window.dojoBuzz(); |
| } |
|
|
| async function init() { |
| const out = await api('init_game', []); |
| state.sessionId = out.session_id; |
| $('hud').innerHTML = out.hud_html || ''; |
| renderChat(out.chat || []); |
| renderModels(out.models, out.default_model); |
| if (out.demo_mode) $('demo-banner').style.display = 'block'; |
| } |
|
|
| async function send() { |
| const msg = $('msg-input').value.trim(); |
| if (!msg || state.busy) return; |
| setBusy(true); |
| const stir = document.createElement('div'); |
| stir.className = 'chat-msg bot stirring'; |
| stir.innerHTML = '<span class="spinner"></span> 🌫️ <i>the guardian stirs…</i>'; |
| $('chat-log').appendChild(stir); |
| $('msg-input').value = ''; |
| try { |
| const out = await api('send_message', [state.sessionId, msg, state.playerName]); |
| await apply(out); |
| } catch (err) { |
| stir.remove(); |
| $('result-area').textContent = String(err); |
| } finally { setBusy(false); } |
| } |
|
|
| async function guess() { |
| const g = $('guess-input').value.trim(); |
| if (!g || state.busy) return; |
| setBusy(true); |
| try { |
| const out = await api('submit_guess', [state.sessionId, g, state.playerName]); |
| $('guess-input').value = ''; |
| await apply(out); |
| } finally { setBusy(false); } |
| } |
|
|
| async function action(name) { |
| if (state.busy) return; |
| setBusy(true); |
| try { await apply(await api(name, [state.sessionId])); } |
| finally { setBusy(false); } |
| } |
|
|
| async function loadLeaderboard() { |
| const rows = await api('get_leaderboard', []); |
| const tbody = $('lb-body'); |
| tbody.innerHTML = ''; |
| (rows || []).forEach((r) => { |
| const tr = document.createElement('tr'); |
| r.forEach((c) => { const td = document.createElement('td'); td.textContent = c; tr.appendChild(td); }); |
| tbody.appendChild(tr); |
| }); |
| } |
|
|
| function bindTabs() { |
| document.querySelectorAll('.tab-btn').forEach((btn) => { |
| btn.addEventListener('click', () => { |
| document.querySelectorAll('.tab-btn').forEach((b) => b.classList.remove('active')); |
| document.querySelectorAll('.tab-panel').forEach((p) => p.classList.remove('active')); |
| btn.classList.add('active'); |
| $(`panel-${btn.dataset.tab}`).classList.add('active'); |
| if (btn.dataset.tab === 'leaderboard') loadLeaderboard(); |
| }); |
| }); |
| } |
|
|
| document.addEventListener('DOMContentLoaded', () => { |
| bindTabs(); |
| $('send-btn').addEventListener('click', send); |
| $('guess-btn').addEventListener('click', guess); |
| $('msg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }); |
| $('guess-input').addEventListener('keydown', (e) => { |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); guess(); } |
| }); |
| $('name-input').addEventListener('input', (e) => { state.playerName = e.target.value; }); |
| $('restart-level').addEventListener('click', () => action('restart_level')); |
| $('restart-game').addEventListener('click', () => action('restart_game')); |
| $('concede').addEventListener('click', () => action('concede')); |
| $('lb-refresh').addEventListener('click', loadLeaderboard); |
| document.addEventListener('keydown', (e) => { |
| if (e.key === '/' && !['INPUT','TEXTAREA'].includes((document.activeElement||{}).tagName)) { |
| e.preventDefault(); $('msg-input').focus(); |
| } |
| }); |
| init().catch((err) => { $('result-area').textContent = String(err); }); |
| }); |
|
|