Spaces:
Running
Running
| /* MuDABench viewer — vanilla JS, two tabs: Corpus + Eval. */ | |
| (function () { | |
| "use strict"; | |
| var $ = function (id) { return document.getElementById(id); }; | |
| function esc(s) { | |
| if (s === null || s === undefined) return ""; | |
| return String(s) | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """); | |
| } | |
| function fmtVal(v) { | |
| if (v === null || v === undefined) return '<span class="fval-null">null</span>'; | |
| if (v === true) return '<span class="fval-bool-true">true</span>'; | |
| if (v === false) return '<span class="fval-bool-false">false</span>'; | |
| return esc(String(v)); | |
| } | |
| function shortId(id) { return id ? String(id).slice(0, 8) : ""; } | |
| var state = { | |
| mode: "corpus", | |
| corpus: [], // all docs | |
| corpusView: [], // filtered docs | |
| corpusIdx: 0, | |
| byId: {}, // id -> corpus doc | |
| questions: [], // all questions | |
| evalView: [], // filtered questions | |
| evalIdx: 0, | |
| }; | |
| /* ---------------- data loading ---------------- */ | |
| function loadAll() { | |
| return Promise.all([ | |
| fetch("corpus_index.json").then(function (r) { return r.json(); }), | |
| fetch("eval.json").then(function (r) { return r.json(); }), | |
| ]).then(function (res) { | |
| state.corpus = res[0] || []; | |
| state.questions = res[1] || []; | |
| state.corpus.forEach(function (d) { state.byId[d.id] = d; }); | |
| }); | |
| } | |
| /* ---------------- corpus tab ---------------- */ | |
| function populateCorpusFilters() { | |
| var types = {}, years = {}; | |
| state.corpus.forEach(function (d) { | |
| if (d.doctype) types[d.doctype] = true; | |
| if (d.year !== null && d.year !== undefined && d.year !== "") years[d.year] = true; | |
| }); | |
| var tf = $("docTypeFilter"), yf = $("yearFilter"); | |
| Object.keys(types).sort().forEach(function (t) { | |
| var o = document.createElement("option"); o.value = t; o.textContent = t; tf.appendChild(o); | |
| }); | |
| Object.keys(years).sort().forEach(function (y) { | |
| var o = document.createElement("option"); o.value = y; o.textContent = y; yf.appendChild(o); | |
| }); | |
| } | |
| function filterCorpus() { | |
| var q = $("corpusFilter").value.trim().toLowerCase(); | |
| var typ = $("docTypeFilter").value; | |
| var yr = $("yearFilter").value; | |
| state.corpusView = state.corpus.filter(function (d) { | |
| if (typ && d.doctype !== typ) return false; | |
| if (yr && String(d.year) !== yr) return false; | |
| if (q) { | |
| var hay = (d.title + " " + (d.symbol || "") + " " + (d.id || "")).toLowerCase(); | |
| if (hay.indexOf(q) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| if (state.corpusIdx >= state.corpusView.length) state.corpusIdx = 0; | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| function rebuildDocSelect() { | |
| var sel = $("docSelect"); | |
| sel.innerHTML = ""; | |
| state.corpusView.forEach(function (d, i) { | |
| var o = document.createElement("option"); | |
| o.value = String(i); | |
| o.textContent = d.title + " · " + shortId(d.id); | |
| sel.appendChild(o); | |
| }); | |
| sel.value = String(state.corpusIdx); | |
| } | |
| function renderCorpus() { | |
| var meta = $("docMetaCard"); | |
| var frame = $("pdfFrame"); | |
| if (!state.corpusView.length) { | |
| meta.innerHTML = '<div class="empty">No documents match the filter.</div>'; | |
| frame.removeAttribute("src"); | |
| $("corpusCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var d = state.corpusView[state.corpusIdx]; | |
| $("docSelect").value = String(state.corpusIdx); | |
| $("corpusCounter").textContent = (state.corpusIdx + 1) + " / " + state.corpusView.length; | |
| var pills = []; | |
| if (d.symbol) pills.push('<span class="meta-pill">Symbol: <b>' + esc(d.symbol) + "</b></span>"); | |
| if (d.year !== null && d.year !== undefined && d.year !== "") pills.push('<span class="meta-pill">Year: <b>' + esc(d.year) + "</b></span>"); | |
| if (d.doctype) pills.push('<span class="meta-pill">Type: <b>' + esc(d.doctype) + "</b></span>"); | |
| pills.push('<span class="meta-pill">Cited by <b>' + (d.n_refs || 0) + "</b> question(s)</span>"); | |
| pills.push('<span class="meta-pill mono">' + esc(d.id) + "</span>"); | |
| var link = '<a class="doc-link" href="' + esc(d.pdf) + '" target="_blank" rel="noopener">open PDF ↗</a>'; | |
| var fieldsHtml = ""; | |
| if (d.fields && d.fields.length) { | |
| var rows = d.fields.map(function (f) { | |
| var vals = (f.values || []).map(fmtVal).join(", "); | |
| return "<tr><td class=\"fname\">" + esc(f.name) + "</td>" + | |
| "<td class=\"fval\">" + (vals || "") + "</td>" + | |
| "<td class=\"fdesc\">" + esc(f.desc || "") + "</td></tr>"; | |
| }).join(""); | |
| fieldsHtml = | |
| '<table class="fields-table"><thead><tr>' + | |
| "<th>Field</th><th>Value(s)</th><th>Description</th>" + | |
| "</tr></thead><tbody>" + rows + "</tbody></table>"; | |
| } | |
| var refsHtml = ""; | |
| if (d.referenced_by && d.referenced_by.length) { | |
| var refPills = d.referenced_by.map(function (r) { | |
| return '<span class="ref-pill" data-qid="' + esc(r.qid) + '" data-ds="' + esc(r.dataset) + | |
| '">' + esc(shortId(r.qid)) + ' <span class="ds">' + esc(r.dataset) + "</span></span>"; | |
| }).join(""); | |
| refsHtml = | |
| '<details class="refs-wrap"><summary>Referenced by ' + d.referenced_by.length + | |
| " question(s)</summary><div class=\"ref-pills\">" + refPills + "</div></details>"; | |
| } | |
| meta.innerHTML = | |
| "<h2>" + esc(d.title) + "</h2>" + | |
| '<div class="meta-grid">' + pills.join("") + link + "</div>" + | |
| fieldsHtml + refsHtml; | |
| // wire ref pills -> jump to that question in the Eval tab | |
| Array.prototype.forEach.call(meta.querySelectorAll(".ref-pill"), function (el) { | |
| el.addEventListener("click", function () { | |
| setMode("eval"); | |
| selectQuestion(this.getAttribute("data-ds"), this.getAttribute("data-qid")); | |
| }); | |
| }); | |
| frame.src = d.pdf; | |
| } | |
| function selectDocById(id) { | |
| // reset corpus filters so the target doc is guaranteed visible | |
| $("corpusFilter").value = ""; | |
| $("docTypeFilter").value = ""; | |
| $("yearFilter").value = ""; | |
| state.corpusView = state.corpus.slice(); | |
| var idx = 0; | |
| for (var i = 0; i < state.corpusView.length; i++) { | |
| if (state.corpusView[i].id === id) { idx = i; break; } | |
| } | |
| state.corpusIdx = idx; | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| /* ---------------- eval tab ---------------- */ | |
| function filterEval() { | |
| var q = $("evalSearch").value.trim().toLowerCase(); | |
| var ds = $("datasetFilter").value; | |
| state.evalView = state.questions.filter(function (x) { | |
| if (ds && x.dataset !== ds) return false; | |
| if (q) { | |
| var docTitles = (x.docs || []).map(function (d) { return d.title + " " + (d.symbol || ""); }).join(" "); | |
| var hay = [x.question, x.final_answer, x.qid, docTitles].join(" ").toLowerCase(); | |
| if (hay.indexOf(q) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| if (state.evalIdx >= state.evalView.length) state.evalIdx = 0; | |
| rebuildQSelect(); | |
| renderEval(); | |
| } | |
| function rebuildQSelect() { | |
| var sel = $("qSelect"); | |
| sel.innerHTML = ""; | |
| state.evalView.forEach(function (x, i) { | |
| var o = document.createElement("option"); | |
| o.value = String(i); | |
| var q = (x.question || "").replace(/\s+/g, " "); | |
| if (q.length > 70) q = q.slice(0, 70) + "…"; | |
| o.textContent = "[" + x.dataset + "] " + q; | |
| sel.appendChild(o); | |
| }); | |
| sel.value = String(state.evalIdx); | |
| } | |
| function field(lbl, valHtml) { | |
| return '<div class="field"><div class="lbl">' + esc(lbl) + '</div><div class="val">' + valHtml + "</div></div>"; | |
| } | |
| function renderEval() { | |
| var card = $("evalCard"); | |
| if (!state.evalView.length) { | |
| card.innerHTML = '<div class="empty">No questions match the search.</div>'; | |
| $("evalCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var x = state.evalView[state.evalIdx]; | |
| $("qSelect").value = String(state.evalIdx); | |
| $("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length; | |
| var badges = | |
| '<span class="badge id">' + esc(x.qid) + "</span>" + | |
| '<span class="badge ds-' + esc(x.dataset) + '">' + esc(x.dataset) + "</span>"; | |
| // supporting facts (source_answer) | |
| var facts = x.source_answer || []; | |
| var factsHtml = facts.length | |
| ? '<ol class="facts-list">' + facts.map(function (f) { return "<li>" + esc(f) + "</li>"; }).join("") + "</ol>" | |
| : '<span style="color:var(--muted)">none</span>'; | |
| // supporting documents | |
| var docs = x.docs || []; | |
| var docsHtml = docs.map(function (d) { | |
| var fields = (d.fields || []).map(function (f) { | |
| return '<div class="doc-field"><span class="fn">' + esc(f.name) + '</span>' + | |
| '<span class="fv">' + fmtVal(f.value) + "</span>" + | |
| (f.desc ? '<span class="fd">' + esc(f.desc) + "</span>" : "") + "</div>"; | |
| }).join(""); | |
| return '<div class="doc-item">' + | |
| '<div class="doc-item-head">' + | |
| '<a class="doclink" data-doc-id="' + esc(d.id) + '">' + esc(d.title) + " ↗</a>" + | |
| '<span class="doc-id">' + esc(d.id) + "</span>" + | |
| "</div>" + | |
| (fields ? '<div class="doc-fields">' + fields + "</div>" : "") + | |
| "</div>"; | |
| }).join(""); | |
| card.innerHTML = | |
| '<div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(x.question) + "</h2>" + | |
| '<div class="answer-banner"><div class="lbl">Final answer (gold)</div><div class="val">' + | |
| esc(x.final_answer) + "</div></div>" + | |
| field("Supporting facts (source_answer)", factsHtml) + | |
| field("Supporting documents (" + docs.length + ")", docsHtml || | |
| '<span style="color:var(--muted)">none</span>'); | |
| // wire doc links -> jump to Corpus tab | |
| Array.prototype.forEach.call(card.querySelectorAll("a.doclink[data-doc-id]"), function (el) { | |
| el.addEventListener("click", function () { | |
| setMode("corpus"); | |
| selectDocById(this.getAttribute("data-doc-id")); | |
| }); | |
| }); | |
| } | |
| function selectQuestion(dataset, qid) { | |
| $("evalSearch").value = ""; | |
| $("datasetFilter").value = ""; | |
| state.evalView = state.questions.slice(); | |
| var idx = 0; | |
| for (var i = 0; i < state.evalView.length; i++) { | |
| if (state.evalView[i].qid === qid && state.evalView[i].dataset === dataset) { idx = i; break; } | |
| } | |
| state.evalIdx = idx; | |
| rebuildQSelect(); | |
| renderEval(); | |
| } | |
| /* ---------------- mode switching ---------------- */ | |
| function setMode(mode) { | |
| state.mode = mode; | |
| $("modeCorpusBtn").classList.toggle("active", mode === "corpus"); | |
| $("modeEvalBtn").classList.toggle("active", mode === "eval"); | |
| $("corpusControls").style.display = mode === "corpus" ? "" : "none"; | |
| $("evalControls").style.display = mode === "eval" ? "" : "none"; | |
| $("corpusView").style.display = mode === "corpus" ? "flex" : "none"; | |
| $("evalView").style.display = mode === "eval" ? "block" : "none"; | |
| } | |
| /* ---------------- wiring ---------------- */ | |
| function wire() { | |
| $("modeCorpusBtn").addEventListener("click", function () { setMode("corpus"); }); | |
| $("modeEvalBtn").addEventListener("click", function () { setMode("eval"); }); | |
| // corpus | |
| $("corpusFilter").addEventListener("input", filterCorpus); | |
| $("docTypeFilter").addEventListener("change", filterCorpus); | |
| $("yearFilter").addEventListener("change", filterCorpus); | |
| $("docSelect").addEventListener("change", function () { | |
| state.corpusIdx = parseInt(this.value, 10) || 0; | |
| renderCorpus(); | |
| }); | |
| $("corpusPrevBtn").addEventListener("click", function () { | |
| if (state.corpusIdx > 0) { state.corpusIdx--; renderCorpus(); } | |
| }); | |
| $("corpusNextBtn").addEventListener("click", function () { | |
| if (state.corpusIdx < state.corpusView.length - 1) { state.corpusIdx++; renderCorpus(); } | |
| }); | |
| // eval | |
| $("evalSearchBtn").addEventListener("click", filterEval); | |
| $("evalSearch").addEventListener("keydown", function (e) { if (e.key === "Enter") filterEval(); }); | |
| $("evalClearBtn").addEventListener("click", function () { | |
| $("evalSearch").value = ""; $("datasetFilter").value = ""; filterEval(); | |
| }); | |
| $("datasetFilter").addEventListener("change", filterEval); | |
| $("qSelect").addEventListener("change", function () { | |
| state.evalIdx = parseInt(this.value, 10) || 0; | |
| renderEval(); | |
| }); | |
| $("evalPrevBtn").addEventListener("click", function () { | |
| if (state.evalIdx > 0) { state.evalIdx--; renderEval(); } | |
| }); | |
| $("evalNextBtn").addEventListener("click", function () { | |
| if (state.evalIdx < state.evalView.length - 1) { state.evalIdx++; renderEval(); } | |
| }); | |
| // keyboard arrows | |
| document.addEventListener("keydown", function (e) { | |
| var tag = (e.target.tagName || "").toLowerCase(); | |
| if (tag === "input" || tag === "select" || tag === "textarea") return; | |
| if (e.key === "ArrowLeft") { | |
| if (state.mode === "corpus") $("corpusPrevBtn").click(); | |
| else $("evalPrevBtn").click(); | |
| } else if (e.key === "ArrowRight") { | |
| if (state.mode === "corpus") $("corpusNextBtn").click(); | |
| else $("evalNextBtn").click(); | |
| } | |
| }); | |
| } | |
| /* ---------------- boot ---------------- */ | |
| loadAll().then(function () { | |
| $("loading").style.display = "none"; | |
| $("sidebarFooter").innerHTML = | |
| state.corpus.length + " documents · " + state.questions.length + " questions<br>" + | |
| '<a href="https://github.com/Zhanli-Li/MuDABench" target="_blank" rel="noopener">GitHub</a> · ' + | |
| '<a href="https://huggingface.co/datasets/Zhanli-Li/MuDABench" target="_blank" rel="noopener">HF dataset</a> · ' + | |
| '<a href="https://arxiv.org/abs/2604.22239" target="_blank" rel="noopener">arXiv</a>'; | |
| populateCorpusFilters(); | |
| wire(); | |
| setMode("corpus"); | |
| filterCorpus(); | |
| filterEval(); | |
| }).catch(function (err) { | |
| $("loading").textContent = "Failed to load data: " + err; | |
| }); | |
| })(); | |