finlongdocqa-viewer / viewer.js
finlongdocqa-viewer bot
FinLongDocQA viewer: Corpus (1456 markdown reports) + Eval (7527 Q) static Space
8415278
Raw
History Blame Contribute Delete
14.5 kB
/* FinLongDocQA viewer — vanilla JS, two tabs: Corpus (markdown reports) + Eval. */
(function () {
"use strict";
var $ = function (id) { return document.getElementById(id); };
function esc(s) {
if (s === null || s === undefined) return "";
return String(s)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
var state = {
mode: "corpus",
corpus: [],
corpusView: [],
corpusIdx: 0,
byDocId: {},
mdCache: {}, // doc_id -> [{num, md}]
curPages: [],
curPageIdx: 0,
pendingPage: null, // page number to jump to once the report loads
loadSeq: 0, // guards against out-of-order async renders
questions: [],
evalView: [],
evalIdx: 0,
};
/* ---------------- data loading ---------------- */
function parseJSONL(text) {
var out = [];
text.split("\n").forEach(function (ln) {
ln = ln.trim();
if (ln) { try { out.push(JSON.parse(ln)); } catch (e) {} }
});
return out;
}
function loadAll() {
return Promise.all([
fetch("corpus_index.json").then(function (r) { return r.json(); }),
fetch("eval.json").then(function (r) { return r.json(); }),
]).then(function (res) {
state.corpus = res[0] || [];
state.questions = res[1] || [];
state.corpus.forEach(function (d) { state.byDocId[d.doc_id] = d; });
});
}
/* ---------------- corpus tab ---------------- */
function populateCorpusFilters() {
var years = {};
state.corpus.forEach(function (d) { if (d.year) years[d.year] = true; });
var yf = $("yearFilter");
Object.keys(years).sort().forEach(function (y) {
var o = document.createElement("option"); o.value = y; o.textContent = y; yf.appendChild(o);
});
}
function filterCorpus() {
var q = $("corpusFilter").value.trim().toLowerCase();
var yr = $("yearFilter").value;
var hasQ = $("hasQFilter").value;
state.corpusView = state.corpus.filter(function (d) {
if (yr && d.year !== yr) return false;
if (hasQ === "q" && !d.n_questions) return false;
if (hasQ === "noq" && d.n_questions) return false;
if (q && (d.company || "").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 = d.title + (d.n_questions ? " · " + d.n_questions + "q" : "");
sel.appendChild(o);
});
sel.value = String(state.corpusIdx);
}
function splitPages(md) {
var lines = md.split("\n");
var re = /^#\s*Page\s+(\d+)\s*$/;
var pages = [], cur = null;
for (var i = 0; i < lines.length; i++) {
var m = lines[i].match(re);
if (m) { cur = { num: parseInt(m[1], 10), lines: [] }; pages.push(cur); }
else if (cur) { cur.lines.push(lines[i]); }
}
if (!pages.length) pages = [{ num: 1, lines: lines }];
return pages.map(function (p) { return { num: p.num, md: p.lines.join("\n") }; });
}
function renderCorpus() {
var meta = $("docMetaCard");
var render = $("mdRender");
var nav = $("pageNav");
if (!state.corpusView.length) {
meta.innerHTML = '<div class="empty">No reports match the filter.</div>';
render.innerHTML = ""; nav.style.display = "none";
$("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 =
'<span class="meta-pill">Company: <b>' + esc(d.company) + "</b></span>" +
'<span class="meta-pill">Fiscal year: <b>' + esc(d.year) + "</b></span>" +
'<span class="meta-pill">Cited by <b>' + (d.n_questions || 0) + "</b> question(s)</span>";
var link = '<a class="doc-link" href="' + esc(d.md_url) + '" target="_blank" rel="noopener">raw .md ↗</a>';
meta.innerHTML = "<h2>" + esc(d.title) + " — Annual Report</h2>" +
'<div class="meta-grid">' + pills + link + "</div>";
var seq = ++state.loadSeq;
nav.style.display = "none";
render.className = "md-render loading";
render.textContent = "Loading report…";
var cached = state.mdCache[d.doc_id];
var got = cached ? Promise.resolve(cached)
: fetch(d.md_url).then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.text();
}).then(function (t) { var p = splitPages(t); state.mdCache[d.doc_id] = p; return p; });
got.then(function (pages) {
if (seq !== state.loadSeq) return; // a newer selection superseded us
state.curPages = pages;
var idx = 0;
if (state.pendingPage != null) {
for (var i = 0; i < pages.length; i++) { if (pages[i].num === state.pendingPage) { idx = i; break; } }
state.pendingPage = null;
}
state.curPageIdx = idx;
rebuildPageSelect();
nav.style.display = "flex";
renderPage();
}).catch(function (err) {
if (seq !== state.loadSeq) return;
render.className = "md-render";
render.innerHTML = '<div class="empty">Failed to load report: ' + esc(err.message) +
'<br><a href="' + esc(d.md_url) + '" target="_blank" rel="noopener">open raw markdown ↗</a></div>';
});
}
function rebuildPageSelect() {
var sel = $("pageSelect");
sel.innerHTML = "";
state.curPages.forEach(function (p, i) {
var o = document.createElement("option");
o.value = String(i);
o.textContent = "Page " + p.num;
sel.appendChild(o);
});
}
function renderPage() {
var render = $("mdRender");
var p = state.curPages[state.curPageIdx];
render.className = "md-render";
render.innerHTML = window.marked ? window.marked.parse(p.md) : esc(p.md);
render.scrollTop = 0;
$("pageCounter").textContent = "Page " + p.num + " (" + (state.curPageIdx + 1) + " / " + state.curPages.length + ")";
$("pageSelect").value = String(state.curPageIdx);
$("pagePrevBtn").disabled = state.curPageIdx <= 0;
$("pageNextBtn").disabled = state.curPageIdx >= state.curPages.length - 1;
}
function selectDocById(docId, pageNum) {
$("corpusFilter").value = "";
$("yearFilter").value = "";
$("hasQFilter").value = "";
state.corpusView = state.corpus.slice();
var idx = 0;
for (var i = 0; i < state.corpusView.length; i++) {
if (state.corpusView[i].doc_id === docId) { idx = i; break; }
}
state.corpusIdx = idx;
state.pendingPage = (pageNum != null) ? pageNum : null;
rebuildDocSelect();
renderCorpus();
}
/* ---------------- eval tab ---------------- */
function filterEval() {
var q = $("evalSearch").value.trim().toLowerCase();
var typ = $("typeFilter").value;
state.evalView = state.questions.filter(function (x) {
if (typ && x.type !== typ) return false;
if (q) {
var hay = [x.question, x.company, x.id, x.title].join(" ").toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
});
if (state.evalIdx >= state.evalView.length) state.evalIdx = 0;
rebuildQSelect();
renderEval();
}
function rebuildQSelect() {
var sel = $("qSelect");
sel.innerHTML = "";
state.evalView.forEach(function (x, i) {
var o = document.createElement("option");
o.value = String(i);
var q = (x.question || "").replace(/\s+/g, " ");
if (q.length > 62) q = q.slice(0, 62) + "…";
o.textContent = "[" + x.title + "] " + q;
sel.appendChild(o);
});
sel.value = String(state.evalIdx);
}
function field(lbl, valHtml) {
return '<div class="field"><div class="lbl">' + esc(lbl) + '</div><div class="val">' + valHtml + "</div></div>";
}
function renderEval() {
var card = $("evalCard");
if (!state.evalView.length) {
card.innerHTML = '<div class="empty">No questions match the search.</div>';
$("evalCounter").textContent = "0 / 0";
return;
}
var x = state.evalView[state.evalIdx];
$("qSelect").value = String(state.evalIdx);
$("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length;
var badges =
'<span class="badge id">#' + esc(x.id) + "</span>" +
'<span class="badge type-' + esc(x.type) + '">' + esc(x.type) + "</span>" +
'<span class="badge company" data-doc-id="' + esc(x.doc_id) + '" title="Open report in Corpus tab">' +
esc(x.title) + " ↗</span>";
var pages = x.page_numbers || [];
var pagesHtml = pages.length
? '<div class="evidence-pages">' + pages.map(function (n) {
return '<span class="page-chip" data-doc-id="' + esc(x.doc_id) + '" data-page="' + n +
'">📄 Page ' + esc(n) + "</span>";
}).join("") + "</div>"
: '<span style="color:var(--muted)">none</span>';
var ansHtml = (x.answer === null || x.answer === undefined) ? "—" : esc(x.answer);
card.innerHTML =
'<div class="badges">' + badges + "</div>" +
"<h2>" + esc(x.question) + "</h2>" +
'<div class="answer-banner"><div class="lbl">Ground-truth answer</div><div class="val">' +
ansHtml + "</div></div>" +
field("Evidence pages", pagesHtml) +
field("Reasoning trace (thoughts)",
'<div class="thoughts-text">' + esc(x.thoughts || "—") + "</div>") +
field("Python code",
'<div class="code-block">' + esc(x.python_code || "—") + "</div>");
// company badge -> open report
Array.prototype.forEach.call(card.querySelectorAll(".badge.company[data-doc-id]"), function (el) {
el.addEventListener("click", function () {
setMode("corpus");
selectDocById(this.getAttribute("data-doc-id"));
});
});
// evidence page chip -> open report at that page
Array.prototype.forEach.call(card.querySelectorAll(".page-chip[data-doc-id]"), function (el) {
el.addEventListener("click", function () {
setMode("corpus");
selectDocById(this.getAttribute("data-doc-id"), parseInt(this.getAttribute("data-page"), 10));
});
});
}
/* ---------------- mode switching ---------------- */
function setMode(mode) {
state.mode = mode;
$("modeCorpusBtn").classList.toggle("active", mode === "corpus");
$("modeEvalBtn").classList.toggle("active", mode === "eval");
$("corpusControls").style.display = mode === "corpus" ? "" : "none";
$("evalControls").style.display = mode === "eval" ? "" : "none";
$("corpusView").style.display = mode === "corpus" ? "flex" : "none";
$("evalView").style.display = mode === "eval" ? "block" : "none";
}
/* ---------------- wiring ---------------- */
function wire() {
$("modeCorpusBtn").addEventListener("click", function () { setMode("corpus"); });
$("modeEvalBtn").addEventListener("click", function () { setMode("eval"); });
// corpus
$("corpusFilter").addEventListener("input", filterCorpus);
$("yearFilter").addEventListener("change", filterCorpus);
$("hasQFilter").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(); }
});
// page nav
$("pagePrevBtn").addEventListener("click", function () {
if (state.curPageIdx > 0) { state.curPageIdx--; renderPage(); }
});
$("pageNextBtn").addEventListener("click", function () {
if (state.curPageIdx < state.curPages.length - 1) { state.curPageIdx++; renderPage(); }
});
$("pageSelect").addEventListener("change", function () {
state.curPageIdx = parseInt(this.value, 10) || 0; renderPage();
});
// eval
$("evalSearchBtn").addEventListener("click", filterEval);
$("evalSearch").addEventListener("keydown", function (e) { if (e.key === "Enter") filterEval(); });
$("evalClearBtn").addEventListener("click", function () {
$("evalSearch").value = ""; $("typeFilter").value = ""; filterEval();
});
$("typeFilter").addEventListener("change", filterEval);
$("qSelect").addEventListener("change", function () {
state.evalIdx = parseInt(this.value, 10) || 0; renderEval();
});
$("evalPrevBtn").addEventListener("click", function () {
if (state.evalIdx > 0) { state.evalIdx--; renderEval(); }
});
$("evalNextBtn").addEventListener("click", function () {
if (state.evalIdx < state.evalView.length - 1) { state.evalIdx++; renderEval(); }
});
// keyboard arrows (report nav in corpus, question nav in eval)
document.addEventListener("keydown", function (e) {
var tag = (e.target.tagName || "").toLowerCase();
if (tag === "input" || tag === "select" || tag === "textarea") return;
if (e.key === "ArrowLeft") {
if (state.mode === "corpus") $("corpusPrevBtn").click(); else $("evalPrevBtn").click();
} else if (e.key === "ArrowRight") {
if (state.mode === "corpus") $("corpusNextBtn").click(); else $("evalNextBtn").click();
}
});
}
/* ---------------- boot ---------------- */
loadAll().then(function () {
$("loading").style.display = "none";
$("sidebarFooter").innerHTML =
state.corpus.length + " reports · " + state.questions.length + " questions<br>" +
'<a href="https://github.com/AI-Application-and-Integration-Lab/FinLongDocQA" target="_blank" rel="noopener">GitHub</a> · ' +
'<a href="https://huggingface.co/datasets/Amian/FinLongDocQA" target="_blank" rel="noopener">HF dataset</a> · ' +
'<a href="https://arxiv.org/abs/2604.03664" target="_blank" rel="noopener">arXiv</a>';
populateCorpusFilters();
wire();
setMode("corpus");
filterCorpus();
filterEval();
}).catch(function (err) {
$("loading").textContent = "Failed to load data: " + err;
});
})();