Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"/> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"/> | |
| <title>HopChain v3.4 — 7,900 verified multi-hop video questions (1080p)</title> | |
| <style> | |
| :root{--bg:#0f1116;--card:#1a1d24;--border:#2a2f3a;--fg:#e6e8ec;--muted:#9aa0aa;--acc:#3b82f6} | |
| @media (prefers-color-scheme: light){:root{--bg:#f6f7f9;--card:#fff;--border:#dfe3e8;--fg:#1a1d24;--muted:#6b7280}} | |
| *{box-sizing:border-box} | |
| body{margin:0;background:var(--bg);color:var(--fg);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.45} | |
| header{position:sticky;top:0;z-index:5;background:var(--bg);border-bottom:1px solid var(--border);padding:12px 16px} | |
| h1{font-size:17px;margin:0 0 6px} | |
| .sub{color:var(--muted);font-size:12px;margin-bottom:10px} | |
| .sub a{color:var(--acc)} | |
| select{background:var(--card);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:7px 10px;font-size:14px;min-width:260px;max-width:90vw} | |
| main{max-width:1200px;margin:0 auto;padding:16px} | |
| .row{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap} | |
| video{width:460px;max-width:92vw;border-radius:8px;background:#000;position:sticky;top:120px} | |
| .qs{flex:1;min-width:300px} | |
| .q{margin:0 0 14px;padding:12px 14px;border:1px solid var(--border);border-radius:8px;background:var(--card)} | |
| .q .qt{white-space:pre-wrap;font-size:14px} | |
| .q .ans{margin-top:8px;font-size:13px} | |
| .q .ans b{color:var(--acc);font-size:15px} | |
| .q .ans button{margin-left:8px;padding:2px 10px;font-size:12px;border-radius:5px;cursor:pointer;border:1px solid var(--border);background:var(--bg);color:var(--fg)} | |
| .hops{margin-top:8px;font-size:12px;color:var(--muted);display:none} | |
| .hops div{margin:3px 0;padding-left:10px;border-left:2px solid var(--border)} | |
| .legend{font-size:11px;color:var(--muted)} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>HopChain v3.4 — verified multi-hop video reasoning (1080p captions, 397B generator)</h1> | |
| <div class="sub">7,900 questions over 1,730 videos. Every answer is a rule-verifiable integer; every question survived a | |
| grounding judge + a too-easy probe. Data: <a href="https://huggingface.co/datasets/ngqtrung/hopchain-v34-1080p">ngqtrung/hopchain-v34-1080p</a> | |
| · videos stream from <a href="https://huggingface.co/datasets/Choiszt/video_res">Choiszt/video_res</a>. | |
| Answers are hidden until you click — try solving from the video first.</div> | |
| <select id="vid"></select> | |
| <span class="legend" id="count"></span> | |
| </header> | |
| <main id="out">Loading…</main> | |
| <script> | |
| const VIDEO_BASE = "https://huggingface.co/datasets/Choiszt/video_res/resolve/main/videos_1080p"; | |
| const esc = s => (s||"").replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c])); | |
| const out = document.getElementById('out'), sel = document.getElementById('vid'), cnt = document.getElementById('count'); | |
| async function show(vid){ | |
| out.textContent = "Loading " + vid + "…"; | |
| let doc; | |
| try { doc = await (await fetch(`data/${vid}.json`)).json(); } | |
| catch(e){ out.textContent = "Failed to load " + vid; return; } | |
| const qs = doc.questions.map((q,i) => ` | |
| <div class="q"> | |
| <div class="qt"><b>Q${i+1}.</b> ${esc(q.question)}</div> | |
| <div class="ans">answer: <b id="a${i}" style="display:none">${esc(String(q.answer))}</b> | |
| <button onclick="document.getElementById('a${i}').style.display='inline';this.remove()">reveal</button> | |
| ${q.hops && q.hops.length ? `<button onclick="const h=document.getElementById('h${i}');h.style.display=h.style.display==='block'?'none':'block'">hops</button>` : ``} | |
| </div> | |
| ${q.hops && q.hops.length ? `<div class="hops" id="h${i}">` + | |
| q.hops.map(h=>`<div>hop ${h.hop}: <i>${esc(h.scene)}</i> — ${esc(h.mapping)}</div>`).join("") + `</div>` : ``} | |
| </div>`).join(""); | |
| out.innerHTML = `<div class="row"> | |
| <div><video src="${VIDEO_BASE}/${vid}.mp4" controls preload="metadata"></video></div> | |
| <div class="qs"><div class="sub">${doc.questions.length} question(s) for <b>${esc(vid)}</b></div>${qs}</div> | |
| </div>`; | |
| } | |
| (async function(){ | |
| let man; | |
| try { man = await (await fetch('data/manifest.json')).json(); } | |
| catch(e){ out.textContent = "Failed to load manifest.json"; return; } | |
| man.sort((a,b)=> a.vid>b.vid?1:-1); | |
| cnt.textContent = ` ${man.length} videos · ${man.reduce((s,m)=>s+m.n,0)} questions`; | |
| sel.innerHTML = man.map(r=>`<option value="${r.vid}">${r.vid} (${r.n} q)</option>`).join(""); | |
| sel.addEventListener('change', ()=>show(sel.value)); | |
| if(man.length) show(man[0].vid); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |