Spaces:
Running on Zero
Running on Zero
| () => { | |
| let PAPERS = []; | |
| let cur = null; | |
| let pile = []; // indices into items, top of pile first | |
| let held = []; // indices, newest first | |
| let items = []; // [{text, kind}] | |
| let busy = false; | |
| let jid = null; | |
| let epoch = 0; | |
| let animLock = false; | |
| const $ = (id) => document.getElementById(id); | |
| function esc(t) { const d = document.createElement('div'); d.textContent = t; return d.innerHTML; } | |
| function shuffle(a) { | |
| for (let i = a.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [a[i], a[j]] = [a[j], a[i]]; | |
| } | |
| return a; | |
| } | |
| function counter() { | |
| $('ivx-counter').textContent = pile.length + ' in the pile \u00b7 ' + held.length + ' held'; | |
| $('ivx-gen').disabled = busy || held.length === 0; | |
| $('ivx-heldhead').hidden = held.length === 0; | |
| } | |
| function showTop() { | |
| const card = $('ivx-card'); | |
| card.classList.remove('fly-left', 'fly-right'); | |
| if (!pile.length) { | |
| card.querySelector('.kind').textContent = ''; | |
| card.querySelector('.txt').textContent = held.length | |
| ? 'Pile empty. Write, or remove a held card to reconsider it.' | |
| : 'Pile empty.'; | |
| counter(); | |
| return; | |
| } | |
| const it = items[pile[0]]; | |
| card.querySelector('.kind').textContent = it.kind; | |
| card.querySelector('.txt').textContent = it.text; | |
| counter(); | |
| } | |
| function renderHeld() { | |
| const box = $('ivx-held'); | |
| box.innerHTML = ''; | |
| held.forEach((idx) => { | |
| const el = document.createElement('div'); | |
| el.className = 'hcard'; | |
| el.innerHTML = '<div class="txt">' + esc(items[idx].text) + | |
| '</div><button class="rm" title="remove, back into the pile">✕</button>'; | |
| el.querySelector('.rm').addEventListener('click', () => { | |
| held = held.filter(h => h !== idx); | |
| pile.push(idx); | |
| renderHeld(); showTop(); | |
| }); | |
| box.appendChild(el); | |
| }); | |
| counter(); | |
| } | |
| function verdict(dir) { | |
| if (animLock || !pile.length) return; | |
| animLock = true; | |
| const card = $('ivx-card'); | |
| card.classList.add(dir === 'hold' ? 'fly-right' : 'fly-left'); | |
| setTimeout(() => { | |
| const idx = pile.shift(); | |
| if (dir === 'hold') { held.unshift(idx); renderHeld(); } | |
| else { pile.push(idx); } | |
| showTop(); | |
| animLock = false; | |
| }, 240); | |
| } | |
| function pickPaper(pid) { | |
| cur = PAPERS.find(p => p.id === pid); | |
| epoch += 1; | |
| items = cur.rhetorical.map(t => ({ text: t, kind: 'rhetorical' })) | |
| .concat(cur.content.map(t => ({ text: t, kind: 'content' }))); | |
| pile = shuffle(items.map((_, i) => i)); | |
| held = []; | |
| document.querySelectorAll('#ivx-tabs button').forEach(b => | |
| b.classList.toggle('on', b.dataset.pid === pid)); | |
| $('ivx-ptitle').textContent = cur.title; | |
| $('ivx-out').hidden = true; | |
| $('ivx-empty').hidden = false; | |
| $('ivx-think').textContent = ''; | |
| $('ivx-abs').textContent = ''; | |
| $('ivx-bars').innerHTML = ''; | |
| renderHeld(); showTop(); | |
| } | |
| function setStatus(t) { | |
| const s = $('ivx-status'); | |
| if (!t) { s.hidden = true; s.textContent = ''; return; } | |
| s.hidden = false; s.textContent = t; | |
| } | |
| function bars(sel, scores) { | |
| return sel.map((t, i) => { | |
| const s = scores[String(i + 1)] || scores[i + 1]; | |
| const pct = s ? s / 5 * 100 : 0; | |
| const color = (s || 0) >= 4 ? 'var(--hold)' : (s || 0) === 3 ? '#d9a514' : '#cc4b4b'; | |
| return '<div class="barrow"><div class="bartext" title="' + esc(t) + '">' + esc(t) + | |
| '</div><div class="bartrack"><div class="barfill" style="width:' + pct + | |
| '%;background:' + color + '"></div></div><div class="barnum">' + (s ? s + '/5' : '?') + | |
| '</div></div>'; | |
| }).join(''); | |
| } | |
| function bridgeCall(inputId, btnId, value) { | |
| const inp = document.querySelector('#' + inputId + ' textarea, #' + inputId + ' input'); | |
| const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value') || | |
| Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value'); | |
| setter.set.call(inp, value); | |
| inp.dispatchEvent(new Event('input', { bubbles: true })); | |
| setTimeout(() => document.querySelector('#' + btnId + ' button, button#' + btnId).click(), 30); | |
| } | |
| function outValue() { | |
| const el = document.querySelector('#ivx-bridge-out textarea, #ivx-bridge-out input'); | |
| return el ? el.value : ''; | |
| } | |
| async function restCall(name, args) { | |
| const r = await fetch('gradio_api/call/' + name, { | |
| method: 'POST', headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ data: args }) }); | |
| const j = await r.json(); | |
| const r2 = await fetch('gradio_api/call/' + name + '/' + j.event_id); | |
| const t = await r2.text(); | |
| const lines = t.split('\n').filter(x => x.startsWith('data:')); | |
| if (!lines.length) throw new Error('no data from ' + name); | |
| return JSON.parse(lines[lines.length - 1].slice(5))[0]; | |
| } | |
| function run() { | |
| if (busy || !cur || !held.length) return; | |
| busy = true; | |
| const myEpoch = epoch; | |
| const sel = held.map(i => items[i].text); | |
| jid = 'j' + Math.random().toString(36).slice(2); | |
| $('ivx-empty').hidden = true; | |
| $('ivx-out').hidden = false; | |
| $('ivx-thinkbox').open = true; | |
| $('ivx-think').textContent = ''; | |
| $('ivx-abs').textContent = ''; | |
| $('ivx-bars').innerHTML = ''; | |
| setStatus('waking the model\u2026'); | |
| counter(); | |
| const lastRaw = outValue(); | |
| bridgeCall('ivx-bridge-in', 'ivx-bridge-btn', | |
| JSON.stringify({ jid: jid, paper_id: cur.id, intuitions: sel })); | |
| const poll = setInterval(async () => { | |
| if (epoch !== myEpoch) { clearInterval(poll); finish(); return; } | |
| let raw = outValue(); | |
| if (!raw || raw === lastRaw) { | |
| try { raw = await restCall('peek', [jid]); } catch (e) { /* keep polling */ } | |
| } | |
| if (!raw || raw === lastRaw) return; | |
| let ob; | |
| try { ob = JSON.parse(raw); } catch (e) { return; } | |
| if (ob.jid !== jid) return; | |
| if (ob.phase === 'error') { setStatus(ob.msg); clearInterval(poll); finish(); return; } | |
| if (ob.thinking) $('ivx-think').textContent = ob.thinking; | |
| if (ob.abstract) $('ivx-abs').textContent = ob.abstract; | |
| setStatus(ob.abstract ? 'writing the abstract\u2026' : 'thinking\u2026'); | |
| const tb = $('ivx-think'); | |
| tb.scrollTop = tb.scrollHeight; | |
| if (ob.phase === 'done') { | |
| $('ivx-bars').innerHTML = bars(sel, ob.scores || {}); | |
| $('ivx-thinkbox').open = false; | |
| setStatus('done'); | |
| clearInterval(poll); finish(); | |
| } | |
| }, 800); | |
| function finish() { busy = false; counter(); } | |
| } | |
| async function init() { | |
| try { | |
| PAPERS = JSON.parse(await restCall('papers', [])); | |
| const tabs = $('ivx-tabs'); | |
| PAPERS.forEach(p => { | |
| const b = document.createElement('button'); | |
| b.textContent = p.title.length > 42 ? p.title.slice(0, 40) + '\u2026' : p.title; | |
| b.dataset.pid = p.id; | |
| b.addEventListener('click', () => pickPaper(p.id)); | |
| tabs.appendChild(b); | |
| }); | |
| $('ivx-gen').addEventListener('click', run); | |
| $('ivx-pass').addEventListener('click', () => verdict('pass')); | |
| $('ivx-hold').addEventListener('click', () => verdict('hold')); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'ArrowLeft') verdict('pass'); | |
| if (e.key === 'ArrowRight') verdict('hold'); | |
| }); | |
| $('ivx-ver').textContent = 'VERSION'; | |
| pickPaper(PAPERS[0].id); | |
| } catch (e) { | |
| const pt = $('ivx-ptitle'); | |
| if (pt) pt.textContent = 'The page failed to load its data. Refresh to retry. (' + e.message + ')'; | |
| } | |
| } | |
| const waiter = setInterval(() => { | |
| if (document.getElementById('ivx-app')) { clearInterval(waiter); init(); } | |
| }, 200); | |
| } | |