HerrHruby's picture
Upload viewer
837cc70 verified
Raw
History Blame Contribute Delete
23.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MR V3 SFT Results — Viewer</title>
<!-- KaTeX + marked are vendored locally (vendor/) so rendering works with no
network access and no CDN/CSP surprises. Run build_index.py once to create
data/, then serve this folder; vendor/ ships with the repo.
If vendor/ is missing the page falls back to raw LaTeX/Markdown. -->
<link rel="stylesheet" href="vendor/katex.min.css" />
<script src="vendor/katex.min.js"></script>
<script src="vendor/marked.min.js"></script>
<style>
:root {
--bg: #0f1115; --panel: #171a21; --panel2: #1e222b; --line: #2a2f3a;
--fg: #e6e9ef; --muted: #9aa3b2; --accent: #6ea8fe; --accent2: #7ee0c0;
--warn: #ffb454; --bad: #ff6b6b; --good: #5ad18b;
--chip: #232838; --dir: #2a3550; --exec: #2c3d33; --final: #3a3146;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
background: var(--bg); color: var(--fg); font: 14px/1.55 -apple-system,
BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display: grid; grid-template-columns: 340px 1fr; height: 100vh; overflow: hidden;
}
/* ---- Sidebar: problem switcher ---- */
#sidebar { background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
#sidebar h1 { font-size: 15px; margin: 0; padding: 14px 16px 10px;
border-bottom: 1px solid var(--line); letter-spacing: .3px; }
#sidebar h1 small { color: var(--muted); font-weight: 400; display: block;
font-size: 11px; margin-top: 2px; }
#search { margin: 10px 12px 4px; padding: 8px 10px; background: var(--panel2);
border: 1px solid var(--line); border-radius: 8px; color: var(--fg); width: calc(100% - 24px); }
#search::placeholder { color: var(--muted); }
#filters { display: flex; gap: 6px; flex-wrap: wrap; padding: 4px 12px 8px;
border-bottom: 1px solid var(--line); }
#filters select { background: var(--panel2); border: 1px solid var(--line);
color: var(--fg); border-radius: 7px; padding: 4px 6px; font-size: 11.5px; flex: 1; min-width: 0; }
#trajList { overflow-y: auto; flex: 1; padding: 4px 8px 16px; }
.trajItem { padding: 9px 10px; border-radius: 8px; cursor: pointer; margin-bottom: 4px;
border: 1px solid transparent; }
.trajItem:hover { background: var(--panel2); }
.trajItem.active { background: var(--panel2); border-color: var(--accent); }
.trajItem .tt { font-size: 12.5px; color: var(--fg); display: -webkit-box;
-webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.trajItem .meta { font-size: 11px; color: var(--muted); margin-top: 4px;
display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.badge { font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--chip);
color: var(--muted); border: 1px solid var(--line); }
.badge.score { color: #0c1117; background: var(--good); border: none; font-weight: 600; }
.badge.score.mid { background: var(--warn); }
.badge.score.low { background: var(--bad); }
.badge.pt { border-color: #55618a; color: #aab9ef; }
.badge.diff-hard { border-color: var(--bad); color: #ff9d9d; }
.badge.diff-medium { border-color: var(--warn); color: var(--warn); }
.badge.diff-easy { border-color: var(--good); color: var(--good); }
/* ---- Main ---- */
#main { overflow-y: auto; height: 100vh; padding: 0 0 80px; }
#topbar { position: sticky; top: 0; z-index: 5; background: rgba(15,17,21,.92);
backdrop-filter: blur(6px); border-bottom: 1px solid var(--line);
padding: 12px 24px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
#topbar .traj-id { font-weight: 600; }
#topbar .stat { color: var(--muted); font-size: 12.5px; }
.toggles { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.toggle { font-size: 12px; padding: 5px 11px; border-radius: 999px; cursor: pointer;
background: var(--panel2); border: 1px solid var(--line); color: var(--muted); user-select: none; }
.toggle.on { color: var(--fg); border-color: var(--accent); background: #1d2740; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
/* problem */
#problem { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
padding: 18px 22px; margin: 22px 0; }
#problem .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
color: var(--accent); margin-bottom: 8px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
#problem .title { font-size: 16px; font-weight: 600; margin-bottom: 10px; }
#problem .body { font-size: 14.5px; }
.tagrow { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }
/* layers */
.layer { margin: 26px 0; }
.layer-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 10px;
border-bottom: 1px solid var(--line); padding-bottom: 6px; }
.layer-head .lnum { font-size: 18px; font-weight: 700; }
.layer-head .lmeta { color: var(--muted); font-size: 12px; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
margin: 10px 0; overflow: hidden; }
.card.exp { border-left: 3px solid var(--accent2); }
.card.final { border-left: 3px solid #b694e8; }
.card.rubric { border-left: 3px solid var(--warn); }
.card.reference { border-left: 3px solid #6f86c9; }
.card-head { padding: 11px 16px; cursor: pointer; display: flex; align-items: center;
gap: 10px; user-select: none; }
.card-head:hover { background: var(--panel2); }
.card-head .ttl { font-weight: 600; font-size: 13.5px; }
.card-head .tag { font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
background: var(--chip); color: var(--muted); border: 1px solid var(--line); }
.card-head .chev { margin-left: auto; color: var(--muted); transition: transform .15s; }
.card.open .card-head .chev { transform: rotate(90deg); }
.card-body { padding: 0 16px; max-height: 0; overflow: hidden; }
.card.open .card-body { padding: 4px 16px 16px; max-height: none; }
.dir-text { background: var(--dir); border-radius: 8px; padding: 10px 13px; margin: 8px 0;
font-size: 13px; }
.subsec { margin: 12px 0 4px; font-size: 11px; text-transform: uppercase;
letter-spacing: .8px; color: var(--muted); display: flex; align-items: center; gap: 8px; }
.subsec::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.prose { font-size: 13.5px; }
.prose p { margin: 8px 0; }
.prose.scroll { max-height: 520px; overflow-y: auto; background: #12151c;
border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; }
.pill { font-size: 11px; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--line);
color: var(--muted); }
.pill.idx { background: var(--exec); color: var(--accent2); border: none; }
.empty { color: var(--muted); font-style: italic; padding: 8px 0; }
.hidden { display: none !important; }
code { background: #12151c; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
pre { white-space: pre-wrap; word-break: break-word; }
.katex { font-size: 1.02em; }
/* display math: let long equations scroll instead of overflowing the card */
.katex-display { overflow-x: auto; overflow-y: hidden; padding: 2px 0; margin: 8px 0; }
.prose .katex-error { color: var(--warn); }
code.rawtex { color: var(--accent2); background: #12151c; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: #2b313d; border-radius: 6px; }
::-webkit-scrollbar-track { background: transparent; }
</style>
</head>
<body>
<aside id="sidebar">
<h1>MR V3 SFT Results<small id="trajCount">loading…</small></h1>
<input id="search" placeholder="filter by title / problem id / paper…" />
<div id="filters">
<select id="fPrompt"><option value="">all prompt types</option></select>
<select id="fDiff"><option value="">all difficulties</option></select>
<select id="fSort">
<option value="score">sort: score ↓</option>
<option value="score_asc">sort: score ↑</option>
<option value="title">sort: title</option>
<option value="layers">sort: layers ↓</option>
</select>
</div>
<div id="trajList"></div>
</aside>
<main id="main">
<div id="topbar">
<span class="traj-id" id="trajId"></span>
<span class="stat" id="trajStats"></span>
<div class="toggles">
<span class="toggle on" data-view="explore">Exploration</span>
<span class="toggle" data-view="dirs">Directions</span>
<span class="toggle on" data-view="final">Final answer</span>
<span class="toggle" data-view="rubric">Rubric</span>
<span class="toggle" data-view="reference">Reference</span>
</div>
</div>
<div class="wrap" id="content">
<div class="empty" style="margin-top:40px">Select a problem from the left.</div>
</div>
</main>
<script>
const DATA_DIR = "data/";
const state = {
manifest: [],
traj: null,
filters: { q: "", prompt: "", diff: "", sort: "score" },
views: { explore: true, dirs: false, final: true, rubric: false, reference: false },
};
// ---- math + markdown rendering ----
// The trace text mixes LaTeX ( \(...\), \[...\], $...$, $$...$$ ) with light
// markdown (**bold**, lists). Running markdown first corrupts the math: `_` and
// `{` become emphasis, backslash-escapes get eaten. So we (1) extract every math
// span into an opaque placeholder, (2) run markdown on the math-free remainder,
// (3) KaTeX-render each span and splice the HTML back in. KaTeX never sees
// markdown and markdown never sees LaTeX.
function escapeHtml(s) {
return (s || "").replace(/[&<>]/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]));
}
function renderMath(el) { /* no-op: math is rendered at parse time in mdToHtml */ }
// Ordered so longer/greedier delimiters match before their single-char prefixes.
const MATH_PATTERNS = [
{ re: /\$\$([\s\S]+?)\$\$/g, display: true },
{ re: /\\\[([\s\S]+?)\\\]/g, display: true },
{ re: /\\\(([\s\S]+?)\\\)/g, display: false },
// single-$ inline: avoid \$ (escaped) and $$ (already consumed above).
{ re: /(?<![\\$])\$(?!\$)([^\n$]+?)\$(?!\$)/g, display: false },
];
function renderKatexSpan(tex, display) {
if (window.katex) {
try {
return katex.renderToString(tex, {
displayMode: display, throwOnError: false, output: "html",
});
} catch (e) { /* fall through to raw */ }
}
// KaTeX not loaded (offline) — show the raw LaTeX, lightly styled.
return `<code class="rawtex">${escapeHtml(tex)}</code>`;
}
function mdToHtml(s) {
if (!s) return "";
// 1. extract math -> placeholders
const spans = [];
let text = s;
for (const { re, display } of MATH_PATTERNS) {
text = text.replace(re, (_m, tex) => {
const token = `@@MATH${spans.length}@@`;
spans.push(renderKatexSpan(tex.trim(), display));
return token;
});
}
// 2. markdown on the math-free remainder
let html;
if (window.marked) {
try { html = marked.parse(text, { breaks: true }); }
catch (e) { html = "<pre>" + escapeHtml(text) + "</pre>"; }
} else {
html = "<pre>" + escapeHtml(text) + "</pre>";
}
// 3. restore rendered math (marked may wrap the token in <p>, leave it as-is)
html = html.replace(/@@MATH(\d+)@@/g, (_m, i) => spans[+i] ?? "");
return html;
}
// Render text that is mostly prose+LaTeX (findings, answers). Keep newlines, render math.
function proseEl(text, scroll) {
const d = document.createElement("div");
d.className = "prose" + (scroll ? " scroll" : "");
d.innerHTML = mdToHtml(text || "");
return d;
}
// ---- sidebar ----
async function loadManifest() {
const r = await fetch(DATA_DIR + "manifest.json");
state.manifest = await r.json();
document.getElementById("trajCount").textContent =
state.manifest.length.toLocaleString() + " problems";
populateFilters();
renderTrajList();
}
function populateFilters() {
const uniq = key => [...new Set(state.manifest.map(m => m[key]).filter(Boolean))].sort();
const fill = (id, vals) => {
const sel = document.getElementById(id);
for (const v of vals) {
const o = document.createElement("option");
o.value = v; o.textContent = v; sel.appendChild(o);
}
};
fill("fPrompt", uniq("prompt_type"));
fill("fDiff", uniq("difficulty"));
}
function scoreClass(norm) {
if (norm == null) return "";
if (norm >= 0.7) return ""; // good (green)
if (norm >= 0.35) return "mid"; // warn (orange)
return "low"; // bad (red)
}
function sortManifest(items) {
const s = state.filters.sort;
const norm = m => m.score_normalized == null ? -1 : m.score_normalized;
const arr = items.slice();
if (s === "score") arr.sort((a, b) => norm(b) - norm(a));
else if (s === "score_asc") arr.sort((a, b) => norm(a) - norm(b));
else if (s === "layers") arr.sort((a, b) => (b.n_layers || 0) - (a.n_layers || 0));
else if (s === "title") arr.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
return arr;
}
function renderTrajList() {
const list = document.getElementById("trajList");
list.innerHTML = "";
const { q, prompt, diff } = state.filters;
const f = q.trim().toLowerCase();
let items = state.manifest.filter(m => {
if (prompt && m.prompt_type !== prompt) return false;
if (diff && m.difficulty !== diff) return false;
if (!f) return true;
return (m.title || "").toLowerCase().includes(f) ||
(m.paper_id || "").toLowerCase().includes(f) ||
String(m.problem_id).toLowerCase().includes(f) ||
(m.topic_tags || []).some(t => t.toLowerCase().includes(f));
});
items = sortManifest(items);
for (const m of items.slice(0, 800)) {
const div = document.createElement("div");
div.className = "trajItem";
div.dataset.file = m.file;
const scoreBadge = m.score != null
? `<span class="badge score ${scoreClass(m.score_normalized)}">${m.score}/${m.max_score ?? "?"}</span>` : "";
const ptBadge = m.prompt_type
? `<span class="badge pt">${escapeHtml(m.prompt_type)}</span>` : "";
const diffBadge = m.difficulty
? `<span class="badge diff-${m.difficulty}">${escapeHtml(m.difficulty)}</span>` : "";
div.innerHTML = `
<div class="tt">${escapeHtml(m.title || "(untitled)")}</div>
<div class="meta">
<span class="badge">${escapeHtml(String(m.problem_id))}</span>
<span class="badge">${m.n_layers} layers · ${m.n_explorations} exp</span>
${ptBadge}${diffBadge}${scoreBadge}
</div>`;
div.onclick = () => selectTraj(m.file, div);
list.appendChild(div);
}
if (!items.length) list.innerHTML = `<div class="empty">no matches</div>`;
}
async function selectTraj(file, el) {
document.querySelectorAll(".trajItem.active").forEach(n => n.classList.remove("active"));
if (el) el.classList.add("active");
const r = await fetch(DATA_DIR + file);
state.traj = await r.json();
renderTraj();
document.getElementById("main").scrollTop = 0;
}
// ---- main render ----
function renderTraj() {
const t = state.traj;
const content = document.getElementById("content");
content.innerHTML = "";
document.getElementById("trajId").textContent = t.title || `Problem ${t.problem_id}`;
const j = t.judge || {};
const scoreTxt = j.score != null ? `${j.score}/${j.max_score ?? "?"} judge` : "ungraded";
document.getElementById("trajStats").textContent =
`${t.problem_id} · ${t.layers.length} layers · ${t.n_explorations} explorations · ${scoreTxt}` +
(j.model ? ` · ${j.model}` : "");
// problem header
const prob = document.createElement("div");
prob.id = "problem";
const metaBadges = [
t.prompt_type && `<span class="badge pt">${escapeHtml(t.prompt_type)}</span>`,
t.difficulty && `<span class="badge diff-${t.difficulty}">${escapeHtml(t.difficulty)}</span>`,
t.target_level && `<span class="badge">${escapeHtml(t.target_level)}</span>`,
t.paper_id && `<span class="badge">${escapeHtml(t.paper_id)}</span>`,
].filter(Boolean).join("");
prob.innerHTML =
`<div class="lbl">Problem ${escapeHtml(String(t.problem_id))} ${metaBadges}</div>` +
(t.title ? `<div class="title">${escapeHtml(t.title)}</div>` : "") +
`<div class="body"></div>`;
prob.querySelector(".body").innerHTML = mdToHtml(t.problem);
if (t.topic_tags && t.topic_tags.length) {
const tr = document.createElement("div");
tr.className = "tagrow";
tr.innerHTML = t.topic_tags.map(tag => `<span class="badge">${escapeHtml(tag)}</span>`).join("");
prob.appendChild(tr);
}
content.appendChild(prob);
// layers of exploration
for (const L of t.layers) content.appendChild(renderLayer(L));
// final answer + judging
content.appendChild(renderFinal(t));
// rubric + reference
content.appendChild(renderRubricReference(t));
applyViews();
renderMath(content);
}
function card(cls, headHtml, open) {
const c = document.createElement("div");
c.className = "card " + cls + (open ? " open" : "");
const h = document.createElement("div");
h.className = "card-head";
h.innerHTML = headHtml + `<span class="chev">▶</span>`;
const b = document.createElement("div");
b.className = "card-body";
h.onclick = () => c.classList.toggle("open");
c.appendChild(h); c.appendChild(b);
c._body = b;
return c;
}
function renderLayer(L) {
const wrap = document.createElement("div");
wrap.className = "layer";
wrap.dataset.view = "explore";
const n = L.explorations.length;
const head = document.createElement("div");
head.className = "layer-head";
head.innerHTML = `<span class="lnum">Layer ${L.layer}</span>
<span class="lmeta">${n} exploration${n === 1 ? "" : "s"}</span>`;
wrap.appendChild(head);
for (const ex of L.explorations) {
const label = (ex.label || "").trim() || `${L.layer}`;
const headline = ex.core_result || ex.found || ex.direction;
const c = card("exp", `<span class="ttl">Exploration</span>
<span class="pill idx">${escapeHtml(label)}</span>
<span class="tag">${truncate(headline, 78)}</span>`, false);
const b = c._body;
// direction explored
if (ex.direction) {
const dirWrap = document.createElement("div");
dirWrap.dataset.view = "dirs";
dirWrap.innerHTML = `<div class="subsec">Direction explored</div>`;
const dt = document.createElement("div");
dt.className = "dir-text";
dt.innerHTML = mdInline(ex.direction);
dirWrap.appendChild(dt);
b.appendChild(dirWrap);
}
// found (execution finding)
if (ex.found) b.appendChild(labeled("Found", ex.found));
// rationale
if (ex.rationale) b.appendChild(labeled("Rationale", ex.rationale));
// core result
if (ex.core_result) b.appendChild(labeled("Core result", ex.core_result, true));
wrap.appendChild(c);
}
return wrap;
}
function renderFinal(t) {
const wrap = document.createElement("div");
wrap.className = "layer";
wrap.dataset.view = "final";
const j = t.judge || {};
const head = document.createElement("div");
head.className = "layer-head";
const pct = j.score_normalized != null ? ` · ${Math.round(j.score_normalized * 100)}%` : "";
const scoreTag = j.score != null
? `<span class="badge score ${scoreClass(j.score_normalized)}" style="font-size:12px">judge ${j.score}/${j.max_score ?? "?"}${pct}</span>` : "";
head.innerHTML = `<span class="lnum">Final answer</span>
<span class="lmeta">${escapeHtml(t.final_kind || "")}${t.final_depth != null ? ` · depth ${t.final_depth}` : ""}${t.final_answer_source ? ` · ${escapeHtml(t.final_answer_source)}` : ""}</span>`;
wrap.appendChild(head);
// judge explanation card
if (j.explanation || j.score != null) {
const c = card("final", `<span class="ttl">Judge verdict</span>
${scoreTag}
${j.model ? `<span class="tag">${escapeHtml(j.model)}${j.reasoning_effort ? ` · ${escapeHtml(j.reasoning_effort)}` : ""}</span>` : ""}`, true);
if (j.explanation) c._body.appendChild(proseEl(j.explanation));
else c._body.appendChild(proseEl(`Score: ${j.score}/${j.max_score}`));
wrap.appendChild(c);
}
// the synthesized solution
const c = card("final", `<span class="ttl">Synthesized solution</span>
<span class="tag">${(t.final_answer || "").length.toLocaleString()} chars</span>`, true);
if (t.final_answer) c._body.appendChild(proseEl(t.final_answer));
else c._body.appendChild(el("div", "empty", "No final answer recorded."));
wrap.appendChild(c);
return wrap;
}
function renderRubricReference(t) {
const wrap = document.createElement("div");
wrap.className = "layer";
if (t.rubric) {
const rw = document.createElement("div"); rw.dataset.view = "rubric";
const c = card("rubric", `<span class="ttl">Grading rubric</span>`, false);
c._body.appendChild(proseEl(t.rubric, true));
rw.appendChild(c); wrap.appendChild(rw);
}
if (t.reference_answer) {
const rw = document.createElement("div"); rw.dataset.view = "reference";
const c = card("reference", `<span class="ttl">Reference answer</span>`, false);
c._body.appendChild(proseEl(t.reference_answer, true));
rw.appendChild(c); wrap.appendChild(rw);
}
return wrap;
}
// ---- small helpers ----
function el(tag, cls, text) {
const d = document.createElement(tag);
if (cls) d.className = cls;
if (text != null) d.textContent = text;
return d;
}
function labeled(label, text, strong) {
const d = document.createElement("div");
d.style.margin = "8px 0";
const l = document.createElement("div");
l.className = "subsec"; l.style.marginTop = "0"; l.textContent = label;
d.appendChild(l);
const body = proseEl(text);
if (strong) body.style.color = "var(--accent2)";
d.appendChild(body);
return d;
}
function mdInline(s) { return mdToHtml(s || "").replace(/^<p>|<\/p>\s*$/g, ""); }
function truncate(s, n) { s = (s || "").replace(/\s+/g, " ").trim();
return s.length > n ? s.slice(0, n) + "…" : s; }
// ---- view toggles ----
function applyViews() {
const v = state.views;
document.querySelectorAll("[data-view]").forEach(el => {
if (el.classList.contains("toggle")) return; // toggle buttons themselves
const key = el.dataset.view;
el.classList.toggle("hidden", !v[key]);
});
document.querySelectorAll(".toggle").forEach(t => {
t.classList.toggle("on", v[t.dataset.view]);
});
}
document.querySelector(".toggles").addEventListener("click", e => {
const t = e.target.closest(".toggle");
if (!t) return;
const key = t.dataset.view;
state.views[key] = !state.views[key];
applyViews();
});
document.getElementById("search").addEventListener("input", e => {
state.filters.q = e.target.value; renderTrajList();
});
document.getElementById("fPrompt").addEventListener("change", e => {
state.filters.prompt = e.target.value; renderTrajList();
});
document.getElementById("fDiff").addEventListener("change", e => {
state.filters.diff = e.target.value; renderTrajList();
});
document.getElementById("fSort").addEventListener("change", e => {
state.filters.sort = e.target.value; renderTrajList();
});
loadManifest().catch(err => {
document.getElementById("trajCount").textContent = "failed to load manifest.json";
document.getElementById("content").innerHTML =
`<div class="empty" style="margin-top:40px">Could not load <code>data/manifest.json</code>.<br>` +
`Run <code>build_index.py</code> first, then serve this folder with a static server ` +
`(e.g. <code>python -m http.server</code>).<br><br>${escapeHtml(String(err))}</div>`;
});
</script>
</body>
</html>