Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>TCS — Trace 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: 320px 1fr; height: 100vh; overflow: hidden; | |
| } | |
| /* ---- Sidebar: trajectory 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; 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); } | |
| #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: 8px; flex-wrap: wrap; } | |
| .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.cov-core_reached { color: var(--good); border-color: var(--good); } | |
| .badge.cov-partial { color: var(--warn); border-color: var(--warn); } | |
| .badge.cov-none { color: var(--bad); border-color: var(--bad); } | |
| /* ---- 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; } | |
| #problem .body { font-size: 14.5px; } | |
| /* 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.planner { border-left: 3px solid var(--accent); } | |
| .card.dir { border-left: 3px solid #6f86c9; } | |
| .card.exec { border-left: 3px solid var(--accent2); } | |
| .card.final { border-left: 3px solid #b694e8; } | |
| .card.frontier { border-left: 3px solid var(--warn); background: #161821; } | |
| .frontier-layer { margin: 6px 0 14px; } | |
| .frontier-layer-h { font-size: 12px; font-weight: 700; color: var(--warn); | |
| text-transform: uppercase; letter-spacing: .6px; margin: 10px 0 6px; } | |
| .frontier-exp { border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px; | |
| margin: 8px 0; background: var(--panel); } | |
| .frontier-exp > .pill.idx { margin-bottom: 4px; display: inline-block; } | |
| .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); } | |
| .cot { background: #12151c; border: 1px solid var(--line); border-radius: 8px; | |
| padding: 12px 14px; font-size: 13px; color: #cdd4e0; white-space: normal; | |
| max-height: 460px; overflow-y: auto; } | |
| .cot.exec-cot { border-left: 2px solid var(--accent2); } | |
| .prose { font-size: 13.5px; } | |
| .prose p { margin: 8px 0; } | |
| .kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 14px; margin: 6px 0; } | |
| .kv .k { color: var(--muted); font-size: 12px; } | |
| .exec-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } | |
| .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; } | |
| .final-grid { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } | |
| .empty { color: var(--muted); font-style: italic; padding: 8px 0; } | |
| .hidden { display: none ; } | |
| 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>TCS Trace Viewer<small id="trajCount">loading…</small></h1> | |
| <input id="search" placeholder="filter by bench / problem id / text…" /> | |
| <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" data-view="frontier">Frontier</span> | |
| <span class="toggle on" data-view="dirs">Directions</span> | |
| <span class="toggle" data-view="cot">Planner CoT</span> | |
| <span class="toggle" data-view="exec">Execution trace</span> | |
| <span class="toggle on" data-view="summary">Summaries</span> | |
| <span class="toggle on" data-view="final">Final answers</span> | |
| </div> | |
| </div> | |
| <div class="wrap" id="content"> | |
| <div class="empty" style="margin-top:40px">Select a trajectory from the left.</div> | |
| </div> | |
| </main> | |
| <script> | |
| const DATA_DIR = "data/"; | |
| const state = { | |
| manifest: [], | |
| traj: null, | |
| views: { frontier: false, dirs: true, cot: false, exec: false, summary: true, final: true }, | |
| }; | |
| // ---- 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 => ({ "&": "&", "<": "<", ">": ">" }[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 (CoT, findings). Keep newlines, render math. | |
| function proseEl(text) { | |
| const d = document.createElement("div"); | |
| d.className = "prose"; | |
| 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() + " trajectories"; | |
| renderTrajList(""); | |
| } | |
| // Colour a judge score. Prefer a normalized (0..1) value when present (eval | |
| // rollouts carry score_normalized + an explicit max); otherwise fall back to the | |
| // legacy absolute-score thresholds (midtrain judge scores are out of 7). | |
| function scoreClass(s, norm) { | |
| const v = (norm != null) ? norm : (s != null ? s / 7 : null); | |
| if (v == null) return ""; | |
| if (v >= 0.85) return ""; // good (green) | |
| if (v >= 0.4) return "mid"; // warn (orange) | |
| return "low"; // bad (red) | |
| } | |
| function renderTrajList(filter) { | |
| const list = document.getElementById("trajList"); | |
| list.innerHTML = ""; | |
| const f = filter.trim().toLowerCase(); | |
| const items = state.manifest.filter(m => { | |
| if (!f) return true; | |
| return (m.title || "").toLowerCase().includes(f) || | |
| String(m.problem_id).toLowerCase().includes(f) || | |
| (m.bench || "").toLowerCase().includes(f); | |
| }); | |
| for (const m of items.slice(0, 600)) { | |
| const div = document.createElement("div"); | |
| div.className = "trajItem"; | |
| div.dataset.file = m.file; | |
| const score = m.best_judge_score; | |
| const maxs = m.max_score; | |
| const scoreBadge = score != null | |
| ? `<span class="badge score ${scoreClass(score, m.score_normalized)}">judge ${score}${maxs != null ? `/${maxs}` : ""}</span>` : ""; | |
| const covBadge = m.coverage | |
| ? `<span class="badge cov-${m.coverage}">${m.coverage}</span>` : ""; | |
| // eval rollouts carry a bench label + termination reason (midtrain has neither). | |
| const benchBadge = m.bench ? `<span class="badge">${escapeHtml(m.bench)}</span>` : ""; | |
| const termBadge = m.termination ? `<span class="badge">${escapeHtml(m.termination)}</span>` : ""; | |
| div.innerHTML = ` | |
| <div class="tt">${escapeHtml(m.title || "(untitled)")}</div> | |
| <div class="meta"> | |
| ${benchBadge}<span class="badge">#${m.problem_id}.${m.spine_id}</span> | |
| <span class="badge">${m.n_layers} layers</span> | |
| <span class="badge">e:${m.counts.e} mr:${m.counts.mr}</span> | |
| ${termBadge}${covBadge}${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(); | |
| } | |
| // ---- main render ---- | |
| function renderTraj() { | |
| const t = state.traj; | |
| const content = document.getElementById("content"); | |
| content.innerHTML = ""; | |
| document.getElementById("trajId").textContent = | |
| `Problem #${t.problem_id} · spine ${t.spine_id}`; | |
| document.getElementById("trajStats").textContent = | |
| `${t.n_layers} layers · ${t.counts.mr} MR · ${t.counts.e} E · ` + | |
| `${t.counts.final_answer} final` + | |
| (t.bench ? ` · ${t.bench}` : "") + | |
| (t.termination ? ` · ${t.termination}` : "") + | |
| (t.judge_score != null ? ` · judge ${t.judge_score}${t.max_score != null ? "/" + t.max_score : ""}` : "") + | |
| (t.src_dirs && t.src_dirs.length ? " · " + t.src_dirs.join(", ") : ""); | |
| // problem | |
| const prob = document.createElement("div"); | |
| prob.id = "problem"; | |
| prob.innerHTML = `<div class="lbl">Problem</div><div class="body"></div>`; | |
| prob.querySelector(".body").innerHTML = mdToHtml(t.problem); | |
| content.appendChild(prob); | |
| // layers | |
| for (const L of t.layers) content.appendChild(renderLayer(L)); | |
| // final answers | |
| if (t.final_answers && t.final_answers.length) { | |
| content.appendChild(renderFinals(t.final_answers)); | |
| } | |
| 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 renderFrontierCard(L) { | |
| const fr = L.assembled_frontier; | |
| const wrapper = document.createElement("div"); | |
| wrapper.dataset.view = "frontier"; // toggled by the "Frontier" switch | |
| // count prior explorations the planner saw | |
| const priorLayers = fr && fr.layers ? Object.keys(fr.layers) : []; | |
| const nExp = fr && fr.layers | |
| ? Object.values(fr.layers).reduce((a, b) => a + b.length, 0) : 0; | |
| const empty = !fr || fr.is_empty || !nExp; | |
| const c = card("frontier", `<span class="ttl">Assembled frontier</span> | |
| <span class="tag">input to this layer's planner</span> | |
| ${empty ? `<span class="tag">empty — first layer</span>` | |
| : `<span class="tag">${nExp} explorations · ${priorLayers.length} prior layer(s)</span>`}`, | |
| false); | |
| const b = c._body; | |
| if (empty) { | |
| const e = document.createElement("div"); | |
| e.className = "empty"; | |
| e.textContent = "No prior exploration — the planner started from the bare problem."; | |
| b.appendChild(e); | |
| } else { | |
| // Render each prior layer's explorations exactly as the MR saw them. | |
| for (const ly of priorLayers.sort((a, z) => +a - +z)) { | |
| const lg = document.createElement("div"); | |
| lg.className = "frontier-layer"; | |
| lg.innerHTML = `<div class="frontier-layer-h">Layer ${ly}</div>`; | |
| for (const ex of fr.layers[ly]) { | |
| lg.appendChild(frontierExpEl(ex)); | |
| } | |
| b.appendChild(lg); | |
| } | |
| } | |
| wrapper.appendChild(c); | |
| return wrapper; | |
| } | |
| function frontierExpEl(ex) { | |
| const d = document.createElement("div"); | |
| d.className = "frontier-exp"; | |
| const label = (ex.label || "").replace("Exploration ", ""); | |
| d.innerHTML = `<span class="pill idx">${label}</span>`; | |
| if (ex.direction) d.appendChild(labeled("Direction explored", ex.direction)); | |
| if (ex.found) d.appendChild(labeled("Found", ex.found)); | |
| if (ex.rationale) d.appendChild(labeled("Rationale", ex.rationale)); | |
| if (ex.core_result) d.appendChild(labeled("Core result", ex.core_result, true)); | |
| return d; | |
| } | |
| function renderLayer(L) { | |
| const wrap = document.createElement("div"); | |
| wrap.className = "layer"; | |
| const p = L.planner; | |
| const nExec = L.executions.length; | |
| const head = document.createElement("div"); | |
| head.className = "layer-head"; | |
| const ndir = p ? (p.directions ? p.directions.length : 0) : 0; | |
| head.innerHTML = `<span class="lnum">Layer ${L.layer}</span> | |
| <span class="lmeta">${ndir} directions committed · ${nExec} executed</span>`; | |
| wrap.appendChild(head); | |
| // ---- Assembled frontier the planner consumed at this layer ---- | |
| // This is the exact "Exploration so far:" context the MR was generated from: | |
| // the union of accepted summaries from layers 1..L-1 (shuffled), as rendered | |
| // by frontier.py. Shown first because it's the *input* to this layer. | |
| wrap.appendChild(renderFrontierCard(L)); | |
| // ---- Planner card: CoT + committed directions ---- | |
| if (p) { | |
| const c = card("planner", `<span class="ttl">Planner (MR)</span> | |
| <span class="tag">${ndir} directions</span> | |
| ${p.n_wrong ? `<span class="tag">${p.n_wrong} considered & dropped</span>` : ""} | |
| ${flagsTag(p.mr_verify)}`, false); | |
| const b = c._body; | |
| // committed directions (the "directions chosen at this layer") | |
| const dirsWrap = document.createElement("div"); | |
| dirsWrap.dataset.view = "dirs"; | |
| dirsWrap.innerHTML = `<div class="subsec">Committed directions</div>`; | |
| (p.directions || []).forEach((d, i) => { | |
| const dd = document.createElement("div"); | |
| dd.className = "dir-text"; | |
| dd.innerHTML = `<b>${i + 1}.</b> ` + mdInline(d); | |
| dirsWrap.appendChild(dd); | |
| }); | |
| b.appendChild(dirsWrap); | |
| // planner CoT | |
| const cotWrap = document.createElement("div"); | |
| cotWrap.dataset.view = "cot"; | |
| cotWrap.innerHTML = `<div class="subsec">Planner reasoning (CoT)</div>`; | |
| const cot = document.createElement("div"); | |
| cot.className = "cot"; | |
| cot.appendChild(proseEl(p.cot)); | |
| cotWrap.appendChild(cot); | |
| b.appendChild(cotWrap); | |
| wrap.appendChild(c); | |
| } | |
| // ---- Execution cards (one per executed direction) ---- | |
| for (const ex of L.executions) { | |
| const label = `${L.layer}${String.fromCharCode(97 + (ex.direction_idx ?? 0))}`; | |
| const headline = ex.summary ? firstLine(ex.summary) : ex.direction; | |
| const c = card("exec", `<span class="ttl">Execution</span> | |
| <span class="pill idx">${label}</span> | |
| <span class="tag">${truncate(headline, 70)}</span>`, false); | |
| const b = c._body; | |
| // direction explored | |
| 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); | |
| // execution CoT | |
| const cotWrap = document.createElement("div"); | |
| cotWrap.dataset.view = "exec"; | |
| cotWrap.innerHTML = `<div class="subsec">Execution reasoning (CoT)</div>`; | |
| const cot = document.createElement("div"); | |
| cot.className = "cot exec-cot"; | |
| cot.appendChild(proseEl(ex.cot)); | |
| cotWrap.appendChild(cot); | |
| // the finding (post-think execution output) | |
| if (ex.finding) { | |
| const fh = document.createElement("div"); | |
| fh.innerHTML = `<div class="subsec">Finding (execution output)</div>`; | |
| fh.appendChild(proseEl(ex.finding)); | |
| cotWrap.appendChild(fh); | |
| } | |
| b.appendChild(cotWrap); | |
| // summary (detailed summary + rationale + core result) | |
| const sumWrap = document.createElement("div"); | |
| sumWrap.dataset.view = "summary"; | |
| sumWrap.innerHTML = `<div class="subsec">Execution summary (F4)</div>`; | |
| if (ex.summary) sumWrap.appendChild(labeled("Summary", ex.summary)); | |
| if (ex.rationale) sumWrap.appendChild(labeled("Rationale", ex.rationale)); | |
| if (ex.core_result) sumWrap.appendChild(labeled("Core result", ex.core_result, true)); | |
| b.appendChild(sumWrap); | |
| wrap.appendChild(c); | |
| } | |
| // ---- Frontier-only explorations (executions whose e-record isn't present, | |
| // but whose summary survives in the rendered frontier of a later layer) ---- | |
| const seenDir = new Set(L.executions.map(e => normKey(e.core_result || e.direction))); | |
| const extra = (L.frontier_explorations || []).filter( | |
| fe => !seenDir.has(normKey(fe.core_result || fe.direction))); | |
| for (const fe of extra) { | |
| const c = card("dir", `<span class="ttl">Exploration</span> | |
| <span class="pill idx">${fe.label.replace("Exploration ", "")}</span> | |
| <span class="tag">${truncate(fe.found || fe.direction, 70)}</span> | |
| <span class="tag">frontier-only</span>`, false); | |
| const b = c._body; | |
| const dirWrap = document.createElement("div"); | |
| dirWrap.dataset.view = "dirs"; | |
| if (fe.direction) { | |
| dirWrap.innerHTML = `<div class="subsec">Direction explored</div>`; | |
| const dt = document.createElement("div"); dt.className = "dir-text"; | |
| dt.innerHTML = mdInline(fe.direction); dirWrap.appendChild(dt); | |
| } | |
| b.appendChild(dirWrap); | |
| const sumWrap = document.createElement("div"); | |
| sumWrap.dataset.view = "summary"; | |
| sumWrap.innerHTML = `<div class="subsec">Summary (from frontier)</div>`; | |
| if (fe.found) sumWrap.appendChild(labeled("Found", fe.found)); | |
| if (fe.rationale) sumWrap.appendChild(labeled("Rationale", fe.rationale)); | |
| if (fe.core_result) sumWrap.appendChild(labeled("Core result", fe.core_result, true)); | |
| b.appendChild(sumWrap); | |
| wrap.appendChild(c); | |
| } | |
| return wrap; | |
| } | |
| function renderFinals(finals) { | |
| const wrap = document.createElement("div"); | |
| wrap.className = "layer"; | |
| wrap.dataset.view = "final"; | |
| const head = document.createElement("div"); | |
| head.className = "layer-head"; | |
| head.innerHTML = `<span class="lnum">Final answers</span> | |
| <span class="lmeta">${finals.length} synthesized solution(s)</span>`; | |
| wrap.appendChild(head); | |
| finals.forEach((f, i) => { | |
| const sc = f.judge_score; | |
| const scTag = sc != null | |
| ? `<span class="badge score ${scoreClass(sc, f.score_normalized)}">judge ${sc}${f.max_score != null ? `/${f.max_score}` : ""}${f.rubric_pct != null ? ` · ${Math.round(f.rubric_pct * 100)}%` : ""}</span>` | |
| : ""; | |
| const c = card("final", `<span class="ttl">Final answer #${f.sample_idx ?? i}</span> | |
| <span class="pill">stop @ layer ${f.stop_layer}</span> | |
| ${f.coverage ? `<span class="badge cov-${f.coverage}">${f.coverage}</span>` : ""} | |
| ${scTag}`, i === 0); | |
| const b = c._body; | |
| if (f.cot) { | |
| const cotWrap = document.createElement("div"); | |
| cotWrap.dataset.view = "cot"; | |
| cotWrap.innerHTML = `<div class="subsec">Synthesis reasoning (CoT)</div>`; | |
| const cot = document.createElement("div"); cot.className = "cot"; | |
| cot.appendChild(proseEl(f.cot)); cotWrap.appendChild(cot); | |
| b.appendChild(cotWrap); | |
| } | |
| const ans = document.createElement("div"); | |
| ans.innerHTML = `<div class="subsec">Solution</div>`; | |
| ans.appendChild(proseEl(f.answer)); | |
| b.appendChild(ans); | |
| wrap.appendChild(c); | |
| }); | |
| return wrap; | |
| } | |
| // ---- small helpers ---- | |
| 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 flagsTag(mrv) { | |
| if (!mrv) return ""; | |
| const flags = Object.entries(mrv).filter(([k, v]) => v === true && k !== "clean"); | |
| if (mrv.clean) return `<span class="tag" style="color:var(--good)">clean</span>`; | |
| if (!flags.length) return ""; | |
| return `<span class="tag" style="color:var(--warn)">${flags.map(f => f[0]).join(", ")}</span>`; | |
| } | |
| function mdInline(s) { return mdToHtml(s || "").replace(/^<p>|<\/p>\s*$/g, ""); } | |
| function firstLine(s) { return (s || "").split("\n")[0]; } | |
| function truncate(s, n) { s = (s || "").replace(/\s+/g, " ").trim(); | |
| return s.length > n ? s.slice(0, n) + "…" : s; } | |
| function normKey(s) { return (s || "").replace(/\s+/g, " ").trim().slice(0, 80).toLowerCase(); } | |
| // ---- 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]); | |
| }); | |
| // a final-answers section toggles via its own data-view="final" | |
| 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 => { | |
| renderTrajList(e.target.value); | |
| }); | |
| 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> | |