Spaces:
Running
Running
| /* PhantomWiki viewer — Corpus/Eval across nine universes plus five canonical | |
| full runs and a prejoined comparison view for size5000_seed1. */ | |
| (function () { | |
| "use strict"; | |
| var SCOPE_SET = "size5000_seed1"; | |
| var RECORD_CACHE_LIMIT = 12; | |
| var RUN_REGISTRY = { | |
| c1: { mode: "run-c1", button: "modeC1Btn", cls: "run-c1" }, | |
| c2: { mode: "run-c2", button: "modeC2Btn", cls: "run-c2" }, | |
| c6: { mode: "run-c6", button: "modeC6Btn", cls: "run-c6" }, | |
| naive: { mode: "run-naive", button: "modeNaiveBtn", cls: "run-naive" }, | |
| e2e: { mode: "run-e2e", button: "modeE2eBtn", cls: "run-e2e" }, | |
| e2e_rawtext: { | |
| mode: "run-e2e-rawtext", | |
| button: "modeE2eRawtextBtn", | |
| cls: "run-e2e-rawtext", | |
| }, | |
| }; | |
| var RUN_ORDER = ["c1", "c2", "c6", "naive", "e2e", "e2e_rawtext"]; | |
| 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; | |
| } | |
| function loadJson(path) { | |
| return fetch(path).then(function (response) { | |
| if (!response.ok) throw new Error(path + " returned " + response.status); | |
| return response.json(); | |
| }); | |
| } | |
| function pct(value) { return (Number(value || 0) * 100).toFixed(2) + "%"; } | |
| function fmtNumber(value) { | |
| return typeof value === "number" ? value.toLocaleString() : "—"; | |
| } | |
| function runSlot(mode) { | |
| for (var slot in RUN_REGISTRY) { | |
| if (RUN_REGISTRY[slot].mode === mode) return slot; | |
| } | |
| return null; | |
| } | |
| function isScopedMode(mode) { return mode === "compare" || Boolean(runSlot(mode)); } | |
| function rememberQid(qid) { | |
| if (qid) window.TrajectoryUI.setQid(qid); | |
| } | |
| function sharedIndex(records) { | |
| var qid = window.TrajectoryUI.getQid(); | |
| if (!qid) return -1; | |
| return records.findIndex(function (record) { | |
| return (record.id || record.qid) === qid; | |
| }); | |
| } | |
| var state = { | |
| sets: [], setName: null, mode: "corpus", priorSetName: null, | |
| corpus: [], corpusView: [], corpusIdx: 0, docByTitle: {}, | |
| questions: [], evalView: [], evalIdx: 0, facets: [], | |
| runManifest: null, runIndexes: {}, runIndexPromises: {}, | |
| runViews: {}, runIdx: {}, renderedRunSlot: null, | |
| runCache: {}, runCacheOrder: [], runRecordPromises: {}, | |
| compareIndex: null, compareIndexPromise: null, compareView: [], compareIdx: 0, | |
| compareCache: {}, compareCacheOrder: [], compareRecordPromises: {}, | |
| setLoadSeq: 0, modeSeq: 0, loadedSetName: null, | |
| loadingSetName: null, setLoadPromise: null, | |
| }; | |
| /* ---------------- loading ---------------- */ | |
| function loadManifests() { | |
| return Promise.all([loadJson("sets.json"), loadJson("runs/manifest.json")]).then(function (results) { | |
| state.sets = results[0] || []; | |
| state.runManifest = results[1] || {}; | |
| var sel = $("setSelect"); | |
| sel.innerHTML = ""; | |
| state.sets.forEach(function (set) { | |
| var option = document.createElement("option"); | |
| option.value = set.set; | |
| option.textContent = set.label + " (" + set.n_docs + " docs · " + set.n_questions + " Q)"; | |
| sel.appendChild(option); | |
| }); | |
| if (!state.sets.length) throw new Error("no universes"); | |
| (state.runManifest.slots || []).forEach(function (entry) { | |
| if (RUN_REGISTRY[entry.slot]) { | |
| Object.keys(entry).forEach(function (key) { RUN_REGISTRY[entry.slot][key] = entry[key]; }); | |
| } | |
| }); | |
| RUN_ORDER.forEach(function (slot) { | |
| var config = RUN_REGISTRY[slot]; | |
| var button = $(config.button); | |
| button.classList.add("run-tab", config.cls); | |
| button.title = config.label + ": " + config.correct + "/" + config.total + " correct"; | |
| }); | |
| $("modeCompareBtn").classList.add("compare-tab"); | |
| return loadSet(state.sets[0].set).then(function (loaded) { | |
| if (loaded) setScopedButtonsDisabled(false); | |
| }); | |
| }); | |
| } | |
| function cachedRecord(cache, order, key) { | |
| if (!Object.prototype.hasOwnProperty.call(cache, key)) return null; | |
| var index = order.indexOf(key); | |
| if (index >= 0) order.splice(index, 1); | |
| order.push(key); | |
| return cache[key]; | |
| } | |
| function storeRecord(cache, order, key, record) { | |
| cache[key] = record; | |
| var index = order.indexOf(key); | |
| if (index >= 0) order.splice(index, 1); | |
| order.push(key); | |
| while (order.length > RECORD_CACHE_LIMIT) delete cache[order.shift()]; | |
| } | |
| function currentManifest() { | |
| for (var i = 0; i < state.sets.length; i++) { | |
| if (state.sets[i].set === state.setName) return state.sets[i]; | |
| } | |
| return null; | |
| } | |
| function loadSet(name) { | |
| if (state.loadedSetName === name) { | |
| state.setLoadSeq++; | |
| state.loadingSetName = null; | |
| state.setLoadPromise = null; | |
| state.setName = name; | |
| $("setSelect").value = name; | |
| $("loading").style.display = "none"; | |
| renderFooter(); | |
| applyModeVisibility(); | |
| return Promise.resolve(true); | |
| } | |
| if (state.loadingSetName === name && state.setLoadPromise) { | |
| return state.setLoadPromise; | |
| } | |
| var loadSeq = ++state.setLoadSeq; | |
| state.setName = name; | |
| $("setSelect").value = name; | |
| var manifest = currentManifest(); | |
| if (!manifest) return Promise.reject(new Error("unknown universe " + name)); | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Loading " + manifest.label + " …"; | |
| hideAllViews(); | |
| var request = Promise.all([loadJson(manifest.corpus_file), loadJson(manifest.eval_file)]).then(function (results) { | |
| if (loadSeq !== state.setLoadSeq) return false; | |
| state.corpus = results[0] || []; | |
| state.questions = results[1] || []; | |
| state.docByTitle = {}; | |
| state.corpus.forEach(function (doc, index) { state.docByTitle[doc.title] = index; }); | |
| state.corpusIdx = 0; | |
| state.evalIdx = 0; | |
| buildFacetFilters(); | |
| $("corpusFilter").value = ""; | |
| $("evalSearch").value = ""; | |
| filterCorpus(); | |
| filterEval(); | |
| renderFooter(); | |
| $("loading").style.display = "none"; | |
| state.loadedSetName = name; | |
| state.loadingSetName = null; | |
| state.setLoadPromise = null; | |
| setScopedButtonsDisabled(false); | |
| applyModeVisibility(); | |
| return true; | |
| }).catch(function (error) { | |
| if (loadSeq !== state.setLoadSeq) return false; | |
| state.loadingSetName = null; | |
| state.setLoadPromise = null; | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Failed to load " + manifest.label + ": " + error; | |
| applyModeVisibility(); | |
| return false; | |
| }); | |
| state.loadingSetName = name; | |
| state.setLoadPromise = request; | |
| return request; | |
| } | |
| /* ---------------- corpus tab ---------------- */ | |
| function filterCorpus() { | |
| var query = $("corpusFilter").value.trim().toLowerCase(); | |
| state.corpusView = state.corpus.filter(function (doc) { | |
| return !query || doc.title.toLowerCase().indexOf(query) !== -1; | |
| }); | |
| if (state.corpusIdx >= state.corpusView.length) state.corpusIdx = 0; | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| function rebuildDocSelect() { | |
| var select = $("docSelect"); | |
| select.innerHTML = ""; | |
| state.corpusView.forEach(function (doc, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = truncate(doc.title, 52); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.corpusIdx); | |
| } | |
| function renderCorpus() { | |
| var meta = $("docMetaCard"); | |
| var body = $("docContent"); | |
| if (!state.corpusView.length) { | |
| meta.innerHTML = '<div class="empty">No people match the filter.</div>'; | |
| body.innerHTML = ""; | |
| $("corpusCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var doc = state.corpusView[state.corpusIdx]; | |
| $("docSelect").value = String(state.corpusIdx); | |
| $("corpusCounter").textContent = (state.corpusIdx + 1) + " / " + state.corpusView.length; | |
| meta.innerHTML = "<h2>" + esc(doc.title) + "</h2>"; | |
| try { body.innerHTML = marked.parse(doc.article || ""); } | |
| catch (error) { body.textContent = doc.article || ""; } | |
| body.scrollTop = 0; | |
| } | |
| function selectDocByTitle(title) { | |
| $("corpusFilter").value = ""; | |
| state.corpusView = state.corpus.slice(); | |
| var index = state.docByTitle.hasOwnProperty(title) ? state.docByTitle[title] : 0; | |
| state.corpusIdx = index; | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| function openCanonicalCorpus(title) { | |
| setMode("corpus", { keepRunScope: true }).then(function () { | |
| selectDocByTitle(title); | |
| }); | |
| } | |
| /* ---------------- eval tab ---------------- */ | |
| function buildFacetFilters() { | |
| var host = $("facetFilters"); | |
| host.innerHTML = ""; | |
| state.facets = (currentManifest() || {}).facets || []; | |
| state.facets.forEach(function (facet) { | |
| var select = document.createElement("select"); | |
| select.className = "facet-select"; | |
| select.setAttribute("data-key", facet.key); | |
| var all = document.createElement("option"); | |
| all.value = ""; | |
| all.textContent = "All " + facet.key.toLowerCase(); | |
| select.appendChild(all); | |
| (facet.values || []).forEach(function (value) { | |
| var option = document.createElement("option"); | |
| option.value = value; | |
| option.textContent = facet.key + " " + value; | |
| select.appendChild(option); | |
| }); | |
| select.addEventListener("change", filterEval); | |
| host.appendChild(select); | |
| }); | |
| } | |
| function activeFacetFilters() { | |
| var result = []; | |
| Array.prototype.forEach.call(document.querySelectorAll("#facetFilters .facet-select"), function (select) { | |
| if (select.value) result.push([select.getAttribute("data-key"), select.value]); | |
| }); | |
| return result; | |
| } | |
| function filterEval() { | |
| var query = $("evalSearch").value.trim().toLowerCase(); | |
| var facets = activeFacetFilters(); | |
| state.evalView = state.questions.filter(function (row) { | |
| for (var i = 0; i < facets.length; i++) { | |
| if (String((row.meta || {})[facets[i][0]]) !== facets[i][1]) return false; | |
| } | |
| if (query) { | |
| var haystack = [row.question, (row.answer || []).join(" "), row.id].join(" ").toLowerCase(); | |
| if (haystack.indexOf(query) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| var target = sharedIndex(state.evalView); | |
| if (target >= 0) state.evalIdx = target; | |
| else if (state.evalIdx >= state.evalView.length) state.evalIdx = 0; | |
| rebuildQuestionSelect(); | |
| renderEval(); | |
| } | |
| function rebuildQuestionSelect() { | |
| var select = $("questionSelect"); | |
| select.innerHTML = ""; | |
| state.evalView.forEach(function (row, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = truncate(row.question, 52); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.evalIdx); | |
| } | |
| function field(label, valueHtml) { | |
| return '<div class="field"><div class="lbl">' + esc(label) + '</div><div class="val">' + valueHtml + "</div></div>"; | |
| } | |
| function personChips(values) { | |
| if (!values || !values.length) return '<span class="empty-answer">(none)</span>'; | |
| return '<div class="answer-chips">' + values.map(function (value) { | |
| if (state.docByTitle.hasOwnProperty(value)) { | |
| return '<a class="ans-chip" data-doc="' + esc(value) + '">' + esc(value) + " ↗</a>"; | |
| } | |
| return '<span class="ans-chip">' + esc(value) + "</span>"; | |
| }).join("") + "</div>"; | |
| } | |
| function wirePersonLinks(container) { | |
| Array.prototype.forEach.call(container.querySelectorAll("a.ans-chip[data-doc]"), function (link) { | |
| link.addEventListener("click", function () { | |
| openCanonicalCorpus(this.getAttribute("data-doc")); | |
| }); | |
| }); | |
| } | |
| 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 row = state.evalView[state.evalIdx]; | |
| if (!window.__e2eStructuresActive) rememberQid(row.id); | |
| $("questionSelect").value = String(state.evalIdx); | |
| $("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length; | |
| var meta = row.meta || {}; | |
| var badges = '<span class="badge id">' + esc(row.id.slice(0, 8)) + "</span>" + | |
| '<span class="badge set">' + esc(state.setName) + "</span>" + | |
| (meta.Difficulty ? '<span class="badge diff">difficulty ' + esc(meta.Difficulty) + "</span>" : "") + | |
| (meta.Type ? '<span class="badge">type ' + esc(meta.Type) + "</span>" : "") + | |
| '<span class="badge">' + (row.answer || []).length + " answer" + ((row.answer || []).length === 1 ? "" : "s") + "</span>"; | |
| var fields = ""; | |
| if (row.template) fields += field("Question template", '<span class="template-txt">' + esc(row.template) + "</span>"); | |
| if (row.prolog && row.prolog.length) { | |
| fields += field("Prolog reasoning (answer = " + esc(row.prolog_answer) + ")", | |
| '<code class="prolog-code">' + esc(row.prolog.join("\n")) + "</code>"); | |
| } | |
| fields += field("Supporting documents", | |
| row.supporting_titles && row.supporting_titles.length | |
| ? personChips(row.supporting_titles) | |
| : '<span class="muted">Answer is an attribute value, not a person document.</span>'); | |
| card.innerHTML = '<div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(row.question) + "</h2>" + | |
| window.TrajectoryUI.gold(row.answer, "Gold") + | |
| fields; | |
| wirePersonLinks(card); | |
| } | |
| /* ---------------- shared run rendering ---------------- */ | |
| function scoreCard(label, numerator, denominator, kind) { | |
| return '<div class="score-card ' + esc(kind || "") + '"><div class="score-label">' + esc(label) + | |
| '</div><div class="score-value">' + esc(numerator) + " / " + esc(denominator) + | |
| '</div><div class="score-pct">' + pct(denominator ? numerator / denominator : 0) + "</div></div>"; | |
| } | |
| function statusBadge(record) { | |
| var status = !record.answered ? "missing" : | |
| (record.correct ? "correct" : | |
| (record.score > 0 ? "partial" : "incorrect")); | |
| var label = status === "missing" ? "missing / unanswered" : status; | |
| return '<span class="status-badge ' + status + '">' + label + "</span>"; | |
| } | |
| function canonicalScoreCard(config) { | |
| if (config.score_label && config.score_label !== "Canonical score") { | |
| return '<div class="score-card correct"><div class="score-label">' + | |
| esc(config.score_label) + '</div><div class="score-value">' + | |
| pct(config.score) + '</div><div class="score-pct">' + | |
| esc(config.score_detail || "") + "</div></div>"; | |
| } | |
| return scoreCard("Canonical score", config.correct, config.total, "correct"); | |
| } | |
| function metadataBadges(record) { | |
| var metadata = record.metadata || {}; | |
| return '<div class="badges"><span class="badge id">' + esc(record.qid) + "</span>" + | |
| '<span class="badge set">' + SCOPE_SET + "</span>" + | |
| (metadata.difficulty ? '<span class="badge diff">difficulty ' + esc(metadata.difficulty) + "</span>" : "") + | |
| (metadata.type ? '<span class="badge">type ' + esc(metadata.type) + "</span>" : "") + | |
| statusBadge(record) + "</div>"; | |
| } | |
| function operationalHtml(record) { | |
| var tokens = record.tokens || {}; | |
| var stop = record.failure_reason || record.stop_reason || record.finish_reason || | |
| ((record.finish_reasons || []).length ? record.finish_reasons[record.finish_reasons.length - 1] : "—"); | |
| return '<div class="ops-grid">' + | |
| field("Stop / failure", '<code>' + esc(stop) + "</code>") + | |
| field("Tokens", "total " + fmtNumber(tokens.total_tokens) + " · prompt " + | |
| fmtNumber(tokens.prompt_tokens) + " · completion " + fmtNumber(tokens.completion_tokens) + | |
| (typeof tokens.reasoning_tokens === "number" ? " · reasoning " + fmtNumber(tokens.reasoning_tokens) : "")) + | |
| field("Agent work", (typeof record.turns === "number" ? record.turns + " turns" : "no agent loop") + | |
| " · " + fmtNumber(record.tool_calls || 0) + " tool calls" + | |
| (Object.keys(record.tool_call_counts || {}).length | |
| ? " (" + Object.keys(record.tool_call_counts).map(function (key) { | |
| return key + " " + record.tool_call_counts[key]; | |
| }).join(", ") + ")" : "")) + | |
| "</div>"; | |
| } | |
| function eventsHtml(events) { | |
| events = events || []; | |
| if (!events.length) return '<div class="muted">No agent events for this run.</div>'; | |
| return window.TrajectoryUI.render(events, "Agent trajectory"); | |
| } | |
| function judgeHtml(record) { | |
| var judge = record.judge || {}; | |
| return '<div class="judge-summary">' + | |
| (typeof record.score === "number" | |
| ? field("Per-question answer F1", (record.score * 100).toFixed(2) + "%") | |
| : "") + | |
| field("Extracted judge answer", record.extracted_judge_answer | |
| ? '<code class="answer-code">' + esc(record.extracted_judge_answer) + "</code>" | |
| : '<span class="muted">(not judged)</span>') + | |
| field("Judge confidence", judge.confidence === null || judge.confidence === undefined | |
| ? "—" : esc(judge.confidence) + "%") + | |
| (judge.text ? '<details><summary>Judge details</summary><pre class="text-block">' + | |
| esc(judge.text) + "</pre></details>" : "") + | |
| "</div>"; | |
| } | |
| function ensureRunIndex(slot) { | |
| var config = RUN_REGISTRY[slot]; | |
| if (state.runIndexes[slot]) return Promise.resolve(state.runIndexes[slot]); | |
| if (state.runIndexPromises[slot]) return state.runIndexPromises[slot]; | |
| var request = loadJson(config.index).then(function (index) { | |
| state.runIndexes[slot] = index; | |
| if (state.runIdx[slot] === undefined) state.runIdx[slot] = 0; | |
| delete state.runIndexPromises[slot]; | |
| return index; | |
| }, function (error) { | |
| delete state.runIndexPromises[slot]; | |
| throw error; | |
| }); | |
| state.runIndexPromises[slot] = request; | |
| return request; | |
| } | |
| function loadRun(slot) { | |
| var config = RUN_REGISTRY[slot]; | |
| if (state.runIndexes[slot]) { | |
| $("loading").style.display = "none"; | |
| filterRun(); | |
| return Promise.resolve(); | |
| } | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Loading " + config.label + " index …"; | |
| return ensureRunIndex(slot).then(function () { | |
| if (runSlot(state.mode) !== slot) return; | |
| $("loading").style.display = "none"; | |
| filterRun(); | |
| applyModeVisibility(); | |
| }).catch(function (error) { | |
| if (runSlot(state.mode) === slot) { | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Failed to load " + config.label + ": " + error; | |
| } | |
| }); | |
| } | |
| function ensureRunRecord(slot, qid) { | |
| var key = slot + "/" + qid; | |
| var cached = cachedRecord(state.runCache, state.runCacheOrder, key); | |
| if (cached) return Promise.resolve(cached); | |
| if (state.runRecordPromises[key]) return state.runRecordPromises[key]; | |
| var config = RUN_REGISTRY[slot]; | |
| var request = loadJson(config.records + "/" + qid + ".json").then(function (record) { | |
| delete state.runRecordPromises[key]; | |
| storeRecord(state.runCache, state.runCacheOrder, key, record); | |
| return record; | |
| }, function (error) { | |
| delete state.runRecordPromises[key]; | |
| throw error; | |
| }); | |
| state.runRecordPromises[key] = request; | |
| return request; | |
| } | |
| function filterRun() { | |
| var slot = runSlot(state.mode); | |
| if (!slot || !state.runIndexes[slot]) return; | |
| var query = $("runSearch").value.trim().toLowerCase(); | |
| var status = $("runStatus").value; | |
| state.runViews[slot] = state.runIndexes[slot].records.filter(function (record) { | |
| if (status && record.status !== status) return false; | |
| if (!query) return true; | |
| return [record.qid, record.question, (record.gold || []).join(" ")].join(" ").toLowerCase().indexOf(query) !== -1; | |
| }); | |
| var target = sharedIndex(state.runViews[slot]); | |
| if (target >= 0) state.runIdx[slot] = target; | |
| else if (state.runIdx[slot] >= state.runViews[slot].length) state.runIdx[slot] = 0; | |
| rebuildRunSelect(slot); | |
| renderRun(slot); | |
| } | |
| function rebuildRunSelect(slot) { | |
| var select = $("runQidSelect"); | |
| select.innerHTML = ""; | |
| (state.runViews[slot] || []).forEach(function (record, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = record.qid.slice(0, 8) + " · " + truncate(record.question, 44); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.runIdx[slot] || 0); | |
| } | |
| function renderRun(slot) { | |
| var view = state.runViews[slot] || []; | |
| var index = state.runIdx[slot] || 0; | |
| var host = $("runView"); | |
| state.renderedRunSlot = slot; | |
| applyModeVisibility(); | |
| if (!view.length) { | |
| host.innerHTML = '<div class="empty">No run records match the filters.</div>'; | |
| $("runCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var summary = view[index]; | |
| var config = RUN_REGISTRY[slot]; | |
| $("runQidSelect").value = String(index); | |
| $("runCounter").textContent = (index + 1) + " / " + view.length; | |
| host.innerHTML = '<div class="loading-inline">Loading ' + esc(summary.qid) + " …</div>"; | |
| var request = ensureRunRecord(slot, summary.qid); | |
| request.then(function (record) { | |
| var current = (state.runViews[slot] || [])[state.runIdx[slot] || 0]; | |
| if (runSlot(state.mode) !== slot || !current || current.qid !== record.qid) return; | |
| rememberQid(record.qid); | |
| var support = personChips((record.metadata || {}).supporting_titles || []); | |
| host.innerHTML = | |
| '<div class="metric-grid">' + | |
| canonicalScoreCard(config) + | |
| scoreCard("Answered", config.answered, config.total, "coverage") + | |
| "</div>" + | |
| '<article class="run-card ' + esc(config.cls) + '">' + | |
| metadataBadges(record) + | |
| "<h2>" + esc(record.question) + "</h2>" + | |
| window.TrajectoryUI.gold(record.gold, "Gold") + | |
| (slot.indexOf("e2e") === 0 | |
| ? window.TrajectoryUI.supporting( | |
| "Supporting people", | |
| support || '<span class="muted">none</span>', | |
| ((record.metadata || {}).supporting_titles || []).length | |
| ) | |
| : field("Supporting people", support)) + | |
| field("Prediction", record.prediction | |
| ? '<pre class="text-block prediction">' + esc(record.prediction) + "</pre>" | |
| : '<div class="missing-panel">No answer returned · ' + esc(record.failure_reason || "unknown failure") + "</div>") + | |
| judgeHtml(record) + | |
| operationalHtml(record) + | |
| eventsHtml(record.events) + | |
| "</article>"; | |
| wirePersonLinks(host); | |
| window.TrajectoryUI.bind(host); | |
| }).catch(function (error) { | |
| var current = (state.runViews[slot] || [])[state.runIdx[slot] || 0]; | |
| if (runSlot(state.mode) === slot && current && current.qid === summary.qid) { | |
| host.innerHTML = '<div class="empty">Failed to load record: ' + esc(error) + "</div>"; | |
| } | |
| }); | |
| } | |
| /* ---------------- compare rendering ---------------- */ | |
| function ensureCompareIndex() { | |
| if (state.compareIndex) return Promise.resolve(state.compareIndex); | |
| if (state.compareIndexPromise) return state.compareIndexPromise; | |
| var request = loadJson((state.runManifest || {}).compare_index || "compare/index.json").then(function (index) { | |
| state.compareIndex = index; | |
| state.compareIndexPromise = null; | |
| return index; | |
| }, function (error) { | |
| state.compareIndexPromise = null; | |
| throw error; | |
| }); | |
| state.compareIndexPromise = request; | |
| return request; | |
| } | |
| function loadCompare() { | |
| if (state.compareIndex) { | |
| $("loading").style.display = "none"; | |
| filterCompare(); | |
| return Promise.resolve(); | |
| } | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Loading comparison index …"; | |
| return ensureCompareIndex().then(function () { | |
| if (state.mode !== "compare") return; | |
| $("loading").style.display = "none"; | |
| filterCompare(); | |
| applyModeVisibility(); | |
| }).catch(function (error) { | |
| if (state.mode === "compare") { | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Failed to load comparison index: " + error; | |
| } | |
| }); | |
| } | |
| function ensureCompareRecord(qid) { | |
| var cached = cachedRecord(state.compareCache, state.compareCacheOrder, qid); | |
| if (cached) return Promise.resolve(cached); | |
| if (state.compareRecordPromises[qid]) return state.compareRecordPromises[qid]; | |
| var request = loadJson("compare/records/" + qid + ".json").then(function (record) { | |
| delete state.compareRecordPromises[qid]; | |
| storeRecord(state.compareCache, state.compareCacheOrder, qid, record); | |
| return record; | |
| }, function (error) { | |
| delete state.compareRecordPromises[qid]; | |
| throw error; | |
| }); | |
| state.compareRecordPromises[qid] = request; | |
| return request; | |
| } | |
| function filterCompare() { | |
| if (!state.compareIndex) return; | |
| var query = $("compareSearch").value.trim().toLowerCase(); | |
| var filter = $("compareFilter").value; | |
| state.compareView = state.compareIndex.records.filter(function (record) { | |
| if (filter === "disagreement" && !record.disagreement) return false; | |
| if (filter === "missing" && !record.any_missing) return false; | |
| if (filter === "e2e-correct" && !record.only_e2e_correct) return false; | |
| return !query || [record.qid, record.question].join(" ").toLowerCase().indexOf(query) !== -1; | |
| }); | |
| var target = sharedIndex(state.compareView); | |
| if (target >= 0) state.compareIdx = target; | |
| else if (state.compareIdx >= state.compareView.length) state.compareIdx = 0; | |
| rebuildCompareSelect(); | |
| renderCompare(); | |
| } | |
| function rebuildCompareSelect() { | |
| var select = $("compareQidSelect"); | |
| select.innerHTML = ""; | |
| state.compareView.forEach(function (record, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = record.qid.slice(0, 8) + " · " + truncate(record.question, 44); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.compareIdx); | |
| } | |
| function compareScoreCards() { | |
| return '<div class="compare-score-grid">' + RUN_ORDER.map(function (slot) { | |
| var config = RUN_REGISTRY[slot]; | |
| return '<div class="compare-score ' + esc(config.cls) + '"><div class="score-label">' + | |
| esc(config.label) + '</div><div class="score-value">' + | |
| (config.score_label && config.score_label !== "Canonical score" | |
| ? pct(config.score) | |
| : config.correct + " / " + config.total) + | |
| '</div><div class="score-pct">' + | |
| (config.score_detail ? esc(config.score_detail) + " · " : "") + | |
| config.answered + " answered</div></div>"; | |
| }).join("") + "</div>"; | |
| } | |
| function compareRunCard(slot, record, qid) { | |
| var config = RUN_REGISTRY[slot]; | |
| return '<section class="compare-run-card ' + esc(config.cls) + '">' + | |
| '<div class="compare-run-head"><h3>' + esc(config.label) + "</h3>" + statusBadge(record) + "</div>" + | |
| field("Prediction", record.prediction | |
| ? '<pre class="text-block prediction">' + esc(record.prediction) + "</pre>" | |
| : '<div class="missing-panel">No answer · ' + esc(record.failure_reason || "unknown failure") + "</div>") + | |
| field("Extracted judge answer", record.extracted_judge_answer | |
| ? '<code class="answer-code">' + esc(record.extracted_judge_answer) + "</code>" | |
| : '<span class="muted">(not judged)</span>') + | |
| (typeof record.score === "number" | |
| ? field("Per-question answer F1", (record.score * 100).toFixed(2) + "%") | |
| : "") + | |
| operationalHtml(record) + | |
| ((record.judge || {}).text ? '<details><summary>Judge details</summary><pre class="text-block">' + | |
| esc(record.judge.text) + "</pre></details>" : "") + | |
| '<details class="compare-events" data-path="' + esc(record.events_path) + '" data-qid="' + | |
| esc(qid) + '" data-slot="' + esc(slot) + '"><summary>Agent events</summary>' + | |
| '<div class="lazy-events muted">Expand to load events.</div></details>' + | |
| "</section>"; | |
| } | |
| function wireCompareEvents(host) { | |
| Array.prototype.forEach.call(host.querySelectorAll("details.compare-events"), function (details) { | |
| details.addEventListener("toggle", function () { | |
| if (!this.open || this.getAttribute("data-loaded") === "1") return; | |
| var detail = this; | |
| var target = detail.querySelector(".lazy-events"); | |
| target.textContent = "Loading events …"; | |
| loadJson(detail.getAttribute("data-path")).then(function (record) { | |
| detail.setAttribute("data-loaded", "1"); | |
| target.classList.remove("muted"); | |
| target.innerHTML = eventsHtml(record.events || []); | |
| window.TrajectoryUI.bind(detail); | |
| }).catch(function (error) { | |
| target.textContent = "Failed to load events: " + error; | |
| }); | |
| }); | |
| }); | |
| } | |
| function renderCompare() { | |
| var host = $("compareView"); | |
| if (!state.compareView.length) { | |
| host.innerHTML = '<div class="empty">No comparison records match the filters.</div>'; | |
| $("compareCounter").textContent = "0 / 0"; | |
| return; | |
| } | |
| var summary = state.compareView[state.compareIdx]; | |
| $("compareQidSelect").value = String(state.compareIdx); | |
| $("compareCounter").textContent = (state.compareIdx + 1) + " / " + state.compareView.length; | |
| host.innerHTML = '<div class="loading-inline">Loading comparison ' + esc(summary.qid) + " …</div>"; | |
| var request = ensureCompareRecord(summary.qid); | |
| request.then(function (record) { | |
| var current = state.compareView[state.compareIdx]; | |
| if (state.mode !== "compare" || !current || current.qid !== record.qid) return; | |
| rememberQid(record.qid); | |
| host.innerHTML = compareScoreCards() + | |
| '<article class="compare-question">' + | |
| metadataBadges({ | |
| qid: record.qid, metadata: record.metadata, answered: summary.answered_count === RUN_ORDER.length, | |
| correct: summary.correct_count === RUN_ORDER.length, | |
| }) + | |
| "<h2>" + esc(record.question) + "</h2>" + | |
| window.TrajectoryUI.gold(record.gold, "Gold") + | |
| field("Supporting people", personChips((record.metadata || {}).supporting_titles || [])) + | |
| "</article>" + | |
| '<div class="compare-grid">' + RUN_ORDER.map(function (slot) { | |
| return compareRunCard(slot, record.runs[slot], record.qid); | |
| }).join("") + "</div>"; | |
| wirePersonLinks(host); | |
| wireCompareEvents(host); | |
| }).catch(function (error) { | |
| var current = state.compareView[state.compareIdx]; | |
| if (state.mode === "compare" && current && current.qid === summary.qid) { | |
| host.innerHTML = '<div class="empty">Failed to load comparison: ' + esc(error) + "</div>"; | |
| } | |
| }); | |
| } | |
| /* ---------------- mode / footer / wiring ---------------- */ | |
| function hideAllViews() { | |
| ["corpusView", "evalView", "runView", "compareView"].forEach(function (id) { | |
| $(id).style.display = "none"; | |
| }); | |
| } | |
| function applyModeVisibility() { | |
| if (window.__e2eStructuresActive) { | |
| hideAllViews(); | |
| return; | |
| } | |
| var slot = runSlot(state.mode); | |
| var baseReady = ( | |
| !state.setLoadPromise && | |
| state.loadedSetName !== null && | |
| state.loadedSetName === state.setName | |
| ); | |
| var scopeReady = ( | |
| !state.setLoadPromise && | |
| state.loadedSetName === SCOPE_SET | |
| ); | |
| var runReady = Boolean( | |
| scopeReady && | |
| slot && | |
| state.runIndexes[slot] && | |
| state.renderedRunSlot === slot | |
| ); | |
| var compareReady = ( | |
| scopeReady && | |
| state.mode === "compare" && | |
| Boolean(state.compareIndex) | |
| ); | |
| $("corpusControls").style.display = state.mode === "corpus" ? "" : "none"; | |
| $("evalControls").style.display = state.mode === "eval" ? "" : "none"; | |
| $("runControls").style.display = slot ? "" : "none"; | |
| $("compareControls").style.display = state.mode === "compare" ? "" : "none"; | |
| $("corpusView").style.display = state.mode === "corpus" && baseReady ? "flex" : "none"; | |
| $("evalView").style.display = state.mode === "eval" && baseReady ? "block" : "none"; | |
| $("runView").style.display = runReady ? "block" : "none"; | |
| $("compareView").style.display = compareReady ? "block" : "none"; | |
| Array.prototype.forEach.call(document.querySelectorAll("#viewToggle button"), function (button) { | |
| button.classList.toggle("active", button.getAttribute("data-mode") === state.mode); | |
| }); | |
| } | |
| function setScopedButtonsDisabled(disabled) { | |
| RUN_ORDER.forEach(function (slot) { | |
| $(RUN_REGISTRY[slot].button).disabled = disabled; | |
| }); | |
| $("modeCompareBtn").disabled = disabled; | |
| } | |
| function setMode(mode, options) { | |
| options = options || {}; | |
| var modeSeq = ++state.modeSeq; | |
| var previousMode = state.mode; | |
| var leftStructures = Boolean(window.__e2eStructuresJustClosed); | |
| window.__e2eStructuresJustClosed = false; | |
| var wasScoped = isScopedMode(previousMode) || leftStructures; | |
| var goingScoped = isScopedMode(mode); | |
| if (wasScoped && mode === "eval") options.keepRunScope = true; | |
| var targetSlot = runSlot(mode); | |
| if (targetSlot && (previousMode !== mode || leftStructures)) { | |
| $("runSearch").value = ""; | |
| $("runStatus").value = ""; | |
| } | |
| if (mode === "compare" && (previousMode !== "compare" || leftStructures)) { | |
| $("compareSearch").value = ""; | |
| $("compareFilter").value = ""; | |
| } | |
| if (mode === "eval" && (previousMode !== "eval" || leftStructures)) { | |
| $("evalSearch").value = ""; | |
| Array.prototype.forEach.call(document.querySelectorAll("#facetFilters .facet-select"), function (select) { | |
| select.value = ""; | |
| }); | |
| } | |
| if (targetSlot && targetSlot !== state.renderedRunSlot) { | |
| state.renderedRunSlot = null; | |
| } | |
| if (goingScoped && !wasScoped) state.priorSetName = state.setName; | |
| state.mode = mode; | |
| if (goingScoped) { | |
| $("setSelect").disabled = true; | |
| $("scopeNote").style.display = "block"; | |
| applyModeVisibility(); | |
| var scopeReady = state.loadedSetName === SCOPE_SET && !state.setLoadPromise | |
| ? Promise.resolve() | |
| : loadSet(SCOPE_SET); | |
| return scopeReady.then(function (loaded) { | |
| if ( | |
| loaded === false || | |
| state.loadedSetName !== SCOPE_SET || | |
| modeSeq !== state.modeSeq || | |
| state.mode !== mode | |
| ) { | |
| return; | |
| } | |
| applyModeVisibility(); | |
| var slot = runSlot(mode); | |
| return slot ? loadRun(slot) : loadCompare(); | |
| }); | |
| } | |
| $("setSelect").disabled = false; | |
| $("scopeNote").style.display = "none"; | |
| var restore = wasScoped && !options.keepRunScope ? state.priorSetName : null; | |
| if (options.keepRunScope) state.priorSetName = null; | |
| if (restore) state.priorSetName = null; | |
| if (restore) { | |
| return loadSet(restore).then(function (loaded) { | |
| if ( | |
| loaded && | |
| state.loadedSetName === restore && | |
| modeSeq === state.modeSeq && | |
| state.mode === mode | |
| ) { | |
| applyModeVisibility(); | |
| } | |
| }); | |
| } | |
| applyModeVisibility(); | |
| if (mode === "corpus") renderCorpus(); | |
| if (mode === "eval") filterEval(); | |
| return Promise.resolve(); | |
| } | |
| function renderFooter() { | |
| var manifest = currentManifest() || {}; | |
| $("sidebarFooter").innerHTML = | |
| "Universe <b>" + esc(manifest.label) + "</b> · " + (manifest.n_docs || 0) + | |
| " people · " + (manifest.n_questions || 0) + " questions<br>" + | |
| '<a href="https://github.com/kilian-group/phantom-wiki" target="_blank" rel="noopener">GitHub</a> · ' + | |
| '<a href="https://huggingface.co/datasets/kilian-group/phantom-wiki-v1" target="_blank" rel="noopener">HF dataset</a> · ' + | |
| '<a href="https://arxiv.org/abs/2502.20377" target="_blank" rel="noopener">paper</a>'; | |
| } | |
| function wire() { | |
| $("setSelect").addEventListener("change", function () { loadSet(this.value); }); | |
| Array.prototype.forEach.call(document.querySelectorAll("#viewToggle button"), function (button) { | |
| button.addEventListener("click", function () { setMode(this.getAttribute("data-mode")); }); | |
| }); | |
| $("corpusFilter").addEventListener("input", 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(); } | |
| }); | |
| $("evalSearch").addEventListener("input", 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(); } | |
| }); | |
| $("runSearch").addEventListener("input", function () { | |
| var slot = runSlot(state.mode); | |
| if (slot) state.runIdx[slot] = 0; | |
| filterRun(); | |
| }); | |
| $("runStatus").addEventListener("change", function () { | |
| var slot = runSlot(state.mode); | |
| if (slot) state.runIdx[slot] = 0; | |
| filterRun(); | |
| }); | |
| $("runQidSelect").addEventListener("change", function () { | |
| var slot = runSlot(state.mode); | |
| if (!slot) return; | |
| state.runIdx[slot] = parseInt(this.value, 10) || 0; | |
| renderRun(slot); | |
| }); | |
| $("runPrevBtn").addEventListener("click", function () { | |
| var slot = runSlot(state.mode); | |
| if (slot && state.runIdx[slot] > 0) { state.runIdx[slot]--; renderRun(slot); } | |
| }); | |
| $("runNextBtn").addEventListener("click", function () { | |
| var slot = runSlot(state.mode); | |
| if (slot && state.runIdx[slot] < (state.runViews[slot] || []).length - 1) { | |
| state.runIdx[slot]++; | |
| renderRun(slot); | |
| } | |
| }); | |
| $("compareSearch").addEventListener("input", function () { | |
| state.compareIdx = 0; | |
| filterCompare(); | |
| }); | |
| $("compareFilter").addEventListener("change", function () { | |
| state.compareIdx = 0; | |
| filterCompare(); | |
| }); | |
| $("compareQidSelect").addEventListener("change", function () { | |
| state.compareIdx = parseInt(this.value, 10) || 0; | |
| renderCompare(); | |
| }); | |
| $("comparePrevBtn").addEventListener("click", function () { | |
| if (state.compareIdx > 0) { state.compareIdx--; renderCompare(); } | |
| }); | |
| $("compareNextBtn").addEventListener("click", function () { | |
| if (state.compareIdx < state.compareView.length - 1) { state.compareIdx++; renderCompare(); } | |
| }); | |
| document.addEventListener("keydown", function (event) { | |
| var tag = (event.target.tagName || "").toLowerCase(); | |
| if (tag === "input" || tag === "select" || tag === "textarea") return; | |
| if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; | |
| var suffix = event.key === "ArrowLeft" ? "PrevBtn" : "NextBtn"; | |
| if (state.mode === "corpus") $("corpus" + suffix).click(); | |
| else if (state.mode === "eval") $("eval" + suffix).click(); | |
| else if (state.mode === "compare") $("compare" + suffix).click(); | |
| else $("run" + suffix).click(); | |
| }); | |
| } | |
| if (window.marked && marked.setOptions) marked.setOptions({ gfm: true, breaks: true }); | |
| setScopedButtonsDisabled(true); | |
| wire(); | |
| loadManifests().catch(function (error) { | |
| $("loading").style.display = "block"; | |
| $("loading").textContent = "Failed to load data: " + error; | |
| }); | |
| })(); | |