Spaces:
Running
Running
| /* FinanceBench viewer — Corpus, Eval, full runs, and prejoined comparison. */ | |
| (function () { | |
| "use strict"; | |
| var $ = function (id) { return document.getElementById(id); }; | |
| var RUN_REGISTRY = {}; | |
| function esc(s) { | |
| if (s === null || s === undefined) return ""; | |
| return String(s) | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """); | |
| } | |
| function shorten(s, n) { | |
| s = String(s || "").replace(/\s+/g, " ").trim(); | |
| return s.length > n ? s.slice(0, n - 1) + "…" : s; | |
| } | |
| function field(label, valueHtml, cls) { | |
| return '<div class="field' + (cls ? " " + cls : "") + '"><div class="lbl">' + | |
| esc(label) + '</div><div class="val">' + valueHtml + "</div></div>"; | |
| } | |
| function formatPairs(value) { | |
| if (!value || typeof value !== "object") return "—"; | |
| var parts = []; | |
| Object.keys(value).forEach(function (key) { | |
| if (value[key] !== null && value[key] !== undefined) { | |
| parts.push(esc(key) + ": <b>" + esc(value[key]) + "</b>"); | |
| } | |
| }); | |
| return parts.length ? parts.join(" · ") : "—"; | |
| } | |
| function statusFor(record) { | |
| if (!record.answered) return "missing"; | |
| return record.correct === true ? "correct" : "incorrect"; | |
| } | |
| function statusBadge(record) { | |
| var status = statusFor(record); | |
| var label = status === "correct" ? "✓ Correct" : | |
| (status === "missing" ? "∅ Missing" : "✕ Incorrect"); | |
| return '<span class="badge status-' + status + '">' + label + "</span>"; | |
| } | |
| var state = { | |
| mode: "corpus", | |
| corpus: [], | |
| corpusView: [], | |
| corpusIdx: 0, | |
| corpusTextIndex: {}, | |
| corpusTextCache: {}, | |
| corpusChunkIdx: 0, | |
| corpusTextSequence: 0, | |
| questions: [], | |
| evalView: [], | |
| evalIdx: 0, | |
| manifest: null, | |
| runs: {}, | |
| compare: null, | |
| }; | |
| /* ---------------- data loading ---------------- */ | |
| function parseJSONL(text) { | |
| var out = []; | |
| text.split("\n").forEach(function (line) { | |
| var trimmed = line.trim(); | |
| if (!trimmed) return; | |
| try { out.push(JSON.parse(trimmed)); } catch (e) { /* skip bad line */ } | |
| }); | |
| return out; | |
| } | |
| function fetchJSON(path) { | |
| return fetch(path).then(function (response) { | |
| if (!response.ok) throw new Error(path + ": HTTP " + response.status); | |
| return response.json(); | |
| }); | |
| } | |
| function loadAll() { | |
| return Promise.all([ | |
| fetchJSON("corpus_index.json"), | |
| fetch("financebench_open_source.jsonl").then(function (response) { | |
| if (!response.ok) throw new Error("financebench_open_source.jsonl: HTTP " + response.status); | |
| return response.text(); | |
| }), | |
| fetchJSON("corpus_text/index.json"), | |
| fetchJSON("runs/manifest.json"), | |
| ]).then(function (results) { | |
| state.corpus = results[0] || []; | |
| state.questions = parseJSONL(results[1]); | |
| (results[2].rows || []).forEach(function (row) { | |
| state.corpusTextIndex[row.doc_name] = row; | |
| }); | |
| state.manifest = results[3]; | |
| (state.manifest.runs || []).forEach(function (run) { | |
| RUN_REGISTRY[run.slot] = run; | |
| state.runs[run.slot] = { | |
| meta: run, | |
| index: null, | |
| view: [], | |
| idx: 0, | |
| cache: {}, | |
| }; | |
| }); | |
| }); | |
| } | |
| function isRunMode(mode) { | |
| return Object.prototype.hasOwnProperty.call(RUN_REGISTRY, mode); | |
| } | |
| function rememberQid(qid) { | |
| if (qid) window.TrajectoryUI.setQid(qid); | |
| } | |
| function findQidIndex(records) { | |
| var qid = window.TrajectoryUI.getQid(); | |
| if (!qid) return -1; | |
| return records.findIndex(function (record) { | |
| return (record.financebench_id || record.qid) === qid; | |
| }); | |
| } | |
| function loadRun(slot) { | |
| var runState = state.runs[slot]; | |
| if (runState.index) return Promise.resolve(runState); | |
| $("runCard").innerHTML = '<div class="empty">Loading ' + esc(runState.meta.label) + "…</div>"; | |
| return fetchJSON(runState.meta.index).then(function (index) { | |
| runState.index = index; | |
| runState.view = index.records || []; | |
| return runState; | |
| }); | |
| } | |
| function loadCompare() { | |
| if (state.compare) return Promise.resolve(state.compare); | |
| $("compareCard").innerHTML = '<div class="empty">Loading comparison…</div>'; | |
| return fetchJSON(state.manifest.compare.index).then(function (index) { | |
| state.compare = { | |
| index: index, | |
| view: index.records || [], | |
| idx: 0, | |
| cache: {}, | |
| }; | |
| renderCompareSummaries(); | |
| return state.compare; | |
| }); | |
| } | |
| function loadShard(cache, item) { | |
| if (cache[item.qid]) return Promise.resolve(cache[item.qid]); | |
| return fetchJSON(item.path).then(function (record) { | |
| cache[item.qid] = record; | |
| return record; | |
| }); | |
| } | |
| function loadCompressedJSON(path) { | |
| return fetch(path).then(function (response) { | |
| if (!response.ok) throw new Error(path + ": HTTP " + response.status); | |
| return response.text(); | |
| }).then(function (encoded) { | |
| var binary = atob(encoded.trim()); | |
| var bytes = new Uint8Array(binary.length); | |
| for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); | |
| if (typeof DecompressionStream === "undefined") { | |
| throw new Error("This browser cannot decompress FinanceBench text shards."); | |
| } | |
| var stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip")); | |
| return new Response(stream).text(); | |
| }).then(JSON.parse); | |
| } | |
| /* ---------------- corpus tab ---------------- */ | |
| function populateCorpusFilters() { | |
| var sectors = {}, types = {}; | |
| state.corpus.forEach(function (doc) { | |
| if (doc.gics_sector) sectors[doc.gics_sector] = true; | |
| if (doc.doc_type) types[doc.doc_type] = true; | |
| }); | |
| Object.keys(sectors).sort().forEach(function (sector) { | |
| var option = document.createElement("option"); | |
| option.value = sector; option.textContent = sector; $("sectorFilter").appendChild(option); | |
| }); | |
| Object.keys(types).sort().forEach(function (type) { | |
| var option = document.createElement("option"); | |
| option.value = type; option.textContent = type; $("docTypeFilter").appendChild(option); | |
| }); | |
| } | |
| function filterCorpus() { | |
| var query = $("corpusFilter").value.trim().toLowerCase(); | |
| var sector = $("sectorFilter").value; | |
| var type = $("docTypeFilter").value; | |
| state.corpusView = state.corpus.filter(function (doc) { | |
| if (sector && doc.gics_sector !== sector) return false; | |
| if (type && doc.doc_type !== type) return false; | |
| if (query) { | |
| var haystack = (doc.doc_name + " " + (doc.company || "")).toLowerCase(); | |
| if (haystack.indexOf(query) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| 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 = doc.doc_name + (doc.company ? " · " + doc.company : ""); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.corpusIdx); | |
| } | |
| function renderCorpus() { | |
| var meta = $("docMetaCard"); | |
| var textHost = $("docTextContent"); | |
| if (!state.corpusView.length) { | |
| meta.innerHTML = '<div class="empty">No documents match the filter.</div>'; | |
| textHost.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; | |
| var pills = []; | |
| if (doc.company) pills.push('<span class="meta-pill"><b>' + esc(doc.company) + "</b></span>"); | |
| if (doc.gics_sector) pills.push('<span class="meta-pill">Sector: <b>' + esc(doc.gics_sector) + "</b></span>"); | |
| if (doc.doc_type) pills.push('<span class="meta-pill">Type: <b>' + esc(doc.doc_type) + "</b></span>"); | |
| if (doc.doc_period) pills.push('<span class="meta-pill">Period: <b>' + esc(doc.doc_period) + "</b></span>"); | |
| if (!doc.has_meta) pills.push('<span class="meta-pill">no metadata</span>'); | |
| var link = doc.doc_link | |
| ? '<a class="doc-link" href="' + esc(doc.doc_link) + '" target="_blank" rel="noopener">Official source ↗</a>' | |
| : ""; | |
| var bundled = doc.pdf | |
| ? '<a class="doc-link" href="' + esc(doc.pdf) + '" target="_blank" rel="noopener">Open bundled PDF ↗</a>' | |
| : ""; | |
| meta.innerHTML = "<h2>" + esc(doc.doc_name) + "</h2>" + | |
| '<div class="meta-grid">' + pills.join("") + bundled + link + "</div>"; | |
| state.corpusChunkIdx = 0; | |
| textHost.innerHTML = '<div class="empty">Loading extracted filing text…</div>'; | |
| var indexRow = state.corpusTextIndex[doc.doc_name]; | |
| if (!indexRow) { | |
| textHost.innerHTML = '<div class="empty">No extracted text shard is available for this filing.</div>'; | |
| return; | |
| } | |
| var sequence = ++state.corpusTextSequence; | |
| var request = state.corpusTextCache[doc.doc_name] | |
| ? Promise.resolve(state.corpusTextCache[doc.doc_name]) | |
| : loadCompressedJSON("corpus_text/" + indexRow.path).then(function (record) { | |
| state.corpusTextCache[doc.doc_name] = record; | |
| return record; | |
| }); | |
| request.then(function (record) { | |
| if (sequence !== state.corpusTextSequence) return; | |
| renderCorpusText(record); | |
| }).catch(function (error) { | |
| textHost.innerHTML = '<div class="empty">Failed to load filing text: ' + esc(error) + "</div>"; | |
| }); | |
| } | |
| function renderCorpusText(record) { | |
| var chunks = record.chunks || []; | |
| if (!chunks.length) { | |
| $("docTextContent").innerHTML = '<div class="empty">This filing has no extracted chunks.</div>'; | |
| return; | |
| } | |
| if (state.corpusChunkIdx >= chunks.length) state.corpusChunkIdx = chunks.length - 1; | |
| var chunk = chunks[state.corpusChunkIdx]; | |
| var options = chunks.map(function (item, index) { | |
| var label = "Pages " + item.page_start + "–" + item.page_end; | |
| return '<option value="' + index + '"' + | |
| (index === state.corpusChunkIdx ? " selected" : "") + ">" + | |
| esc(label + " · " + item.id) + "</option>"; | |
| }).join(""); | |
| $("docTextContent").innerHTML = | |
| '<div class="chunk-toolbar"><button id="chunkPrevBtn" type="button">← Previous chunk</button>' + | |
| '<select id="chunkSelect">' + options + "</select>" + | |
| '<span>' + (state.corpusChunkIdx + 1) + " / " + chunks.length + "</span>" + | |
| '<button id="chunkNextBtn" type="button">Next chunk →</button></div>' + | |
| '<div class="chunk-heading"><b>Pages ' + esc(chunk.page_start) + "–" + | |
| esc(chunk.page_end) + '</b><code>' + esc(chunk.id) + "</code></div>" + | |
| '<pre class="filing-text">' + esc(chunk.contents) + "</pre>"; | |
| $("chunkSelect").addEventListener("change", function () { | |
| state.corpusChunkIdx = parseInt(this.value, 10) || 0; | |
| renderCorpusText(record); | |
| }); | |
| $("chunkPrevBtn").disabled = state.corpusChunkIdx === 0; | |
| $("chunkNextBtn").disabled = state.corpusChunkIdx === chunks.length - 1; | |
| $("chunkPrevBtn").addEventListener("click", function () { | |
| if (state.corpusChunkIdx > 0) { state.corpusChunkIdx--; renderCorpusText(record); } | |
| }); | |
| $("chunkNextBtn").addEventListener("click", function () { | |
| if (state.corpusChunkIdx < chunks.length - 1) { | |
| state.corpusChunkIdx++; | |
| renderCorpusText(record); | |
| } | |
| }); | |
| } | |
| function selectDocByName(name) { | |
| $("corpusFilter").value = ""; | |
| $("sectorFilter").value = ""; | |
| $("docTypeFilter").value = ""; | |
| state.corpusView = state.corpus.slice(); | |
| state.corpusIdx = 0; | |
| state.corpusView.some(function (doc, index) { | |
| if (doc.doc_name !== name) return false; | |
| state.corpusIdx = index; | |
| return true; | |
| }); | |
| rebuildDocSelect(); | |
| renderCorpus(); | |
| } | |
| /* ---------------- eval tab ---------------- */ | |
| function filterEval() { | |
| var query = $("evalSearch").value.trim().toLowerCase(); | |
| var type = $("qTypeFilter").value; | |
| state.evalView = state.questions.filter(function (question) { | |
| if (type && question.question_type !== type) return false; | |
| if (query) { | |
| var haystack = [ | |
| question.question, question.company, question.financebench_id, | |
| question.answer, question.doc_name, question.justification, | |
| ].join(" ").toLowerCase(); | |
| if (haystack.indexOf(query) === -1) return false; | |
| } | |
| return true; | |
| }); | |
| var target = findQidIndex(state.evalView); | |
| if (target >= 0) state.evalIdx = target; | |
| else if (state.evalIdx >= state.evalView.length) state.evalIdx = 0; | |
| renderEval(); | |
| } | |
| function selectEvalByQid(qid) { | |
| $("evalSearch").value = ""; | |
| $("qTypeFilter").value = ""; | |
| state.evalView = state.questions.slice(); | |
| state.evalIdx = 0; | |
| state.evalView.some(function (question, index) { | |
| if (question.financebench_id !== qid) return false; | |
| state.evalIdx = index; | |
| return true; | |
| }); | |
| setMode("eval"); | |
| renderEval(); | |
| } | |
| 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 question = state.evalView[state.evalIdx]; | |
| rememberQid(question.financebench_id); | |
| $("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length; | |
| var typeClass = "type-" + (question.question_type || ""); | |
| var badges = | |
| '<span class="badge id">' + esc(question.financebench_id) + "</span>" + | |
| (question.question_type ? '<span class="badge ' + typeClass + '">' + esc(question.question_type) + "</span>" : "") + | |
| (question.company ? '<span class="badge">' + esc(question.company) + "</span>" : ""); | |
| var fields = ""; | |
| if (question.question_reasoning) fields += field("Reasoning type", esc(question.question_reasoning)); | |
| if (question.domain_question_num) fields += field("Domain question #", esc(question.domain_question_num)); | |
| fields += field( | |
| "Source document", | |
| '<a class="doclink" data-doc="' + esc(question.doc_name) + '">' + esc(question.doc_name) + " ↗</a>" | |
| ); | |
| if (question.justification) fields += field("Justification", esc(question.justification)); | |
| var evidence = (question.evidence || []).map(function (item) { | |
| var head = '<div class="evidence-head"><span class="page-pill">page ' + | |
| esc(item.evidence_page_num) + "</span>" + | |
| (item.doc_name ? "<span>" + esc(item.doc_name) + "</span>" : "") + "</div>"; | |
| var text = '<div class="evidence-text">' + esc(item.evidence_text) + "</div>"; | |
| var full = ""; | |
| if (item.evidence_text_full_page && item.evidence_text_full_page !== item.evidence_text) { | |
| full = '<details class="full-page"><summary>Show full page extract</summary>' + | |
| '<div class="evidence-text">' + esc(item.evidence_text_full_page) + "</div></details>"; | |
| } | |
| return '<div class="evidence-item">' + head + text + full + "</div>"; | |
| }).join(""); | |
| card.innerHTML = | |
| '<div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(question.question) + "</h2>" + | |
| window.TrajectoryUI.gold(question.answer, "Gold") + | |
| fields + | |
| field( | |
| "Evidence (" + (question.evidence || []).length + ")", | |
| evidence || '<span style="color:var(--muted)">none</span>' | |
| ); | |
| wireContextLinks(card); | |
| } | |
| /* ---------------- run tabs ---------------- */ | |
| function buildRunTabs() { | |
| var host = $("runTabButtons"); | |
| host.innerHTML = ""; | |
| (state.manifest.runs || []).forEach(function (run) { | |
| var button = document.createElement("button"); | |
| button.type = "button"; | |
| button.dataset.mode = run.slot; | |
| button.textContent = run.label; | |
| button.style.setProperty("--tab-accent", run.accent); | |
| button.addEventListener("click", function () { activateMode(run.slot); }); | |
| host.appendChild(button); | |
| }); | |
| var compare = document.createElement("button"); | |
| compare.type = "button"; | |
| compare.dataset.mode = "compare"; | |
| compare.textContent = "Compare"; | |
| compare.style.setProperty("--tab-accent", "#fb7185"); | |
| compare.addEventListener("click", function () { activateMode("compare"); }); | |
| host.appendChild(compare); | |
| } | |
| function filterRun() { | |
| var runState = state.runs[state.mode]; | |
| if (!runState || !runState.index) return; | |
| var selected = runState.view[runState.idx] && runState.view[runState.idx].qid; | |
| var query = $("runSearch").value.trim().toLowerCase(); | |
| var status = $("runStatusFilter").value; | |
| runState.view = runState.index.records.filter(function (record) { | |
| if (status !== "all" && record.status !== status) return false; | |
| if (!query) return true; | |
| var haystack = [ | |
| record.qid, record.question, record.gold, record.prediction, | |
| record.company, record.doc_name, | |
| ].join(" ").toLowerCase(); | |
| return haystack.indexOf(query) !== -1; | |
| }); | |
| runState.idx = 0; | |
| var sharedIndex = findQidIndex(runState.view); | |
| if (sharedIndex >= 0) { | |
| runState.idx = sharedIndex; | |
| } else if (selected) { | |
| runState.view.some(function (record, index) { | |
| if (record.qid !== selected) return false; | |
| runState.idx = index; | |
| return true; | |
| }); | |
| } | |
| rebuildRunQidSelect(runState); | |
| renderRun(); | |
| } | |
| function rebuildRunQidSelect(runState) { | |
| var select = $("runQidSelect"); | |
| select.innerHTML = ""; | |
| runState.view.forEach(function (record, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = record.qid + " · " + shorten(record.question, 72); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(runState.idx); | |
| } | |
| function renderRunSummary(runState) { | |
| var meta = runState.meta; | |
| $("runSummary").style.setProperty("--run-accent", meta.accent); | |
| $("runSummary").innerHTML = | |
| '<div><span class="run-title">' + esc(meta.label) + "</span>" + | |
| '<span class="score-value">' + esc(meta.score.numerator) + "/" + | |
| esc(meta.score.denominator) + " · " + Number(meta.score.percent).toFixed(2) + "%</span></div>" + | |
| '<div class="summary-detail">' + esc(meta.answered) + " answered · " + | |
| esc(meta.missing) + " missing · full-denominator score</div>"; | |
| } | |
| function renderRun() { | |
| var runState = state.runs[state.mode]; | |
| if (!runState || !runState.index) return; | |
| renderRunSummary(runState); | |
| if (!runState.view.length) { | |
| $("runCard").innerHTML = '<div class="empty">No records match these filters.</div>'; | |
| $("runCounter").textContent = "0 / 0"; | |
| $("runQidSelect").innerHTML = ""; | |
| return; | |
| } | |
| var item = runState.view[runState.idx]; | |
| $("runCounter").textContent = (runState.idx + 1) + " / " + runState.view.length; | |
| $("runQidSelect").value = String(runState.idx); | |
| $("runCard").innerHTML = '<div class="empty">Loading ' + esc(item.qid) + "…</div>"; | |
| var expectedMode = state.mode; | |
| loadShard(runState.cache, item).then(function (record) { | |
| if (state.mode !== expectedMode) return; | |
| var current = runState.view[runState.idx]; | |
| if (!current || current.qid !== record.qid) return; | |
| renderRunRecord(record); | |
| }).catch(function (error) { | |
| $("runCard").innerHTML = '<div class="empty">Failed to load record: ' + esc(error) + "</div>"; | |
| }); | |
| } | |
| function renderRunRecord(record) { | |
| rememberQid(record.qid); | |
| var metadata = record.metadata || {}; | |
| var isE2E = state.mode.indexOf("e2e") === 0; | |
| var badges = | |
| '<span class="badge id">' + esc(record.qid) + "</span>" + | |
| statusBadge(record) + | |
| (metadata.company ? '<span class="badge">' + esc(metadata.company) + "</span>" : "") + | |
| (metadata.question_type ? '<span class="badge type-' + esc(metadata.question_type) + '">' + | |
| esc(metadata.question_type) + "</span>" : "") + | |
| (metadata.question_reasoning ? '<span class="badge">' + | |
| esc(metadata.question_reasoning) + "</span>" : ""); | |
| var documentLink = metadata.doc_name | |
| ? '<a data-doc="' + esc(metadata.doc_name) + '">Open ' + esc(metadata.doc_name) + " ↗</a>" | |
| : ""; | |
| var links = '<div class="context-links"><a data-eval-qid="' + esc(record.qid) + | |
| '">Open in Eval ↗</a>' + (isE2E ? "" : documentLink) + "</div>"; | |
| var prediction = record.answered | |
| ? '<div class="response-text">' + esc(record.prediction) + "</div>" | |
| : '<div class="missing-response">No response was produced. This record counts as incorrect.</div>'; | |
| var judge = ""; | |
| if (record.extracted_answer !== null && record.extracted_answer !== undefined) { | |
| judge += field("Extracted judge answer", esc(record.extracted_answer)); | |
| } | |
| if (record.judge_confidence !== null && record.judge_confidence !== undefined) { | |
| judge += field("Judge confidence", esc(record.judge_confidence) + "%"); | |
| } | |
| if (record.judge_text) { | |
| judge += '<details class="detail-block"><summary>Judge details</summary>' + | |
| '<pre>' + esc(record.judge_text) + "</pre></details>"; | |
| } | |
| var operational = '<div class="operational-grid">' + | |
| metric("Stop", record.stop_reason) + | |
| metric("Finish", record.finish_reason) + | |
| metric("Turns", record.turns) + | |
| metric("Tools", formatPairs(record.tool_counts), "", true) + | |
| metric("Tokens", formatPairs(record.token_usage), "wide", true) + | |
| (record.failure_reason ? metric("Failure", record.failure_reason, "wide failure") : "") + | |
| "</div>"; | |
| $("runCard").innerHTML = | |
| '<div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(record.question) + "</h2>" + | |
| links + | |
| window.TrajectoryUI.gold(record.gold, "Gold") + | |
| (isE2E && documentLink | |
| ? window.TrajectoryUI.supporting("Supporting filing", documentLink) | |
| : "") + | |
| field("Prediction", prediction) + | |
| judge + | |
| field("Run details", operational) + | |
| renderTrajectory(record.events || []); | |
| wireContextLinks($("runCard")); | |
| window.TrajectoryUI.bind($("runCard")); | |
| } | |
| function metric(label, value, cls, isHtml) { | |
| var display = value === null || value === undefined || value === "" ? "—" : value; | |
| return '<div class="metric' + (cls ? " " + cls : "") + '"><span>' + | |
| esc(label) + "</span><div>" + (isHtml ? display : esc(display)) + "</div></div>"; | |
| } | |
| function renderTrajectory(events) { | |
| return window.TrajectoryUI.render(events, "Agent trajectory"); | |
| } | |
| /* ---------------- comparison tab ---------------- */ | |
| function renderCompareSummaries() { | |
| if (!state.compare) return; | |
| $("compareSummaries").innerHTML = state.compare.index.runs.map(function (run) { | |
| return '<div class="score-card" style="--run-accent:' + esc(run.accent) + '">' + | |
| '<div class="score-card-label">' + esc(run.label) + "</div>" + | |
| '<div class="score-card-value">' + esc(run.score.numerator) + "/" + | |
| esc(run.score.denominator) + " <span>" + Number(run.score.percent).toFixed(2) + "%</span></div>" + | |
| '<div class="score-card-detail">' + esc(run.answered) + " answered · " + | |
| esc(run.missing) + " missing</div></div>"; | |
| }).join(""); | |
| } | |
| function filterCompare() { | |
| if (!state.compare) return; | |
| var selected = state.compare.view[state.compare.idx] && state.compare.view[state.compare.idx].qid; | |
| var query = $("compareSearch").value.trim().toLowerCase(); | |
| var filter = $("compareFilter").value; | |
| state.compare.view = state.compare.index.records.filter(function (record) { | |
| if (filter === "disagreement" && !record.flags.disagreement) return false; | |
| if (filter === "missing" && !record.flags.any_missing) return false; | |
| if (filter === "e2e-only" && !record.flags.only_e2e_correct) return false; | |
| if (!query) return true; | |
| return [record.qid, record.question, record.company].join(" ").toLowerCase().indexOf(query) !== -1; | |
| }); | |
| state.compare.idx = 0; | |
| var sharedIndex = findQidIndex(state.compare.view); | |
| if (sharedIndex >= 0) { | |
| state.compare.idx = sharedIndex; | |
| } else if (selected) { | |
| state.compare.view.some(function (record, index) { | |
| if (record.qid !== selected) return false; | |
| state.compare.idx = index; | |
| return true; | |
| }); | |
| } | |
| rebuildCompareQidSelect(); | |
| renderCompare(); | |
| } | |
| function rebuildCompareQidSelect() { | |
| var select = $("compareQidSelect"); | |
| select.innerHTML = ""; | |
| state.compare.view.forEach(function (record, index) { | |
| var option = document.createElement("option"); | |
| option.value = String(index); | |
| option.textContent = record.qid + " · " + shorten(record.question, 72); | |
| select.appendChild(option); | |
| }); | |
| select.value = String(state.compare.idx); | |
| } | |
| function renderCompare() { | |
| if (!state.compare) return; | |
| if (!state.compare.view.length) { | |
| $("compareCard").innerHTML = '<div class="card"><div class="empty">No questions match this comparison filter.</div></div>'; | |
| $("compareCounter").textContent = "0 / 0"; | |
| $("compareQidSelect").innerHTML = ""; | |
| return; | |
| } | |
| var item = state.compare.view[state.compare.idx]; | |
| $("compareCounter").textContent = (state.compare.idx + 1) + " / " + state.compare.view.length; | |
| $("compareQidSelect").value = String(state.compare.idx); | |
| $("compareCard").innerHTML = '<div class="card"><div class="empty">Loading ' + esc(item.qid) + "…</div></div>"; | |
| loadShard(state.compare.cache, item).then(function (record) { | |
| if (state.mode !== "compare") return; | |
| var current = state.compare.view[state.compare.idx]; | |
| if (!current || current.qid !== record.qid) return; | |
| renderCompareRecord(record); | |
| }).catch(function (error) { | |
| $("compareCard").innerHTML = '<div class="card"><div class="empty">Failed to load record: ' + | |
| esc(error) + "</div></div>"; | |
| }); | |
| } | |
| function renderCompareRecord(record) { | |
| rememberQid(record.qid); | |
| var metadata = record.metadata || {}; | |
| var badges = | |
| '<span class="badge id">' + esc(record.qid) + "</span>" + | |
| (metadata.company ? '<span class="badge">' + esc(metadata.company) + "</span>" : "") + | |
| (metadata.question_reasoning ? '<span class="badge">' + | |
| esc(metadata.question_reasoning) + "</span>" : "") + | |
| (record.flags.disagreement ? '<span class="badge compare-flag">disagreement</span>' : "") + | |
| (record.flags.any_missing ? '<span class="badge status-missing">any missing</span>' : ""); | |
| var links = | |
| '<div class="context-links"><a data-eval-qid="' + esc(record.qid) + '">Open in Eval ↗</a>' + | |
| (metadata.doc_name ? '<a data-doc="' + esc(metadata.doc_name) + '">Open ' + | |
| esc(metadata.doc_name) + " ↗</a>" : "") + "</div>"; | |
| var columns = state.compare.index.runs.map(function (run) { | |
| var value = record.runs[run.slot]; | |
| var prediction = value.answered | |
| ? '<div class="response-text">' + esc(value.prediction) + "</div>" | |
| : '<div class="missing-response">No response; counted incorrect.</div>'; | |
| var details = []; | |
| if (value.extracted_answer !== null && value.extracted_answer !== undefined) { | |
| details.push("<b>Extracted:</b> " + esc(value.extracted_answer)); | |
| } | |
| if (value.judge_confidence !== null && value.judge_confidence !== undefined) { | |
| details.push("<b>Confidence:</b> " + esc(value.judge_confidence) + "%"); | |
| } | |
| if (value.stop_reason || value.finish_reason) { | |
| details.push("<b>Stop / finish:</b> " + esc(value.stop_reason || "—") + | |
| " / " + esc(value.finish_reason || "—")); | |
| } | |
| if (value.failure_reason) details.push("<b>Failure:</b> " + esc(value.failure_reason)); | |
| return '<article class="compare-run-card" style="--run-accent:' + esc(run.accent) + '">' + | |
| '<div class="compare-run-head"><h3>' + esc(run.label) + "</h3>" + | |
| statusBadge(value) + "</div>" + | |
| prediction + | |
| (details.length ? '<div class="compare-details">' + details.join("<br>") + "</div>" : "") + | |
| "</article>"; | |
| }).join(""); | |
| $("compareCard").innerHTML = | |
| '<div class="card compare-question"><div class="badges">' + badges + "</div>" + | |
| "<h2>" + esc(record.question) + "</h2>" + links + | |
| window.TrajectoryUI.gold(record.gold, "Gold") + "</div>" + | |
| '<div class="compare-grid">' + columns + "</div>"; | |
| wireContextLinks($("compareCard")); | |
| } | |
| /* ---------------- mode switching and links ---------------- */ | |
| function wireContextLinks(container) { | |
| Array.prototype.forEach.call(container.querySelectorAll("[data-doc]"), function (link) { | |
| link.addEventListener("click", function () { | |
| setMode("corpus"); | |
| selectDocByName(this.getAttribute("data-doc")); | |
| }); | |
| }); | |
| Array.prototype.forEach.call(container.querySelectorAll("[data-eval-qid]"), function (link) { | |
| link.addEventListener("click", function () { | |
| selectEvalByQid(this.getAttribute("data-eval-qid")); | |
| }); | |
| }); | |
| } | |
| function setMode(mode) { | |
| var previousMode = state.mode; | |
| var leftStructures = Boolean(window.__e2eStructuresJustClosed); | |
| window.__e2eStructuresJustClosed = false; | |
| state.mode = mode; | |
| if (mode === "eval" && (previousMode !== "eval" || leftStructures)) { | |
| $("evalSearch").value = ""; | |
| $("qTypeFilter").value = ""; | |
| state.evalView = state.questions.slice(); | |
| } | |
| if (mode === "eval" && state.evalView.length) { | |
| var sharedIndex = findQidIndex(state.evalView); | |
| if (sharedIndex >= 0) state.evalIdx = sharedIndex; | |
| } | |
| Array.prototype.forEach.call($("viewToggle").querySelectorAll("[data-mode]"), function (button) { | |
| button.classList.toggle("active", button.dataset.mode === mode); | |
| }); | |
| $("corpusControls").style.display = mode === "corpus" ? "" : "none"; | |
| $("evalControls").style.display = mode === "eval" ? "" : "none"; | |
| $("runControls").style.display = isRunMode(mode) ? "" : "none"; | |
| $("compareControls").style.display = mode === "compare" ? "" : "none"; | |
| $("corpusView").style.display = mode === "corpus" ? "flex" : "none"; | |
| $("evalView").style.display = mode === "eval" ? "block" : "none"; | |
| $("runView").style.display = isRunMode(mode) ? "block" : "none"; | |
| $("compareView").style.display = mode === "compare" ? "block" : "none"; | |
| if (mode === "eval" && state.evalView.length) renderEval(); | |
| } | |
| function activateMode(mode) { | |
| setMode(mode); | |
| if (isRunMode(mode)) { | |
| loadRun(mode).then(function () { | |
| if (state.mode !== mode) return; | |
| $("runSearch").value = ""; | |
| $("runStatusFilter").value = "all"; | |
| filterRun(); | |
| }).catch(showLoadError); | |
| } else if (mode === "compare") { | |
| loadCompare().then(function () { | |
| if (state.mode !== "compare") return; | |
| $("compareSearch").value = ""; | |
| $("compareFilter").value = "all"; | |
| filterCompare(); | |
| }).catch(showLoadError); | |
| } | |
| } | |
| function showLoadError(error) { | |
| var target = state.mode === "compare" ? $("compareCard") : $("runCard"); | |
| target.innerHTML = '<div class="card"><div class="empty">Failed to load data: ' + | |
| esc(error) + "</div></div>"; | |
| } | |
| /* ---------------- wiring ---------------- */ | |
| function wire() { | |
| $("modeCorpusBtn").addEventListener("click", function () { setMode("corpus"); }); | |
| $("modeEvalBtn").addEventListener("click", function () { setMode("eval"); }); | |
| $("corpusFilter").addEventListener("input", filterCorpus); | |
| $("sectorFilter").addEventListener("change", filterCorpus); | |
| $("docTypeFilter").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(); } | |
| }); | |
| $("evalSearchBtn").addEventListener("click", filterEval); | |
| $("evalSearch").addEventListener("keydown", function (event) { | |
| if (event.key === "Enter") filterEval(); | |
| }); | |
| $("evalClearBtn").addEventListener("click", function () { | |
| $("evalSearch").value = ""; $("qTypeFilter").value = ""; filterEval(); | |
| }); | |
| $("qTypeFilter").addEventListener("change", filterEval); | |
| $("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", filterRun); | |
| $("runStatusFilter").addEventListener("change", filterRun); | |
| $("runQidSelect").addEventListener("change", function () { | |
| var runState = state.runs[state.mode]; | |
| runState.idx = parseInt(this.value, 10) || 0; | |
| renderRun(); | |
| }); | |
| $("runPrevBtn").addEventListener("click", function () { | |
| var runState = state.runs[state.mode]; | |
| if (runState && runState.idx > 0) { runState.idx--; renderRun(); } | |
| }); | |
| $("runNextBtn").addEventListener("click", function () { | |
| var runState = state.runs[state.mode]; | |
| if (runState && runState.idx < runState.view.length - 1) { | |
| runState.idx++; renderRun(); | |
| } | |
| }); | |
| $("compareSearch").addEventListener("input", filterCompare); | |
| $("compareFilter").addEventListener("change", filterCompare); | |
| $("compareQidSelect").addEventListener("change", function () { | |
| state.compare.idx = parseInt(this.value, 10) || 0; | |
| renderCompare(); | |
| }); | |
| $("comparePrevBtn").addEventListener("click", function () { | |
| if (state.compare && state.compare.idx > 0) { state.compare.idx--; renderCompare(); } | |
| }); | |
| $("compareNextBtn").addEventListener("click", function () { | |
| if (state.compare && state.compare.idx < state.compare.view.length - 1) { | |
| state.compare.idx++; 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 direction = event.key === "ArrowLeft" ? "Prev" : "Next"; | |
| if (state.mode === "corpus") $("corpus" + direction + "Btn").click(); | |
| else if (state.mode === "eval") $("eval" + direction + "Btn").click(); | |
| else if (state.mode === "compare") $("compare" + direction + "Btn").click(); | |
| else if (isRunMode(state.mode)) $("run" + direction + "Btn").click(); | |
| }); | |
| } | |
| /* ---------------- boot ---------------- */ | |
| loadAll().then(function () { | |
| $("loading").style.display = "none"; | |
| $("sidebarFooter").innerHTML = | |
| state.corpus.length + " documents · " + state.questions.length + " questions<br>" + | |
| "Run scores use all " + esc(state.manifest.denominator) + " questions.<br>" + | |
| '<a href="https://github.com/patronus-ai/financebench" target="_blank" rel="noopener">FinanceBench</a> · ' + | |
| '<a href="https://huggingface.co/datasets/PatronusAI/financebench" target="_blank" rel="noopener">HF dataset</a>'; | |
| populateCorpusFilters(); | |
| buildRunTabs(); | |
| wire(); | |
| setMode("corpus"); | |
| filterCorpus(); | |
| filterEval(); | |
| }).catch(function (error) { | |
| $("loading").textContent = "Failed to load data: " + error; | |
| }); | |
| })(); | |