herb-viewer / viewer.js
timchen0618's picture
Add HERB computation comparison split
8b27dd6 verified
Raw
History Blame Contribute Delete
47.2 kB
/* HERB viewer — corpus, complete eval set, six answerable-only runs, and comparison. */
(function () {
"use strict";
var $ = function (id) { return document.getElementById(id); };
var HERB_BASE = "https://huggingface.co/datasets/Salesforce/HERB/resolve/main/products/";
var RECORD_CACHE_LIMIT = 8;
var ARTIFACT_TYPES = [
{ key: "slack", label: "Slack", icon: "💬" },
{ key: "documents", label: "Documents", icon: "📄" },
{ key: "meeting_transcripts", label: "Meeting transcripts", icon: "🎙" },
{ key: "meeting_chats", label: "Meeting chats", icon: "💭" },
{ key: "urls", label: "URLs", icon: "🔗" },
{ key: "prs", label: "Pull requests", icon: "🔀" },
];
var TYPE_LABEL = {}; ARTIFACT_TYPES.forEach(function (t) { TYPE_LABEL[t.key] = t; });
function esc(s) {
if (s === null || s === undefined) return "";
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function truncate(s, n) { s = String(s || "").replace(/\s+/g, " ").trim(); return s.length > n ? s.slice(0, n) + "…" : s; }
var state = {
mode: "corpus",
products: [], productByName: {},
employees: {}, customers: {},
curProduct: null, productCache: {},
curType: null, artifactsView: [], artifactIdx: 0,
pendingArtifactId: null, loadSeq: 0,
questions: [], evalView: [], evalIdx: 0,
manifest: null, runBySlot: {}, runIndexes: {}, runIndexPromises: {},
runRecords: {}, runRecordOrder: [], runRecordPromises: {},
runSlot: null, runView: [], runIdx: 0, runLoadSeq: 0, runFilterSeq: 0,
compareIndex: null, compareIndexPromise: null,
compareRecords: {}, compareRecordOrder: [], compareRecordPromises: {},
compareView: [], compareIdx: 0, compareLoadSeq: 0, compareFilterSeq: 0,
};
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()];
}
/* ---------------- id resolution ---------------- */
function resolveEid(eid) {
var e = state.employees[eid];
return e ? e.name : eid;
}
function eidTitle(eid) {
var e = state.employees[eid];
return e ? (e.role || "") + (e.org ? " · " + e.org : "") : "";
}
// escape text, then turn eid_xxxx tokens into resolved name tags
function escResolve(text) {
var out = esc(text);
out = out.replace(/eid_[0-9a-f]{6,}/g, function (m) {
return '<span class="eid-tag" title="' + esc(m + " — " + eidTitle(m)) + '">' + esc(resolveEid(m)) + "</span>";
});
return out;
}
function normalizeQid(value) {
return String(value || "").replace(/#a(\d+)$/, "_a$1");
}
function rememberQid(value) {
var qid = normalizeQid(value);
if (qid) window.TrajectoryUI.setQid(qid);
}
function questionMeta(value) {
return window.HerbQuestionMetaByQid[normalizeQid(value)] || {};
}
function computationFilterMatches(value, filter) {
if (!filter) return true;
return Boolean(questionMeta(value).computation_comparison) === (filter === "yes");
}
function computationBadge(record) {
var meta = record.computation_comparison === undefined
? questionMeta(record.qid || record.gid)
: record;
var isComputation = Boolean(meta.computation_comparison);
var label = isComputation ? "computation / comparison" : "other";
return '<span class="badge computation-' + (isComputation ? "yes" : "no") +
'" title="HERB computation/comparison split">' + label + "</span>";
}
function sharedIndex(records) {
var qid = window.TrajectoryUI.getQid();
if (!qid) return -1;
return records.findIndex(function (record) {
return normalizeQid(record.qid || record.gid) === qid;
});
}
function displayGold(value) {
var values = Array.isArray(value) ? value : [value];
return values.map(function (item) {
return typeof item === "string" && /^eid_[0-9a-f]+$/.test(item) ? resolveEid(item) : item;
});
}
window.HerbDisplayGold = displayGold;
/* ---------------- data loading ---------------- */
function loadAll() {
return Promise.all([
fetch("products.json").then(function (r) { return r.json(); }),
fetch("eval.json").then(function (r) { return r.json(); }),
fetch("employees.json").then(function (r) { return r.json(); }),
fetch("customers.json").then(function (r) { return r.json(); }),
fetch("runs/manifest.json").then(function (r) { if (!r.ok) throw new Error("run manifest HTTP " + r.status); return r.json(); }),
]).then(function (res) {
state.products = res[0] || [];
state.questions = res[1] || [];
state.employees = res[2] || {};
(res[3] || []).forEach(function (c) { state.customers[c.id] = c; });
state.manifest = res[4] || { runs: [] };
(state.manifest.runs || []).forEach(function (run) { state.runBySlot[run.slot] = run; });
state.products.forEach(function (p) { state.productByName[p.name] = p; });
window.HerbQuestionMetaByQid = {};
state.questions.forEach(function (question) {
window.HerbQuestionMetaByQid[normalizeQid(question.gid)] = question;
});
});
}
function loadProduct(name) {
if (state.productCache[name]) return Promise.resolve(state.productCache[name]);
return fetch(HERB_BASE + name + ".json").then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
}).then(function (d) {
var byType = {}, byId = {};
ARTIFACT_TYPES.forEach(function (t) {
var items = d[t.key] || [];
byType[t.key] = items;
items.forEach(function (it) { if (it && it.id != null && !(it.id in byId)) byId[it.id] = { type: t.key, item: it }; });
});
var rec = { data: d, byType: byType, byId: byId };
state.productCache[name] = rec;
return rec;
});
}
/* ---------------- corpus tab ---------------- */
function populateProductSelects() {
var cp = $("corpusProduct"), ep = $("evalProduct"), rp = $("runProduct"), xp = $("compareProduct");
state.products.forEach(function (p) {
var o = document.createElement("option"); o.value = p.name;
o.textContent = p.name + " (" + p.n_artifacts + ")"; cp.appendChild(o);
var o2 = document.createElement("option"); o2.value = p.name; o2.textContent = p.name; ep.appendChild(o2);
var o3 = document.createElement("option"); o3.value = p.name; o3.textContent = p.name; rp.appendChild(o3);
var o4 = document.createElement("option"); o4.value = p.name; o4.textContent = p.name; xp.appendChild(o4);
});
["person", "content", "company", "pr", "url"].forEach(function (typ) {
var r = document.createElement("option"); r.value = typ; r.textContent = typ; $("runType").appendChild(r);
var x = document.createElement("option"); x.value = typ; x.textContent = typ; $("compareType").appendChild(x);
});
}
function populateTypeSelect(rec) {
var sel = $("artifactType");
sel.innerHTML = "";
ARTIFACT_TYPES.forEach(function (t) {
var n = (rec.byType[t.key] || []).length;
if (!n) return;
var o = document.createElement("option"); o.value = t.key;
o.textContent = t.icon + " " + t.label + " (" + n + ")";
sel.appendChild(o);
});
}
function onProductChange() {
var name = $("corpusProduct").value;
state.curProduct = name;
var seq = ++state.loadSeq;
$("artifactMeta").innerHTML = "";
$("artifactBody").className = "artifact-body loading";
$("artifactBody").textContent = "Loading " + name + "…";
loadProduct(name).then(function (rec) {
if (seq !== state.loadSeq) return;
populateTypeSelect(rec);
// choose type: pending artifact's type, else current if still valid, else first
var chosen = null;
if (state.pendingArtifactId != null && rec.byId[state.pendingArtifactId]) {
chosen = rec.byId[state.pendingArtifactId].type;
} else if (state.curType && (rec.byType[state.curType] || []).length) {
chosen = state.curType;
}
if (!chosen) chosen = $("artifactType").options.length ? $("artifactType").options[0].value : null;
state.curType = chosen;
$("artifactType").value = chosen || "";
$("corpusFilter").value = "";
filterArtifacts();
}).catch(function (err) {
if (seq !== state.loadSeq) return;
$("artifactBody").className = "artifact-body";
$("artifactBody").innerHTML = '<div class="empty">Failed to load product: ' + esc(err.message) + "</div>";
});
}
function artifactTitle(type, it) {
switch (type) {
case "slack":
var ch = (it.Channel && it.Channel.name) ? "#" + it.Channel.name + ": " : "";
var txt = (it.Message && it.Message.User && it.Message.User.text) || "";
return ch + truncate(txt, 60) || it.id;
case "documents": return (it.type ? it.type + " — " : "") + it.id;
case "meeting_transcripts": return (it.document_type ? it.document_type + " — " : "") + it.id;
case "meeting_chats": return it.id;
case "urls": return it.description ? truncate(it.description, 70) : (it.link || it.id);
case "prs": return (it.number ? "#" + it.number + " " : "") + (it.title || it.id);
default: return it.id;
}
}
function filterArtifacts() {
var rec = state.productCache[state.curProduct];
if (!rec || !state.curType) { state.artifactsView = []; }
else {
var q = $("corpusFilter").value.trim().toLowerCase();
var items = rec.byType[state.curType] || [];
state.artifactsView = items.map(function (it) { return { item: it, title: artifactTitle(state.curType, it) }; })
.filter(function (a) { return !q || a.title.toLowerCase().indexOf(q) !== -1 || String(a.item.id).toLowerCase().indexOf(q) !== -1; });
}
if (state.artifactIdx >= state.artifactsView.length) state.artifactIdx = 0;
rebuildArtifactSelect();
renderArtifact();
}
function rebuildArtifactSelect() {
var sel = $("artifactSelect");
sel.innerHTML = "";
state.artifactsView.forEach(function (a, i) {
var o = document.createElement("option"); o.value = String(i); o.textContent = a.title; sel.appendChild(o);
});
sel.value = String(state.artifactIdx);
}
function kvRow(pairs) {
var rows = pairs.filter(function (p) { return p[1] !== undefined && p[1] !== null && p[1] !== ""; })
.map(function (p) { return '<div class="k">' + esc(p[0]) + '</div><div class="v">' + p[1] + "</div>"; }).join("");
return rows ? '<div class="kv">' + rows + "</div>" : "";
}
function renderSlackReply(rep) {
var u = (rep.Message && rep.Message.User) || rep.User || rep;
var who = u.userId ? resolveEid(u.userId) : (rep.sender ? resolveEid(rep.sender) : "");
var txt = u.text || rep.text || rep.message || "";
return '<div class="slack-msg slack-reply"><span class="who">' + esc(who) + "</span>" +
(u.timestamp ? '<span class="ts">' + esc(u.timestamp) + "</span>" : "") +
'<div class="txt">' + escResolve(txt) + "</div></div>";
}
function renderArtifactBody(type, it) {
if (type === "slack") {
var u = (it.Message && it.Message.User) || {};
var who = u.userId ? resolveEid(u.userId) : "";
var reacts = (it.Message && it.Message.Reactions) || [];
var html = '<div class="slack-msg"><span class="who">' + esc(who) + "</span>" +
(u.timestamp ? '<span class="ts">' + esc(u.timestamp) + "</span>" : "") +
'<div class="txt">' + escResolve(u.text) + "</div></div>";
if (reacts.length) html += '<div class="field-title">Reactions</div><div class="pre-text">' + esc(JSON.stringify(reacts)) + "</div>";
var replies = it.ThreadReplies || [];
if (replies.length) {
html += '<div class="field-title">Thread replies (' + replies.length + ")</div>";
html += replies.map(renderSlackReply).join("");
}
return html;
}
if (type === "documents") {
var kv = kvRow([["Type", esc(it.type)], ["Author", escResolve(it.author)], ["Date", esc(it.date)]]);
var body = window.marked ? window.marked.parse(it.content || "") : esc(it.content);
return kv + '<div class="field-title">Content</div><div class="md-render">' + body + "</div>";
}
if (type === "meeting_transcripts") {
var parts = (it.participants || []).map(function (e) { return '<span class="eid-tag" title="' + esc(e) + '">' + esc(resolveEid(e)) + "</span>"; }).join(", ");
var kv2 = kvRow([["Meeting type", esc(it.document_type)], ["Date", esc(it.date)], ["Participants", parts]]);
return kv2 + '<div class="field-title">Transcript</div><div class="pre-text">' + escResolve(it.transcript) + "</div>";
}
if (type === "meeting_chats") {
return '<div class="field-title">Chat</div><div class="pre-text">' + escResolve(it.text) + "</div>";
}
if (type === "urls") {
return kvRow([["Link", '<a href="' + esc(it.link) + '" target="_blank" rel="noopener">' + esc(it.link) + " ↗</a>"],
["Description", escResolve(it.description)]]);
}
if (type === "prs") {
var kv3 = kvRow([
["Number", esc(it.number)],
["State", '<span class="state-badge state-' + esc(it.state) + '">' + esc(it.state) + "</span>"],
["Merged", esc(it.merged)], ["Mergeable", esc(it.mergeable)],
["Author", esc(it.user && it.user.login)], ["Created", esc(it.created_at)],
["Link", it.link ? '<a href="' + esc(it.link) + '" target="_blank" rel="noopener">' + esc(it.link) + " ↗</a>" : ""],
]);
var summary = it.summary ? '<div class="field-title">Summary</div><div>' + escResolve(it.summary) + "</div>" : "";
var reviews = "";
if (it.reviews) {
var parsed = null;
try { parsed = JSON.parse(String(it.reviews).replace(/'/g, '"')); } catch (e) { parsed = null; }
if (parsed && parsed.length) {
reviews = '<div class="field-title">Reviews (' + parsed.length + ")</div>" + parsed.map(function (r) {
return '<div class="review-item"><span class="state-badge state-' + esc(r.state) + '">' + esc(r.state) + "</span> " +
esc(r.user && r.user.login) + (r.submitted_at ? ' · <span style="color:var(--muted)">' + esc(r.submitted_at) + "</span>" : "") +
(r.comment ? '<div style="margin-top:4px">' + escResolve(r.comment) + "</div>" : "") + "</div>";
}).join("");
} else {
reviews = '<div class="field-title">Reviews</div><div class="pre-text">' + esc(it.reviews) + "</div>";
}
}
return kv3 + summary + reviews;
}
return '<div class="pre-text">' + esc(JSON.stringify(it, null, 2)) + "</div>";
}
function renderArtifact() {
var meta = $("artifactMeta"), body = $("artifactBody");
if (!state.artifactsView.length) {
meta.innerHTML = '<div class="empty">No artifacts match.</div>';
body.className = "artifact-body"; body.innerHTML = "";
$("corpusCounter").textContent = "0 / 0";
return;
}
var a = state.artifactsView[state.artifactIdx];
var it = a.item, type = state.curType;
$("artifactSelect").value = String(state.artifactIdx);
$("corpusCounter").textContent = (state.artifactIdx + 1) + " / " + state.artifactsView.length;
var tl = TYPE_LABEL[type];
var link = "";
if (it.link) link = '<a class="doc-link" href="' + esc(it.link) + '" target="_blank" rel="noopener">open link ↗</a>';
else if (it.document_link) link = '<a class="doc-link" href="' + esc(it.document_link) + '" target="_blank" rel="noopener">source ↗</a>';
meta.innerHTML = "<h2>" + esc(a.title) + "</h2>" +
'<div class="meta-grid">' +
'<span class="meta-pill">Product: <b>' + esc(state.curProduct) + "</b></span>" +
'<span class="meta-pill type-pill ' + type + '">' + esc(tl.label) + "</span>" +
'<span class="meta-pill mono">' + esc(it.id) + "</span>" + link + "</div>";
body.className = "artifact-body";
body.innerHTML = renderArtifactBody(type, it);
body.scrollTop = 0;
}
function selectArtifact(product, artifactId) {
$("corpusProduct").value = product;
state.pendingArtifactId = artifactId;
state.artifactIdx = 0;
// onProductChange will honor pendingArtifactId to pick the type; then locate the item
var seq = ++state.loadSeq;
state.curProduct = product;
$("artifactBody").className = "artifact-body loading";
$("artifactBody").textContent = "Loading " + product + "…";
loadProduct(product).then(function (rec) {
if (seq !== state.loadSeq) return;
populateTypeSelect(rec);
var hit = rec.byId[artifactId];
state.curType = hit ? hit.type : ($("artifactType").options.length ? $("artifactType").options[0].value : null);
$("artifactType").value = state.curType || "";
$("corpusFilter").value = "";
state.pendingArtifactId = null;
// build view then locate index of the artifact
var items = rec.byType[state.curType] || [];
state.artifactsView = items.map(function (x) { return { item: x, title: artifactTitle(state.curType, x) }; });
var idx = 0;
for (var i = 0; i < state.artifactsView.length; i++) { if (state.artifactsView[i].item.id === artifactId) { idx = i; break; } }
state.artifactIdx = idx;
rebuildArtifactSelect();
renderArtifact();
});
}
/* ---------------- eval tab ---------------- */
function filterEval() {
var q = $("evalSearch").value.trim().toLowerCase();
var prod = $("evalProduct").value;
var kind = $("kindFilter").value;
var typ = $("qTypeFilter").value;
var computationFilter = $("evalComputationFilter").value;
state.evalView = state.questions.filter(function (x) {
if (prod && x.product !== prod) return false;
if (kind && x.kind !== kind) return false;
if (typ && x.type !== typ) return false;
if (!computationFilterMatches(x.gid, computationFilter)) return false;
if (q) {
var hay = [x.question, x.gid, x.product, Array.isArray(x.ground_truth) ? x.ground_truth.join(" ") : x.ground_truth].join(" ").toLowerCase();
if (hay.indexOf(q) === -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;
rebuildQSelect();
renderEval();
}
function rebuildQSelect() {
var sel = $("qSelect");
sel.innerHTML = "";
state.evalView.forEach(function (x, i) {
var o = document.createElement("option"); o.value = String(i);
o.textContent = "[" + x.product + (x.kind === "unanswerable" ? " ⊘" : "") + "] " + truncate(x.question, 58);
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 renderGroundTruth(gt) {
if (Array.isArray(gt)) {
return '<ul class="gt-list">' + gt.map(function (v) { return "<li>" + escResolve(v) + "</li>"; }).join("") + "</ul>";
}
return escResolve(gt);
}
function renderEval() {
var card = $("evalCard");
if (!state.evalView.length) {
card.innerHTML = '<div class="empty">No questions match.</div>';
$("evalCounter").textContent = "0 / 0";
return;
}
var x = state.evalView[state.evalIdx];
rememberQid(x.gid);
$("qSelect").value = String(state.evalIdx);
$("evalCounter").textContent = (state.evalIdx + 1) + " / " + state.evalView.length;
var badges =
'<span class="badge id">' + esc(x.gid) + "</span>" +
'<span class="badge product" data-product="' + esc(x.product) + '" title="Browse this product in the Corpus tab">' + esc(x.product) + " ↗</span>" +
'<span class="badge kind-' + esc(x.kind) + '">' + esc(x.kind) + "</span>" +
(x.type && x.type !== "unanswerable" ? '<span class="badge qtype">' + esc(x.type) + "</span>" : "") +
computationBadge(x);
var answerHtml;
if (x.kind === "unanswerable") {
answerHtml = '<div class="answer-banner unans"><div class="lbl">Unanswerable</div>' +
'<div class="val">This question has no answer in the corpus (intentional distractor).</div></div>';
} else {
answerHtml = window.TrajectoryUI.gold(displayGold(x.ground_truth), "Gold");
}
var cites = x.citations || [];
var citesHtml = cites.length
? '<div class="cite-chips">' + cites.map(function (c) {
return '<span class="cite-chip" data-product="' + esc(x.product) + '" data-cid="' + esc(c) +
'"><span class="ic">📎</span>' + esc(c) + "</span>";
}).join("") + "</div>"
: '<span style="color:var(--muted)">none</span>';
card.innerHTML =
'<div class="badges">' + badges + "</div>" +
"<h2>" + esc(x.question) + "</h2>" +
answerHtml +
(x.kind === "unanswerable" ? "" :
field("Evidence citations (" + cites.length + ")", citesHtml));
Array.prototype.forEach.call(card.querySelectorAll(".cite-chip[data-cid]"), function (el) {
el.addEventListener("click", function () {
setMode("corpus");
selectArtifact(this.getAttribute("data-product"), this.getAttribute("data-cid"));
});
});
var pb = card.querySelector(".badge.product[data-product]");
if (pb) pb.addEventListener("click", function () {
setMode("corpus");
$("corpusProduct").value = this.getAttribute("data-product");
state.curType = null; onProductChange();
});
}
/* ---------------- full-run + compare tabs ---------------- */
function buildTabButtons() {
var tabs = [
{ mode: "corpus", label: "📚 Corpus" },
{ mode: "eval", label: "❓ Eval" },
];
(state.manifest.runs || []).forEach(function (run) {
tabs.push({ mode: run.slot, label: run.label });
});
tabs.push({ mode: "compare", label: "⚖ Compare" });
$("viewToggle").innerHTML = tabs.map(function (tab) {
return '<button type="button" data-mode="' + esc(tab.mode) + '">' + esc(tab.label) + "</button>";
}).join("");
}
function metricCard(label, value, detail, cls) {
return '<div class="metric-card"><div class="metric-label">' + esc(label) +
'</div><div class="metric-value ' + esc(cls || "") + '">' + esc(value) +
'</div><div class="metric-detail">' + esc(detail) + "</div></div>";
}
function runStatus(record) {
if (!record.answered) return { key: "missing", label: "missing / unanswered" };
if (record.failure && /judge/.test(record.failure)) {
return { key: "failure", label: "judge failure" };
}
if (record.correct) return { key: "correct", label: "perfect" };
if (record.score > 0) return { key: "partial", label: "partial credit" };
return { key: "incorrect", label: "incorrect" };
}
function citationChips(product, citations) {
if (!citations || !citations.length) return '<span class="muted">none</span>';
return '<div class="cite-chips">' + citations.map(function (cid) {
return '<span class="cite-chip" data-product="' + esc(product) + '" data-cid="' + esc(cid) +
'"><span class="ic">📎</span>' + esc(cid) + "</span>";
}).join("") + "</div>";
}
function wireCitationClicks(container) {
Array.prototype.forEach.call(container.querySelectorAll(".cite-chip[data-cid]"), function (el) {
el.addEventListener("click", function () {
setMode("corpus");
selectArtifact(this.getAttribute("data-product"), this.getAttribute("data-cid"));
});
});
Array.prototype.forEach.call(container.querySelectorAll(".badge.product[data-product]"), function (el) {
el.addEventListener("click", function () {
setMode("corpus");
$("corpusProduct").value = this.getAttribute("data-product");
state.curType = null; onProductChange();
});
});
}
function questionBadges(record, status) {
return '<div class="badges"><span class="badge id">' + esc(record.gid || record.qid) + "</span>" +
'<span class="badge product" data-product="' + esc(record.product) +
'" title="Browse this product in Corpus">' + esc(record.product) + " ↗</span>" +
'<span class="badge qtype">' + esc(record.type) + "</span>" +
computationBadge(record) +
(status ? '<span class="badge status-' + esc(status.key) + '">' + esc(status.label) + "</span>" : "") +
"</div>";
}
function answerPanel(label, value, cls) {
var html = value === null || value === undefined || value === ""
? '<span class="muted">none</span>'
: (Array.isArray(value) ? renderGroundTruth(value) : escResolve(value));
return '<div class="answer-panel ' + esc(cls || "") + '"><div class="lbl">' + esc(label) +
'</div><div class="val">' + html + "</div></div>";
}
function runSummaryHtml(run) {
return '<div class="scope-banner">Scope: 815 answerable HERB questions only. ' +
"Each run uses its canonical evaluator; missing or unanswered questions count as zero.</div>" +
'<div class="metric-grid">' +
metricCard(run.score_label || "Score", run.score_pct.toFixed(2) + "%",
run.score_detail || (run.correct + " / " + run.scope), "status-correct") +
metricCard("Coverage", run.coverage_pct.toFixed(2) + "%", run.answered + " / " + run.scope + " answered", "") +
metricCard("Missing", String(run.scope - run.answered), "explicit unanswered records", run.scope === run.answered ? "" : "status-missing") +
"</div>";
}
function ensureRunIndex(slot) {
if (state.runIndexes[slot]) return Promise.resolve(state.runIndexes[slot]);
if (state.runIndexPromises[slot]) return state.runIndexPromises[slot];
var request = fetch("runs/" + encodeURIComponent(slot) + "/index.json").then(function (r) {
if (!r.ok) throw new Error("run index HTTP " + r.status);
return r.json();
}).then(function (index) {
state.runIndexes[slot] = index;
delete state.runIndexPromises[slot];
return index;
}, function (error) {
delete state.runIndexPromises[slot];
throw error;
});
state.runIndexPromises[slot] = request;
return request;
}
function ensureRunRecord(slot, qid) {
var key = slot + "/" + qid;
var cached = cachedRecord(state.runRecords, state.runRecordOrder, key);
if (cached) return Promise.resolve(cached);
if (state.runRecordPromises[key]) return state.runRecordPromises[key];
var request = fetch("runs/" + encodeURIComponent(slot) + "/records/" + encodeURIComponent(qid) + ".json").then(function (r) {
if (!r.ok) throw new Error("run record HTTP " + r.status);
return r.json();
}).then(function (record) {
delete state.runRecordPromises[key];
storeRecord(state.runRecords, state.runRecordOrder, key, record);
return record;
}, function (error) {
delete state.runRecordPromises[key];
throw error;
});
state.runRecordPromises[key] = request;
return request;
}
function rebuildRunSelect() {
var sel = $("runQSelect");
sel.innerHTML = "";
state.runView.forEach(function (item, i) {
var option = document.createElement("option");
option.value = String(i);
option.textContent = "[" + item.product + "] " + truncate(item.question, 58);
sel.appendChild(option);
});
sel.value = String(state.runIdx);
}
function filterRun() {
var filterSeq = ++state.runFilterSeq;
state.runLoadSeq++;
var slot = state.runSlot;
if (!slot) return;
state.runView = [];
$("runSummary").innerHTML = state.runBySlot[slot]
? runSummaryHtml(state.runBySlot[slot])
: "";
$("runQSelect").innerHTML = "";
$("runCounter").textContent = "0 / 0";
$("runCard").innerHTML = '<div class="empty">Loading run index…</div>';
ensureRunIndex(slot).then(function (index) {
if (filterSeq !== state.runFilterSeq || state.mode !== slot) return;
var q = $("runSearch").value.trim().toLowerCase();
var product = $("runProduct").value;
var typ = $("runType").value;
var status = $("runStatus").value;
var computationFilter = $("runComputationFilter").value;
state.runView = index.items.filter(function (item) {
if (product && item.product !== product) return false;
if (typ && item.type !== typ) return false;
if (status === "correct" && !item.correct) return false;
if (status === "partial" && !(item.answered && item.score > 0 && item.score < 1)) return false;
if (status === "incorrect" && (!item.answered || item.correct || item.score > 0 || (item.failure && /judge/.test(item.failure)))) return false;
if (status === "failure" && !(item.answered && item.failure && /judge/.test(item.failure))) return false;
if (status === "missing" && item.answered) return false;
if (!computationFilterMatches(item.qid, computationFilter)) return false;
if (q) {
var hay = [item.qid, item.gid, item.product, item.question, item.prediction, item.extracted_answer].join(" ").toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
});
var target = sharedIndex(state.runView);
if (target >= 0) state.runIdx = target;
else if (state.runIdx >= state.runView.length) state.runIdx = 0;
rebuildRunSelect();
renderRun();
}).catch(function (err) {
if (filterSeq === state.runFilterSeq && state.mode === slot) {
$("runCard").innerHTML = '<div class="empty">Failed to load run: ' + esc(err.message) + "</div>";
}
});
}
function renderRun() {
var seq = ++state.runLoadSeq;
var run = state.runBySlot[state.runSlot];
$("runSummary").innerHTML = run ? runSummaryHtml(run) : "";
if (!state.runView.length) {
$("runCard").innerHTML = '<div class="empty">No run questions match.</div>';
$("runCounter").textContent = "0 / 0";
return;
}
var item = state.runView[state.runIdx];
$("runQSelect").value = String(state.runIdx);
$("runCounter").textContent = (state.runIdx + 1) + " / " + state.runView.length;
$("runCard").innerHTML = '<div class="empty">Loading ' + esc(item.qid) + "…</div>";
var slot = state.runSlot;
ensureRunRecord(slot, item.qid).then(function (record) {
if (seq !== state.runLoadSeq || slot !== state.runSlot || state.mode !== slot) return;
var status = runStatus(record);
rememberQid(record.qid);
var stop = record.stop_reason || record.finish_reason || (record.finish_reasons || []).slice(-1)[0] || "—";
var events = record.events || [];
var eventHtml = window.TrajectoryUI.render(events, "Agent trajectory");
var citations = citationChips(record.product, record.citations);
$("runCard").innerHTML =
questionBadges(record, status) +
"<h2>" + esc(record.question) + "</h2>" +
window.TrajectoryUI.gold(displayGold(record.gold), "Gold") +
(/^e2e_/.test(slot)
? window.TrajectoryUI.supporting(
"Evidence citations",
citations || '<span class="muted">none</span>',
(record.citations || []).length
)
: field("Evidence citations (" + (record.citations || []).length + ")", citations)) +
answerPanel("Prediction", record.prediction, "prediction") +
answerPanel("Extracted judge answer", record.extracted_answer, "") +
'<div class="run-meta">' +
'<span class="meta-pill">stop: <b>' + esc(stop) + "</b></span>" +
'<span class="meta-pill">failure: <b>' + esc(record.failure || "none") + "</b></span>" +
'<span class="meta-pill">confidence: <b>' + esc(record.confidence == null ? "—" : record.confidence) + "</b></span>" +
(typeof record.score === "number"
? '<span class="meta-pill">canonical score: <b>' + (record.score * 100).toFixed(2) + "%</b></span>"
: "") +
'<span class="meta-pill">turns: <b>' + esc(record.turns == null ? "—" : record.turns) + "</b></span>" +
'<span class="meta-pill">tokens: <b>' + esc((record.tokens.total_tokens || 0).toLocaleString()) + "</b></span>" +
'<span class="meta-pill">tools: <b>' + esc(JSON.stringify(record.tool_counts || {})) + "</b></span>" +
"</div>" +
answerPanel("Judge text", record.judge_text, "") +
eventHtml;
wireCitationClicks($("runCard"));
window.TrajectoryUI.bind($("runCard"));
}).catch(function (err) {
if (seq === state.runLoadSeq && slot === state.runSlot && state.mode === slot) {
$("runCard").innerHTML = '<div class="empty">Failed to load record: ' + esc(err.message) + "</div>";
}
});
}
function ensureCompareIndex() {
if (state.compareIndex) return Promise.resolve(state.compareIndex);
if (state.compareIndexPromise) return state.compareIndexPromise;
var request = fetch("compare/index.json").then(function (r) {
if (!r.ok) throw new Error("compare index HTTP " + r.status);
return r.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 ensureCompareRecord(qid) {
var cached = cachedRecord(state.compareRecords, state.compareRecordOrder, qid);
if (cached) return Promise.resolve(cached);
if (state.compareRecordPromises[qid]) return state.compareRecordPromises[qid];
var request = fetch("compare/records/" + encodeURIComponent(qid) + ".json").then(function (r) {
if (!r.ok) throw new Error("compare record HTTP " + r.status);
return r.json();
}).then(function (record) {
delete state.compareRecordPromises[qid];
storeRecord(state.compareRecords, state.compareRecordOrder, qid, record);
return record;
}, function (error) {
delete state.compareRecordPromises[qid];
throw error;
});
state.compareRecordPromises[qid] = request;
return request;
}
function rebuildCompareSelect() {
var sel = $("compareQSelect");
sel.innerHTML = "";
state.compareView.forEach(function (item, i) {
var option = document.createElement("option");
option.value = String(i);
option.textContent = "[" + item.product + "] " + truncate(item.question, 58);
sel.appendChild(option);
});
sel.value = String(state.compareIdx);
}
function compareSummaryHtml(index) {
return '<div class="scope-banner">Compare scope: all runs joined to the same 815 answerable HERB questions. ' +
"Missing or unanswered predictions receive zero.</div><div class=\"metric-grid\">" +
index.runs.map(function (run) {
return metricCard(run.label, run.score_pct.toFixed(2) + "%", (run.score_detail || run.correct + "/815") + " · " +
run.coverage_pct.toFixed(2) + "% coverage (" + run.answered + " answered)",
run.slot === "e2e_combined" ? "status-correct" : "");
}).join("") + "</div>";
}
function filterCompare() {
var filterSeq = ++state.compareFilterSeq;
state.compareLoadSeq++;
state.compareView = [];
$("compareQSelect").innerHTML = "";
$("compareCounter").textContent = "0 / 0";
$("compareQuestion").innerHTML = '<div class="empty">Loading comparison index…</div>';
$("compareGrid").innerHTML = "";
ensureCompareIndex().then(function (index) {
if (filterSeq !== state.compareFilterSeq || state.mode !== "compare") return;
var q = $("compareSearch").value.trim().toLowerCase();
var product = $("compareProduct").value;
var typ = $("compareType").value;
var filter = $("compareFilter").value;
var computationFilter = $("compareComputationFilter").value;
state.compareView = index.items.filter(function (item) {
if (product && item.product !== product) return false;
if (typ && item.type !== typ) return false;
if (filter === "disagreement" && !item.disagreement) return false;
if (filter === "missing" && !item.any_missing) return false;
if (filter === "combined-correct" && !item.only_e2e_combined_correct) return false;
if (!computationFilterMatches(item.qid, computationFilter)) return false;
if (q && [item.qid, item.gid, item.product, item.question].join(" ").toLowerCase().indexOf(q) === -1) return false;
return true;
});
var target = sharedIndex(state.compareView);
if (target >= 0) state.compareIdx = target;
else if (state.compareIdx >= state.compareView.length) state.compareIdx = 0;
rebuildCompareSelect();
renderCompare();
}).catch(function (err) {
if (filterSeq === state.compareFilterSeq && state.mode === "compare") {
$("compareQuestion").innerHTML = '<div class="empty">Failed to load compare index: ' + esc(err.message) + "</div>";
}
});
}
function renderCompare() {
var seq = ++state.compareLoadSeq;
if (state.compareIndex) $("compareSummary").innerHTML = compareSummaryHtml(state.compareIndex);
if (!state.compareView.length) {
$("compareQuestion").innerHTML = '<div class="empty">No comparisons match.</div>';
$("compareGrid").innerHTML = "";
$("compareCounter").textContent = "0 / 0";
return;
}
var item = state.compareView[state.compareIdx];
$("compareQSelect").value = String(state.compareIdx);
$("compareCounter").textContent = (state.compareIdx + 1) + " / " + state.compareView.length;
$("compareQuestion").innerHTML = '<div class="empty">Loading ' + esc(item.qid) + "…</div>";
$("compareGrid").innerHTML = "";
ensureCompareRecord(item.qid).then(function (record) {
if (seq !== state.compareLoadSeq || state.mode !== "compare") return;
rememberQid(record.qid);
$("compareQuestion").innerHTML =
questionBadges(record, null) + "<h2>" + esc(record.question) + "</h2>" +
window.TrajectoryUI.gold(displayGold(record.gold), "Gold") +
field("Evidence citations (" + (record.citations || []).length + ")", citationChips(record.product, record.citations));
wireCitationClicks($("compareQuestion"));
$("compareGrid").innerHTML = state.compareIndex.runs.map(function (run) {
var result = record.runs[run.slot];
var status = runStatus(result);
return '<article class="compare-card ' + esc(status.key) + '">' +
'<div class="compare-head"><h3>' + esc(run.label) + '</h3><span class="badge status-' +
esc(status.key) + '">' + esc(status.label) + "</span></div>" +
'<div class="prediction-text">' + (result.prediction ? escResolve(result.prediction) : '<span class="muted">No prediction</span>') + "</div>" +
'<details class="event-details"><summary>Judge + run metadata</summary><pre>' +
esc(JSON.stringify({
extracted_answer: result.extracted_answer,
canonical_score: result.score,
score_kind: result.score_kind,
judge_text: result.judge_text,
confidence: result.confidence,
failure: result.failure,
stop_reason: result.stop_reason || result.finish_reason,
turns: result.turns,
tokens: result.tokens,
tool_counts: result.tool_counts,
}, null, 2)) + "</pre></details></article>";
}).join("");
}).catch(function (err) {
if (seq === state.compareLoadSeq && state.mode === "compare") {
$("compareQuestion").innerHTML = '<div class="empty">Failed to load comparison: ' + esc(err.message) + "</div>";
}
});
}
/* ---------------- mode switching ---------------- */
function setMode(mode) {
if (mode !== "corpus" && mode !== "eval" && mode !== "compare" && !state.runBySlot[mode]) return;
var previousMode = state.mode;
var leftStructures = Boolean(window.__e2eStructuresJustClosed);
window.__e2eStructuresJustClosed = false;
state.mode = mode;
Array.prototype.forEach.call($("viewToggle").querySelectorAll("button[data-mode]"), function (button) {
button.classList.toggle("active", button.getAttribute("data-mode") === mode);
});
$("corpusControls").style.display = mode === "corpus" ? "" : "none";
$("evalControls").style.display = mode === "eval" ? "" : "none";
$("runControls").style.display = state.runBySlot[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 = state.runBySlot[mode] ? "block" : "none";
$("compareView").style.display = mode === "compare" ? "block" : "none";
if (state.runBySlot[mode]) {
if (previousMode !== mode || leftStructures) {
$("runSearch").value = "";
$("runProduct").value = "";
$("runType").value = "";
$("runStatus").value = "";
$("runComputationFilter").value = "";
}
state.runSlot = mode;
filterRun();
} else if (mode === "compare") {
if (previousMode !== "compare" || leftStructures) {
$("compareSearch").value = "";
$("compareProduct").value = "";
$("compareType").value = "";
$("compareFilter").value = "";
$("compareComputationFilter").value = "";
}
filterCompare();
} else if (mode === "eval") {
if (previousMode !== "eval" || leftStructures) {
$("evalSearch").value = "";
$("evalProduct").value = "";
$("kindFilter").value = "";
$("qTypeFilter").value = "";
$("evalComputationFilter").value = "";
}
filterEval();
}
}
/* ---------------- wiring ---------------- */
function wire() {
$("viewToggle").addEventListener("click", function (event) {
var button = event.target.closest("button[data-mode]");
if (button) setMode(button.getAttribute("data-mode"));
});
$("corpusProduct").addEventListener("change", function () { state.curType = null; onProductChange(); });
$("artifactType").addEventListener("change", function () {
state.curType = this.value; state.artifactIdx = 0; $("corpusFilter").value = ""; filterArtifacts();
});
$("corpusFilter").addEventListener("input", function () { state.artifactIdx = 0; filterArtifacts(); });
$("artifactSelect").addEventListener("change", function () { state.artifactIdx = parseInt(this.value, 10) || 0; renderArtifact(); });
$("corpusPrevBtn").addEventListener("click", function () { if (state.artifactIdx > 0) { state.artifactIdx--; renderArtifact(); } });
$("corpusNextBtn").addEventListener("click", function () { if (state.artifactIdx < state.artifactsView.length - 1) { state.artifactIdx++; renderArtifact(); } });
$("evalSearchBtn").addEventListener("click", filterEval);
$("evalSearch").addEventListener("keydown", function (e) { if (e.key === "Enter") filterEval(); });
$("evalClearBtn").addEventListener("click", function () {
$("evalSearch").value = ""; $("evalProduct").value = ""; $("kindFilter").value = ""; $("qTypeFilter").value = ""; $("evalComputationFilter").value = ""; filterEval();
});
$("evalProduct").addEventListener("change", filterEval);
$("kindFilter").addEventListener("change", filterEval);
$("qTypeFilter").addEventListener("change", filterEval);
$("evalComputationFilter").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(); } });
["runSearch", "runProduct", "runType", "runStatus", "runComputationFilter"].forEach(function (id) {
$(id).addEventListener(id === "runSearch" ? "input" : "change", function () { state.runIdx = 0; filterRun(); });
});
$("runQSelect").addEventListener("change", function () { state.runIdx = parseInt(this.value, 10) || 0; renderRun(); });
$("runPrevBtn").addEventListener("click", function () { if (state.runIdx > 0) { state.runIdx--; renderRun(); } });
$("runNextBtn").addEventListener("click", function () { if (state.runIdx < state.runView.length - 1) { state.runIdx++; renderRun(); } });
["compareSearch", "compareProduct", "compareType", "compareFilter", "compareComputationFilter"].forEach(function (id) {
$(id).addEventListener(id === "compareSearch" ? "input" : "change", function () { state.compareIdx = 0; filterCompare(); });
});
$("compareQSelect").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 (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 if (state.mode === "eval") $("evalPrevBtn").click();
else if (state.mode === "compare") $("comparePrevBtn").click();
else $("runPrevBtn").click();
} else if (e.key === "ArrowRight") {
if (state.mode === "corpus") $("corpusNextBtn").click();
else if (state.mode === "eval") $("evalNextBtn").click();
else if (state.mode === "compare") $("compareNextBtn").click();
else $("runNextBtn").click();
}
});
}
/* ---------------- boot ---------------- */
loadAll().then(function () {
$("loading").style.display = "none";
var tot = state.products.reduce(function (s, p) { return s + p.n_artifacts; }, 0);
$("sidebarFooter").innerHTML =
state.products.length + " products · " + tot.toLocaleString() + " artifacts · " + state.questions.length + " questions<br>" +
'<a href="https://github.com/SalesforceAIResearch/HERB" target="_blank" rel="noopener">GitHub</a> · ' +
'<a href="https://huggingface.co/datasets/Salesforce/HERB" target="_blank" rel="noopener">HF dataset</a> · ' +
'<a href="https://arxiv.org/abs/2506.23139" target="_blank" rel="noopener">arXiv</a>';
populateProductSelects();
buildTabButtons();
wire();
setMode("corpus");
if (state.products.length) { $("corpusProduct").value = state.products[0].name; onProductChange(); }
filterEval();
}).catch(function (err) {
$("loading").textContent = "Failed to load data: " + err;
});
})();