wildchat-aqa-viewer / viewer.js
timchen0618's picture
Pack corpus into 2048 buckets; fix static root routing
7a2cc9a verified
Raw
History Blame Contribute Delete
13.4 kB
(function () {
"use strict";
var TARGET_LABEL = {
label_level_1: "Topic (L1)", label_level_2: "Subtopic (L2)", user_name: "User",
country: "Country", language: "Language", time_week: "Time (week)",
keywords_aggregated: "Keywords / entities",
};
var index = [], filtered = [], pos = 0;
var recordCache = {}, corpusCache = {}, bucketCache = {};
var NBUCKETS = 2048;
var $ = function (id) { return document.getElementById(id); };
function esc(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function num(n) { return (n == null ? 0 : n).toLocaleString(); }
function loadIndex() { return fetch("eval/index.json").then(function (r) { return r.json(); }); }
function loadRecord(h) {
if (recordCache[h]) return Promise.resolve(recordCache[h]);
return fetch("eval/records/" + encodeURIComponent(h) + ".json")
.then(function (r) { return r.json(); }).then(function (rec) { recordCache[h] = rec; return rec; });
}
// Conversations are packed into corpus/b<N>.json buckets (see build script);
// fetch the whole bucket once, then index into it by hash.
function loadCorpus(h) {
if (corpusCache[h]) return Promise.resolve(corpusCache[h]);
var b = parseInt(h.slice(0, 8), 16) % NBUCKETS;
if (!bucketCache[b]) bucketCache[b] = fetch("corpus/b" + b + ".json").then(function (r) { return r.json(); });
return bucketCache[b].then(function (docs) { var c = docs[h]; corpusCache[h] = c; return c; });
}
// ── filtering ──
function applyFilters() {
var q = $("searchInput").value.trim().toLowerCase();
var tgt = $("targetFilter").value, kw = $("keywordsFilter").value, cond = $("condFilter").value;
filtered = index.filter(function (e) {
if (tgt !== "all" && e.target_type !== tgt) return false;
if (kw !== "all" && (e.keywords_type || "") !== kw) return false;
if (cond === "across") { if (e.n_conditions !== 0) return false; }
else if (cond !== "all") { if ((e.cond_types || []).indexOf(cond) === -1) return false; }
if (q && (e.question + " " + e.hash).toLowerCase().indexOf(q) === -1) return false;
return true;
});
pos = 0;
render();
}
function updateCounter() {
$("counter").textContent = filtered.length ? (pos + 1) + " / " + filtered.length : "0 / 0";
$("prevBtn").disabled = pos <= 0;
$("nextBtn").disabled = pos >= filtered.length - 1;
}
function render() {
updateCounter();
if (!filtered.length) {
$("card").style.display = "none";
$("loading").style.display = "block";
$("loading").textContent = "No questions match the current filters.";
return;
}
$("loading").style.display = "none";
loadRecord(filtered[pos].hash).then(renderRecord).catch(function (e) {
$("loading").style.display = "block";
$("loading").textContent = "Failed to load record: " + e;
});
}
function renderRecord(rec) {
$("card").style.display = "block";
var b = [];
b.push('<span class="badge target">🎯 ' + esc(TARGET_LABEL[rec.target_type] || rec.target_type) + "</span>");
if (rec.keywords_type) b.push('<span class="badge kw">🏷 ' + esc(rec.keywords_type) + "</span>");
b.push('<span class="badge cond">' + (rec.condition.length ? rec.condition.length + " condition" + (rec.condition.length > 1 ? "s" : "") : "no condition") + "</span>");
b.push('<span class="badge support">💬 ' + num(rec.n_support_total) + " matching convs</span>");
if (rec.across_all) b.push('<span class="badge across">🌐 across all</span>');
$("badgeRow").innerHTML = b.join("");
$("questionText").textContent = rec.question;
// condition bar
if (rec.condition.length) {
var chips = rec.condition.map(function (c) {
var v = c.value_name ? (esc(c.value_name) + ' <span style="color:#71717a">(' + esc(c.value) + ")</span>") : esc(c.value);
return '<span class="cond-chip"><span class="ct">' + esc(c.type) + '</span> = <span class="cv">' + v + "</span></span>";
}).join("");
$("conditionBar").innerHTML = '<span class="cond-label">Aggregating over conversations where</span><br>' + chips;
} else {
$("conditionBar").innerHTML = '<span class="cond-label">Aggregating over</span> the entire corpus (no condition filter).';
}
renderAnswer(rec);
renderEvidence(rec);
renderSupport(rec);
renderMeta(rec);
}
function renderAnswer(rec) {
if (rec.answer) {
$("answerBanner").innerHTML =
'<div class="ab-label">Correct answer (highest weight)</div>' +
'<div class="ab-text">' + esc(rec.answer.text) + "</div>" +
'<div class="ab-weight">weight ' + rec.answer.weight.toFixed(3) + "</div>";
} else $("answerBanner").innerHTML = "";
$("answerHint").textContent = rec.options.length + " options";
var rows = rec.options.map(function (o, i) { return { i: i, text: o.text, weight: o.weight }; });
var maxw = Math.max.apply(null, rows.map(function (r) { return r.weight; }).concat([0]));
rows.sort(function (a, b) { return b.weight - a.weight; });
$("optionsList").innerHTML = rows.map(function (r) {
var pct = maxw > 0 ? (r.weight / maxw * 100) : 0;
var top = r.weight === maxw && maxw > 0 ? " top" : "";
return '<div class="bar-row' + top + '">' +
'<div class="bar-label">' + esc(r.text) + "</div>" +
'<div class="bar-track"><div class="bar-fill" style="width:' + pct.toFixed(1) + '%"></div></div>' +
'<div class="bar-val">' + r.weight.toFixed(3) + "</div></div>";
}).join("");
}
function renderEvidence(rec) {
var tc = rec.target_candidates || [];
$("evidenceHint").textContent = "top " + tc.length + " of " + rec.target_candidates_total + " values (by count)";
var maxc = Math.max.apply(null, tc.map(function (c) { return c.count; }).concat([0]));
$("evidenceList").innerHTML = tc.map(function (c) {
var pct = maxc > 0 ? (c.count / maxc * 100) : 0;
var label = c.value_name ? esc(c.value_name) + ' <span style="color:#71717a">(' + esc(c.value) + ")</span>" : esc(c.value);
return '<div class="bar-row">' +
'<div class="bar-label">' + label + "</div>" +
'<div class="bar-track"><div class="bar-fill evidence-fill" style="width:' + pct.toFixed(1) + '%"></div></div>' +
'<div class="bar-val">' + num(c.count) + "</div></div>";
}).join("");
}
function renderSupport(rec) {
$("supportHint").textContent = "showing " + rec.n_support_shown + " of " + num(rec.n_support_total);
var note = "";
if (rec.across_all) note = '<span class="warn">⚠ No condition — these are a sample from the entire ' + num(rec.n_support_total) + '-conversation corpus (by size).</span>';
else if (rec.capped) note = '<span class="warn">⚠ ' + num(rec.n_support_total) + " conversations match; showing the " + rec.n_support_shown + " largest.</span>";
else note = "All " + rec.n_support_shown + " matching conversations shown.";
$("supportNote").innerHTML = note;
var list = $("supportList");
list.innerHTML = "";
rec.support.forEach(function (s, i) {
var card = document.createElement("div");
card.className = "conv-card";
var sub = [];
if (s.user_name) sub.push('<span class="chip">👤 ' + esc(s.user_name) + "</span>");
if (s.country) sub.push('<span class="chip">📍 ' + esc(s.country) + "</span>");
if (s.language) sub.push('<span class="chip">🗣 ' + esc(s.language) + "</span>");
if (s.week) sub.push('<span class="chip">📅 ' + esc(s.week) + "</span>");
if (s.token_count != null) sub.push('<span class="chip">' + num(s.token_count) + " tok</span>");
card.innerHTML =
'<div class="conv-head">' +
'<span class="conv-ord">#' + (i + 1) + "</span>" +
'<div class="conv-head-main">' +
'<div class="conv-summary">' + esc(s.summary || "(no summary)") + "</div>" +
'<div class="conv-sub">' + sub.join(" · ") + "</div>" +
"</div>" +
'<span class="conv-toggle">▶ open</span>' +
"</div>" +
'<div class="conv-body"></div>';
var head = card.querySelector(".conv-head");
var body = card.querySelector(".conv-body");
var toggle = card.querySelector(".conv-toggle");
var loaded = false;
head.onclick = function () {
var open = body.classList.toggle("open");
toggle.textContent = open ? "▼ close" : "▶ open";
if (open && !loaded) {
loaded = true;
body.innerHTML = '<div class="conv-loading">Loading conversation…</div>';
loadCorpus(s.hash).then(function (c) { renderConv(body, c); })
.catch(function (e) { body.innerHTML = '<div class="conv-loading">Failed to load: ' + esc(e) + "</div>"; });
}
};
list.appendChild(card);
});
}
function renderConv(body, c) {
var meta = [];
if (c.classes_level_1 && c.classes_level_1.length) meta.push("Topics: " + c.classes_level_1.map(esc).join(", "));
if (c.classes_level_2 && c.classes_level_2.length) meta.push("Subtopics: " + c.classes_level_2.slice(0, 8).map(esc).join(", "));
if (c.keywords_aggregated && c.keywords_aggregated.length)
meta.push("Keywords: " + c.keywords_aggregated.map(function (k) { return esc(k.value); }).join(", "));
var head = '<div class="conv-meta-line">hash ' + esc(c.hash) + " · " + esc(c.timestamp || "") +
(meta.length ? "<br>" + meta.join(" · ") : "") + "</div>";
var turns = (c.conversation || []).map(function (t) {
var role = (t.role || "").toLowerCase();
var cls = role === "assistant" ? "assistant" : "user";
return '<div class="turn ' + cls + '"><div class="turn-role">' + esc(t.role || "?") + "</div>" +
'<div class="turn-content">' + esc(t.content || "") + "</div></div>";
}).join("");
body.innerHTML = head + turns;
}
function renderMeta(rec) {
var html = '<div class="meta-grid">' +
'<div class="meta-k">target_type</div><div class="meta-v">' + esc(rec.target_type) + "</div>" +
'<div class="meta-k">keywords_type</div><div class="meta-v">' + esc(rec.keywords_type || "(none)") + "</div>" +
'<div class="meta-k">conditions</div><div class="meta-v">' + esc(rec.condition.map(function (c) { return c.type + "=" + c.value; }).join(" AND ") || "(across all)") + "</div>" +
'<div class="meta-k">matching convs</div><div class="meta-v">' + num(rec.n_support_total) + "</div>" +
'<div class="meta-k">hash</div><div class="meta-v">' + esc(rec.hash) + "</div></div>";
var gq = rec.generated_query;
if (gq) {
html += '<div class="probe-box"><div class="pb-label">PROBE — GPT-generated retrieval query</div>';
if (gq.explanation) html += '<div class="pb-explain">' + esc(gq.explanation) + "</div>";
var qs = gq.query;
if (Array.isArray(qs)) html += "<ul>" + qs.map(function (q) { return "<li>" + esc(q) + "</li>"; }).join("") + "</ul>";
else if (qs) html += '<div class="pb-explain">' + esc(qs) + "</div>";
html += "</div>";
}
$("metaBody").innerHTML = html;
}
function wireSectionToggles() {
document.querySelectorAll(".section-title[data-toggle]").forEach(function (h) {
h.addEventListener("click", function () {
var content = $(h.getAttribute("data-toggle") + "Content");
if (!content) return;
var collapsed = h.classList.toggle("collapsed");
content.style.display = collapsed ? "none" : "block";
var icon = h.querySelector(".toggle-icon");
if (icon) icon.textContent = collapsed ? "▶" : "▼";
});
});
}
function go(delta) {
var n = pos + delta;
if (n < 0 || n >= filtered.length) return;
pos = n; render(); $("mainBody").scrollTop = 0;
}
function populateKeywordFilter() {
var kws = {};
index.forEach(function (e) { if (e.keywords_type) kws[e.keywords_type] = 1; });
var sel = $("keywordsFilter");
Object.keys(kws).sort().forEach(function (k) {
var o = document.createElement("option"); o.value = k; o.textContent = k; sel.appendChild(o);
});
}
function init() {
$("searchBtn").onclick = applyFilters;
$("clearBtn").onclick = function () { $("searchInput").value = ""; applyFilters(); };
$("searchInput").addEventListener("keydown", function (e) { if (e.key === "Enter") applyFilters(); });
["targetFilter", "keywordsFilter", "condFilter"].forEach(function (id) {
$(id).addEventListener("change", applyFilters);
});
$("prevBtn").onclick = function () { go(-1); };
$("nextBtn").onclick = function () { go(1); };
document.addEventListener("keydown", function (e) {
if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT") return;
if (e.key === "ArrowLeft") go(-1); else if (e.key === "ArrowRight") go(1);
});
wireSectionToggles();
loadIndex().then(function (data) {
index = data;
$("totalCount").textContent = index.length.toLocaleString();
populateKeywordFilter();
applyFilters();
}).catch(function (e) { $("loading").textContent = "Failed to load index.json: " + e; });
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
else init();
})();