Spaces:
Running
Running
| /* MEBench viewer — vanilla JS. Two tabs: Corpus (shared entity docs) + Eval | |
| (per-split questions with a train/test/single_test selector). */ | |
| (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 truncate(s, n) { | |
| s = String(s || ""); | |
| return s.length > n ? s.slice(0, n - 1) + "…" : s; | |
| } | |
| var state = { | |
| splits: [], // manifest rows | |
| splitName: null, | |
| mode: "corpus", | |
| corpus: [], // shared entity documents | |
| corpusView: [], | |
| corpusIdx: 0, | |
| docByTitle: {}, // title -> index into state.corpus | |
| topicCounts: {}, // topic -> #corpus docs | |
| questions: [], // current split's questions | |
| evalView: [], | |
| evalIdx: 0, | |
| }; | |
| /* ---------------- boot / loading ---------------- */ | |
| function boot() { | |
| return Promise.all([ | |
| fetch("sets.json").then(function (r) { return r.json(); }), | |
| fetch("corpus.json").then(function (r) { return r.json(); }), | |
| ]).then(function (res) { | |
| state.splits = res[0] || []; | |
| state.corpus = res[1] || []; | |
| state.docByTitle = {}; | |
| state.topicCounts = {}; | |
| state.corpus.forEach(function (d, i) { | |
| state.docByTitle[d.title] = i; | |
| (d.topics || []).forEach(function (t) { | |
| state.topicCounts[t] = (state.topicCounts[t] || 0) + 1; | |
| }); | |
| }); | |
| populateCorpusTopicFilter(); | |
| populateSplitSelect(); | |
| // eval split loaded by loadSplit below | |
| return loadSplit(state.splits[0].split); | |
| }); | |
| } | |
| function currentSplit() { | |
| for (var i = 0; i < state.splits.length; i++) { | |
| if (state.splits[i].split === state.splitName) return state.splits[i]; | |
| } | |
| return null; | |
| } | |
| function loadSplit(name) { | |
| state.splitName = name; | |
| $("splitSelect").value = name; | |
| var m = currentSplit(); | |
| return fetch(m.eval_file).then(function (r) { return r.json(); }).then(function (qs) { | |
| state.questions = qs || []; | |
| state.evalIdx = 0; | |
| populateEvalFilters(); | |
| $("evalSearch").value = ""; | |
| filterEval(); | |
| renderFooter(); | |
| }); | |
| } | |
| /* ---------------- corpus tab ---------------- */ | |
| function populateCorpusTopicFilter() { | |
| var el = $("corpusTopicFilter"); | |
| el.innerHTML = '<option value="">All topics</option>'; | |
| Object.keys(state.topicCounts).sort().forEach(function (t) { | |
| var o = document.createElement("option"); | |
| o.value = t; o.textContent = t + " (" + state.topicCounts[t] + ")"; | |
| el.appendChild(o); | |
| }); | |
| } | |
| function filterCorpus() { | |
| var q = $("corpusFilter").value.trim().toLowerCase(); | |
| var topic = $("corpusTopicFilter").value; | |
| state.corpusView = state.corpus.filter(function (d) { | |
| if (topic && (d.topics || []).indexOf(topic) === -1) return false; | |
| if (q && d.title.toLowerCase().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 = truncate(d.title, 52); | |
| sel.appendChild(o); | |
| }); | |
| sel.value = String(state.corpusIdx); | |
| } | |
| function renderCorpus() { | |
| var meta = $("docMetaCard"); | |
| var body = $("docContent"); | |
| if (!state.corpusView.length) { | |
| meta.innerHTML = '<div class="empty">No documents match the filter.</div>'; | |
| body.textContent = ""; | |
| $("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 = (d.topics || []).map(function (t) { | |
| return '<span class="meta-pill">Topic: <b>' + esc(t) + "</b></span>"; | |
| }); | |
| pills.push('<span class="meta-pill"><b>' + Math.round(d.size / 1024 * 10) / 10 + "</b> KB</span>"); | |
| var link = d.wiki_url | |
| ? '<a class="doc-link" href="' + esc(d.wiki_url) + '" target="_blank" rel="noopener">Wikipedia ↗</a>' | |
| : ""; | |
| meta.innerHTML = "<h2>" + esc(d.title) + "</h2>" + | |
| '<div class="meta-grid">' + pills.join("") + link + "</div>"; | |
| renderDocBody(d, body); | |
| } | |
| function renderDocBody(d, body) { | |
| if (typeof d.content === "string") { body.textContent = d.content; body.scrollTop = 0; return; } | |
| body.textContent = "Loading document…"; | |
| var target = d; | |
| fetch(d.file).then(function (r) { return r.text(); }).then(function (text) { | |
| target.content = text; | |
| if (state.corpusView[state.corpusIdx] === target) { body.textContent = text; body.scrollTop = 0; } | |
| }).catch(function (err) { | |
| body.textContent = "Failed to load document: " + err; | |
| }); | |
| } | |
| function selectDocByTitle(title) { | |
| $("corpusFilter").value = ""; | |
| $("corpusTopicFilter").value = ""; | |
| state.corpusView = state.corpus.slice(); | |
| var idx = 0; | |
| for (var i = 0; i < state.corpusView.length; i++) { | |
| if (state.corpusView[i].title === title) { idx = i; break; } | |
| } | |
| state.corpusIdx = idx; | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| function showTopicInCorpus(topic) { | |
| $("corpusFilter").value = ""; | |
| $("corpusTopicFilter").value = topic; | |
| state.corpusIdx = 0; | |
| filterCorpus(); | |
| } | |
| /* ---------------- eval tab ---------------- */ | |
| function populateSplitSelect() { | |
| var sel = $("splitSelect"); | |
| sel.innerHTML = ""; | |
| state.splits.forEach(function (s) { | |
| var o = document.createElement("option"); | |
| o.value = s.split; | |
| o.textContent = s.split + " (" + s.n_questions + " Q)"; | |
| sel.appendChild(o); | |
| }); | |
| } | |
| function populateEvalFilters() { | |
| var m = currentSplit() || {}; | |
| var tf = $("evalTopicFilter"); | |
| tf.innerHTML = '<option value="">All topics</option>'; | |
| (m.topics || []).forEach(function (t) { | |
| var o = document.createElement("option"); o.value = t; o.textContent = t; tf.appendChild(o); | |
| }); | |
| var yf = $("evalTypeFilter"); | |
| yf.innerHTML = '<option value="">All question types</option>'; | |
| Object.keys(m.types || {}).forEach(function (t) { | |
| var o = document.createElement("option"); | |
| o.value = t; o.textContent = t + " (" + m.types[t] + ")"; | |
| yf.appendChild(o); | |
| }); | |
| } | |
| function formatAnswer(a) { | |
| if (a === null || a === undefined) return '<span class="empty-answer">null</span>'; | |
| if (Array.isArray(a)) { | |
| if (!a.length) return '<span class="empty-answer">∅ empty result</span>'; | |
| // train SQL result shape: [columns, rows] | |
| if (a.length === 2 && Array.isArray(a[0]) && Array.isArray(a[1])) { | |
| var cols = a[0], rows = a[1]; | |
| var flat = []; | |
| rows.forEach(function (r) { (Array.isArray(r) ? r : [r]).forEach(function (c) { flat.push(c); }); }); | |
| var allEmpty = !rows.length || flat.every(function (c) { return c === null || c === undefined; }); | |
| if (allEmpty) return '<span class="empty-answer">∅ no result (SQL returned empty / null)</span>'; | |
| var head = "<tr>" + cols.map(function (c) { return "<th>" + esc(c) + "</th>"; }).join("") + "</tr>"; | |
| var trs = rows.map(function (r) { | |
| var cells = (Array.isArray(r) ? r : [r]); | |
| return "<tr>" + cells.map(function (c) { | |
| return "<td>" + (c === null || c === undefined ? "—" : esc(c)) + "</td>"; | |
| }).join("") + "</tr>"; | |
| }).join(""); | |
| return "<table>" + head + trs + "</table>"; | |
| } | |
| return "<ul>" + a.map(function (x) { return "<li>" + esc(x) + "</li>"; }).join("") + "</ul>"; | |
| } | |
| var s = String(a).trim(); | |
| return s ? esc(s) : '<span class="empty-answer">(empty)</span>'; | |
| } | |
| function filterEval() { | |
| var q = $("evalSearch").value.trim().toLowerCase(); | |
| var topic = $("evalTopicFilter").value; | |
| var typ = $("evalTypeFilter").value; | |
| state.evalView = state.questions.filter(function (x) { | |
| if (topic && x.topic !== topic) return false; | |
| if (typ && x.type !== typ) return false; | |
| if (q) { | |
| var hay = [x.question, JSON.stringify(x.answer), x.entity || "", x.topic] | |
| .join(" ").toLowerCase(); | |
| if (hay.indexOf(q) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| if (state.evalIdx >= state.evalView.length) state.evalIdx = 0; | |
| rebuildQuestionSelect(); | |
| renderEval(); | |
| } | |
| function rebuildQuestionSelect() { | |
| var sel = $("questionSelect"); | |
| sel.innerHTML = ""; | |
| state.evalView.forEach(function (x, i) { | |
| var o = document.createElement("option"); | |
| o.value = String(i); | |
| o.textContent = "#" + x.qid + " · " + truncate(x.question, 44); | |
| 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 filter.</div>'; | |
| $("evalCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var x = state.evalView[state.evalIdx]; | |
| $("questionSelect").value = String(state.evalIdx); | |
| $("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length; | |
| var catCls = "cat-" + (x.category || "").split(" ")[0]; | |
| var badges = | |
| '<span class="badge id">#' + esc(x.qid) + "</span>" + | |
| (x.category ? '<span class="badge ' + catCls + '">' + esc(x.category) + "</span>" : "") + | |
| (x.type ? '<span class="badge">' + esc(x.type) + "</span>" : "") + | |
| (x.topic ? '<span class="badge topic">' + esc(x.topic) + "</span>" : "") + | |
| '<span class="badge">' + esc(state.splitName) + "</span>"; | |
| var fields = ""; | |
| if (x.entity) fields += field("Entity", esc(x.entity)); | |
| if (x.edge) fields += field("Relation (edge)", esc(x.edge)); | |
| if (x.properties) fields += field("Properties", esc(x.properties)); | |
| if (x.hops) fields += field("Hops", esc(x.hops)); | |
| if (x["class"]) fields += field("Class", esc(x["class"])); | |
| if (x.sql) fields += field("Ground-truth SQL", '<code class="sql-code">' + esc(x.sql) + "</code>"); | |
| // supporting documents | |
| var support; | |
| if (x.supporting_titles && x.supporting_titles.length) { | |
| support = '<div class="doc-chips">' + x.supporting_titles.map(function (t) { | |
| var known = state.docByTitle.hasOwnProperty(t); | |
| return '<a class="doclink' + (known ? "" : " missing") + '"' + | |
| (known ? ' data-doc="' + esc(t) + '"' : "") + ">" + esc(t) + (known ? " ↗" : "") + "</a>"; | |
| }).join("") + "</div>"; | |
| } else if (x.topic_level) { | |
| var n = state.topicCounts[x.topic] || 0; | |
| support = '<div class="doc-chips"><button class="topic-btn" data-topic="' + esc(x.topic) + '">' + | |
| "📚 All " + n + " documents in “" + esc(x.topic) + "” ↗</button></div>" + | |
| '<div style="color:var(--muted);font-size:12px;margin-top:6px;">' + | |
| "This question ranges over every member of the topic." + "</div>"; | |
| } else { | |
| support = '<span style="color:var(--muted)">none</span>'; | |
| } | |
| var docsSection = field("Supporting documents", support); | |
| card.innerHTML = | |
| '<div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(x.question) + "</h2>" + | |
| '<div class="answer-banner"><div class="lbl">Gold answer</div><div class="val">' + | |
| formatAnswer(x.answer) + "</div></div>" + | |
| docsSection + fields; | |
| Array.prototype.forEach.call(card.querySelectorAll("a.doclink[data-doc]"), function (link) { | |
| link.addEventListener("click", function () { | |
| setMode("corpus"); | |
| selectDocByTitle(this.getAttribute("data-doc")); | |
| }); | |
| }); | |
| var tb = card.querySelector("button.topic-btn[data-topic]"); | |
| if (tb) tb.addEventListener("click", function () { | |
| setMode("corpus"); | |
| showTopicInCorpus(this.getAttribute("data-topic")); | |
| }); | |
| } | |
| /* ---------------- 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"; | |
| } | |
| function renderFooter() { | |
| $("sidebarFooter").innerHTML = | |
| state.corpus.length + " entity docs · split <b>" + esc(state.splitName) + "</b> (" + | |
| (currentSplit() || {}).n_questions + " Q)<br>" + | |
| '<a href="https://github.com/tl2309/MEBench" target="_blank" rel="noopener">GitHub</a> · ' + | |
| '<a href="https://huggingface.co/datasets/Tim999999/MEBench" target="_blank" rel="noopener">HF dataset</a> · ' + | |
| '<a href="https://arxiv.org/abs/2502.18993" target="_blank" rel="noopener">paper</a>'; | |
| } | |
| /* ---------------- wiring ---------------- */ | |
| function wire() { | |
| $("modeCorpusBtn").addEventListener("click", function () { setMode("corpus"); }); | |
| $("modeEvalBtn").addEventListener("click", function () { setMode("eval"); }); | |
| $("corpusFilter").addEventListener("input", filterCorpus); | |
| $("corpusTopicFilter").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(); } | |
| }); | |
| $("splitSelect").addEventListener("change", function () { loadSplit(this.value); }); | |
| $("evalSearch").addEventListener("input", filterEval); | |
| $("evalTopicFilter").addEventListener("change", filterEval); | |
| $("evalTypeFilter").addEventListener("change", filterEval); | |
| $("questionSelect").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(); } | |
| }); | |
| document.addEventListener("keydown", function (e) { | |
| var tag = (e.target.tagName || "").toLowerCase(); | |
| if (tag === "input" || tag === "select" || tag === "textarea") return; | |
| if (e.key === "ArrowLeft") { | |
| (state.mode === "corpus" ? $("corpusPrevBtn") : $("evalPrevBtn")).click(); | |
| } else if (e.key === "ArrowRight") { | |
| (state.mode === "corpus" ? $("corpusNextBtn") : $("evalNextBtn")).click(); | |
| } | |
| }); | |
| } | |
| /* ---------------- boot ---------------- */ | |
| wire(); | |
| boot().then(function () { | |
| $("loading").style.display = "none"; | |
| setMode("corpus"); | |
| filterCorpus(); | |
| }).catch(function (err) { | |
| $("loading").textContent = "Failed to load data: " + err; | |
| }); | |
| })(); | |