whisperkey / static /game.js
chiruu12's picture
Deploy: working gr.Server frontend + review fixes
5a811e2 verified
Raw
History Blame Contribute Delete
6.2 kB
/* Whisperkey - custom frontend for gr.Server */
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*/, ''));
// gr.Server wraps each handler's return in a JSON array of outputs; we have one output per handler.
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); });
});