Spaces:
Running
Running
| (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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | |
| } | |
| 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(); | |
| })(); | |