hopchain-v34-viewer / index.html
ngqtrung's picture
HopChain v3.4 viewer: 7,900 questions / 1,730 videos, streams video from Choiszt/video_res (part 6)
3ceac4b verified
Raw
History Blame Contribute Delete
4.59 kB
<!DOCTYPE html>
<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 => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[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>