| "use strict"; |
|
|
| const FALLBACK_FEEDBACK = "Revise the review to better match the rubric's content, tone, style, and length."; |
| const state = { |
| index: null, |
| trajectories: [], |
| visible: [], |
| checkpoints: [], |
| payload: null, |
| activeRound: 0, |
| view: "trajectory", |
| }; |
|
|
| const byId = (id) => document.getElementById(id); |
| const finite = (value) => value !== null && value !== undefined && Number.isFinite(Number(value)); |
| const number = (value) => (finite(value) ? Number(value) : null); |
|
|
| function formatScore(value, digits = 3) { |
| return finite(value) ? Number(value).toFixed(digits) : "N/A"; |
| } |
|
|
| function formatSigned(value, digits = 3) { |
| if (!finite(value)) return "N/A"; |
| const numeric = Number(value); |
| return `${numeric > 0 ? "+" : ""}${numeric.toFixed(digits)}`; |
| } |
|
|
| function formatPercent(value, digits = 1) { |
| if (!finite(value)) return "N/A"; |
| return `${(Number(value) * 100).toFixed(digits)}%`; |
| } |
|
|
| function mean(values) { |
| const valid = values.filter(finite).map(Number); |
| return valid.length ? valid.reduce((total, value) => total + value, 0) / valid.length : null; |
| } |
|
|
| function words(value) { |
| return String(value || "").trim().split(/\s+/).filter(Boolean).length; |
| } |
|
|
| function cleanResearchText(value) { |
| return String(value || "") |
| .replace(/<br\s*\/?>/gi, "\n") |
| .replace(/<\/?(?:response|rubric)>/gi, "") |
| .replace(/&(#x[0-9a-f]+|#\d+|quot|amp|apos|lt|gt|nbsp);/gi, (match, entity) => { |
| const named = { quot: '"', amp: "&", apos: "'", lt: "<", gt: ">", nbsp: " " }; |
| const lowered = entity.toLowerCase(); |
| if (Object.prototype.hasOwnProperty.call(named, lowered)) return named[lowered]; |
| const numeric = lowered.startsWith("#x") |
| ? Number.parseInt(lowered.slice(2), 16) |
| : Number.parseInt(lowered.slice(1), 10); |
| if (!Number.isInteger(numeric) || numeric < 0 || numeric > 0x10ffff) return match; |
| try { |
| return String.fromCodePoint(numeric); |
| } catch (_error) { |
| return match; |
| } |
| }) |
| .trim(); |
| } |
|
|
| function appendInline(container, text) { |
| const chunks = String(text).split(/(\*\*[^*]+\*\*)/g); |
| chunks.forEach((chunk) => { |
| if (chunk.startsWith("**") && chunk.endsWith("**") && chunk.length > 4) { |
| const strong = document.createElement("strong"); |
| strong.textContent = chunk.slice(2, -2); |
| container.appendChild(strong); |
| } else { |
| container.appendChild(document.createTextNode(chunk)); |
| } |
| }); |
| } |
|
|
| function renderStructured(container, value) { |
| container.replaceChildren(); |
| const text = cleanResearchText(value); |
| if (!text) { |
| const empty = document.createElement("p"); |
| empty.textContent = "No text was logged."; |
| container.appendChild(empty); |
| return; |
| } |
| const blocks = text.split(/\n\s*\n/).filter((block) => block.trim()); |
| blocks.forEach((block) => { |
| const lines = block.split("\n").filter((line) => line.trim()); |
| const numbered = lines.length > 1 && lines.every((line) => /^\s*\d+[.)]\s+/.test(line)); |
| if (numbered) { |
| const list = document.createElement("ol"); |
| lines.forEach((line) => { |
| const item = document.createElement("li"); |
| appendInline(item, line.replace(/^\s*\d+[.)]\s+/, "")); |
| list.appendChild(item); |
| }); |
| container.appendChild(list); |
| return; |
| } |
| const paragraph = document.createElement("p"); |
| lines.forEach((line, index) => { |
| if (index) paragraph.appendChild(document.createElement("br")); |
| appendInline(paragraph, line); |
| }); |
| container.appendChild(paragraph); |
| }); |
| } |
|
|
| function addOption(select, value, label) { |
| const option = document.createElement("option"); |
| option.value = String(value); |
| option.textContent = label; |
| select.appendChild(option); |
| } |
|
|
| function badge(label, className = "") { |
| const element = document.createElement("span"); |
| element.className = `badge ${className}`.trim(); |
| element.textContent = label; |
| return element; |
| } |
|
|
| function metricCell(label, value, detail, className = "") { |
| const cell = document.createElement("div"); |
| cell.className = `metric-cell ${className}`.trim(); |
| const name = document.createElement("span"); |
| name.textContent = label; |
| const strong = document.createElement("strong"); |
| strong.textContent = value; |
| cell.append(name, strong); |
| if (detail) { |
| const small = document.createElement("small"); |
| small.textContent = detail; |
| cell.appendChild(small); |
| } |
| return cell; |
| } |
|
|
| function scorePill(label, value, className = "") { |
| const pill = document.createElement("div"); |
| pill.className = `score-pill ${className}`.trim(); |
| const name = document.createElement("span"); |
| name.textContent = label; |
| const strong = document.createElement("strong"); |
| strong.textContent = value; |
| pill.append(name, strong); |
| return pill; |
| } |
|
|
| function deltaClass(value) { |
| if (!finite(value) || Math.abs(Number(value)) < 1e-9) return ""; |
| return Number(value) > 0 ? "positive" : "negative"; |
| } |
|
|
| function outcomeLabel(value) { |
| const labels = { improved: "Gold improved", unchanged: "Gold unchanged", worsened: "Gold worsened" }; |
| return labels[value] || String(value || "Outcome unavailable"); |
| } |
|
|
| function trajectorySummary(record, payload = null) { |
| return (payload && (payload.trajectory || payload.summary)) || record || {}; |
| } |
|
|
| function trajectoryId(record) { |
| return String(record.id || record.trajectory_id || record.path || ""); |
| } |
|
|
| function trajectoryPath(record) { |
| return String(record.path || record.data_path || `data/trajectories/${trajectoryId(record)}.json`); |
| } |
|
|
| function checkpointId(record) { |
| return String(record.checkpoint_id || `${record.run_id || record.run_label || "run"}:${record.step}:${record.uid}`); |
| } |
|
|
| function checkpointLabel(checkpoint) { |
| const title = checkpoint.title || checkpoint.context_label || checkpoint.uid || "Source example"; |
| const run = checkpoint.run_label || checkpoint.run_id || "run"; |
| return `Step ${checkpoint.step} · ${title} · ${run}`; |
| } |
|
|
| function buildCheckpointIndex() { |
| const supplied = Array.isArray(state.index.checkpoints) ? state.index.checkpoints : []; |
| const byKey = new Map(supplied.map((item) => [String(item.id || checkpointId(item)), { ...item }])); |
| state.trajectories.forEach((record) => { |
| const id = checkpointId(record); |
| if (!byKey.has(id)) { |
| byKey.set(id, { |
| id, |
| run_id: record.run_id, |
| run_label: record.run_label, |
| step: record.step, |
| uid: record.uid, |
| title: record.title, |
| author: record.author, |
| category: record.category, |
| }); |
| } |
| const checkpoint = byKey.get(id); |
| checkpoint.id = id; |
| }); |
| state.checkpoints = [...byKey.values()].sort((a, b) => Number(a.step) - Number(b.step) || String(a.run_id).localeCompare(String(b.run_id))); |
| } |
|
|
| function populateCheckpointSelect(preferred = null) { |
| const select = byId("checkpoint-select"); |
| const current = preferred || select.value; |
| select.replaceChildren(); |
| state.checkpoints.forEach((checkpoint) => addOption(select, checkpoint.id, checkpointLabel(checkpoint))); |
| if (state.checkpoints.some((checkpoint) => checkpoint.id === current)) select.value = current; |
| if (!select.value && state.checkpoints.length) select.value = state.checkpoints[0].id; |
| } |
|
|
| function applyTrajectoryFilter(preferredId = null) { |
| const checkpoint = byId("checkpoint-select").value; |
| const outcome = byId("outcome-select").value; |
| let visible = state.trajectories.filter((record) => checkpointId(record) === checkpoint); |
| if (outcome !== "all") visible = visible.filter((record) => String(record.outcome) === outcome); |
| if (!visible.length && outcome !== "all") { |
| byId("outcome-select").value = "all"; |
| visible = state.trajectories.filter((record) => checkpointId(record) === checkpoint); |
| } |
| state.visible = visible.sort((a, b) => Number(a.rubric_idx) - Number(b.rubric_idx) || Number(a.judge_idx) - Number(b.judge_idx)); |
|
|
| const select = byId("trajectory-select"); |
| const current = preferredId || select.value; |
| select.replaceChildren(); |
| state.visible.forEach((record) => { |
| const tie = Number(record.tie_count || 1) > 1 ? ` · ${record.tie_count}-way tie` : ""; |
| addOption( |
| select, |
| trajectoryId(record), |
| `Rubric ${record.rubric_idx} · path ${record.judge_idx} · selected r${record.selected_round}${tie} · ${outcomeLabel(record.outcome)}`, |
| ); |
| }); |
| if (state.visible.some((record) => trajectoryId(record) === current)) select.value = current; |
| if (!select.value && state.visible.length) select.value = trajectoryId(state.visible[0]); |
| updateTrajectoryPosition(); |
| } |
|
|
| function selectedRecord() { |
| const id = byId("trajectory-select").value; |
| return state.visible.find((record) => trajectoryId(record) === id) || null; |
| } |
|
|
| function updateTrajectoryPosition() { |
| const id = byId("trajectory-select").value; |
| const index = state.visible.findIndex((record) => trajectoryId(record) === id); |
| byId("trajectory-position").textContent = index >= 0 ? `${index + 1} of ${state.visible.length}` : "No paths"; |
| byId("previous-trajectory").disabled = index <= 0; |
| byId("next-trajectory").disabled = index < 0 || index >= state.visible.length - 1; |
| } |
|
|
| async function loadSelectedTrajectory() { |
| const record = selectedRecord(); |
| updateTrajectoryPosition(); |
| if (!record) { |
| showError("No trajectory matches these filters."); |
| return; |
| } |
| byId("loading").hidden = false; |
| byId("trajectory-content").hidden = true; |
| byId("error").hidden = true; |
| try { |
| const response = await fetch(trajectoryPath(record)); |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); |
| state.payload = await response.json(); |
| state.activeRound = 0; |
| renderTrajectory(record, state.payload); |
| byId("loading").hidden = true; |
| byId("trajectory-content").hidden = false; |
| } catch (error) { |
| showError(`Could not load this trajectory (${error.message}).`); |
| } |
| } |
|
|
| function showError(message) { |
| byId("loading").hidden = true; |
| byId("trajectory-content").hidden = true; |
| byId("error").textContent = message; |
| byId("error").hidden = false; |
| } |
|
|
| function extractContext(payload) { |
| if (typeof payload.context === "string") return { text: payload.context }; |
| return payload.context || payload.source_context || {}; |
| } |
|
|
| function extractRubric(payload) { |
| if (typeof payload.rubric === "string") return { parsed: payload.rubric, generated: payload.rubric }; |
| return payload.rubric || {}; |
| } |
|
|
| function extractPrompts(payload) { |
| return payload.prompts || payload.prompt || {}; |
| } |
|
|
| function renderTrajectory(record, payload) { |
| const summary = trajectorySummary(record, payload); |
| const checkpoint = payload.checkpoint || {}; |
| const context = extractContext(payload); |
| const rubric = extractRubric(payload); |
| const rounds = payload.rounds || payload.turns || []; |
| payload.rounds = rounds; |
|
|
| const title = context.title || checkpoint.title || record.title || "Target book"; |
| byId("record-eyebrow").textContent = `Step ${checkpoint.step ?? record.step} · ${checkpoint.run_label || record.run_label || checkpoint.run_id || record.run_id}`; |
| byId("record-title").textContent = title; |
| byId("record-subtitle").textContent = [context.author || record.author, context.category || record.category].filter(Boolean).join(" · "); |
| byId("record-id").textContent = `${checkpoint.uid || record.uid} · rubric ${summary.rubric_idx ?? record.rubric_idx} · path ${summary.judge_idx ?? record.judge_idx}`; |
|
|
| renderSummary(summary, rounds); |
| renderContext(context, payload.target_response || payload.target || payload.reference_response || ""); |
| renderPrompts(extractPrompts(payload)); |
| renderRubric(rubric, summary, record); |
| renderRoundTrack(rounds); |
| renderRoundDetail(rounds); |
| renderHistory(rounds); |
| refreshIcons(); |
| } |
|
|
| function summaryValue(summary, ...keys) { |
| for (const key of keys) if (summary[key] !== undefined) return summary[key]; |
| return null; |
| } |
|
|
| function renderSummary(summary, rounds) { |
| const initialGold = summaryValue(summary, "initial_gold") ?? rounds[0]?.gold_score; |
| const lastGold = summaryValue(summary, "last_gold") ?? rounds.at(-1)?.gold_score; |
| const selectedGold = summaryValue(summary, "selected_gold", "selected_gold_weighted"); |
| const bestGold = summaryValue(summary, "best_gold", "oracle_best_gold") ?? Math.max(...rounds.map((row) => Number(row.gold_score))); |
| const selectedDelta = finite(selectedGold) && finite(initialGold) ? Number(selectedGold) - Number(initialGold) : null; |
| const spearman = summaryValue(summary, "proxy_gold_spearman", "spearman"); |
| const tieCount = Number(summary.tie_count || summary.selected_tie_count || 1); |
| const selectedRound = summary.selected_round; |
| const container = byId("trajectory-summary"); |
| container.replaceChildren( |
| metricCell("Initial gold", formatScore(initialGold), "round 0", "gold"), |
| metricCell("Selected gold", formatScore(selectedGold), tieCount > 1 ? `mean of ${tieCount} tied rounds` : `round ${selectedRound}`, "gold"), |
| metricCell("Selected change", formatSigned(selectedDelta), "selected minus initial", deltaClass(selectedDelta)), |
| metricCell("Last gold", formatScore(lastGold), `round ${rounds.length - 1}`, "gold"), |
| metricCell("Oracle best gold", formatScore(bestGold), "best logged state", "gold"), |
| metricCell("Proxy-gold rank", formatScore(spearman), "Spearman across valid rounds", "proxy"), |
| ); |
| } |
|
|
| function renderContext(context, target) { |
| const metadata = [ |
| ["Title", context.title], |
| ["Author", context.author], |
| ["Category", context.category], |
| ["Store", context.store_label || context.store], |
| ["Subtitle", context.subtitle], |
| ["Price", context.price], |
| ].filter(([, value]) => value !== null && value !== undefined && String(value).trim()); |
| const list = byId("context-meta"); |
| list.replaceChildren(); |
| metadata.forEach(([label, value]) => { |
| const wrapper = document.createElement("div"); |
| const term = document.createElement("dt"); |
| term.textContent = label; |
| const definition = document.createElement("dd"); |
| definition.textContent = String(value); |
| wrapper.append(term, definition); |
| list.appendChild(wrapper); |
| }); |
| renderStructured(byId("context-text"), context.text || context.context || ""); |
| renderStructured(byId("target-response"), target); |
| resetClamp("context-text", "context-toggle", "Show full context", "Collapse context"); |
| } |
|
|
| function renderMessages(container, messages) { |
| container.replaceChildren(); |
| (messages || []).forEach((message) => { |
| const wrapper = document.createElement("article"); |
| wrapper.className = "message"; |
| const role = document.createElement("div"); |
| role.className = "message-role"; |
| role.textContent = message.role || "message"; |
| const content = document.createElement("div"); |
| content.className = "message-content"; |
| content.textContent = cleanResearchText(message.content || ""); |
| wrapper.append(role, content); |
| container.appendChild(wrapper); |
| }); |
| if (!messages || !messages.length) { |
| const empty = document.createElement("p"); |
| empty.textContent = "No prompt messages were exported."; |
| container.appendChild(empty); |
| } |
| } |
|
|
| function renderPrompts(prompts) { |
| const candidateMessages = prompts.candidate_messages || prompts.icl_messages || []; |
| const rubricMessages = prompts.rubric_messages || prompts.rubric_prompt || []; |
| renderMessages(byId("candidate-messages"), candidateMessages); |
| renderMessages(byId("rubric-messages"), rubricMessages); |
| byId("candidate-prompt-size").textContent = `${candidateMessages.length} messages · ${candidateMessages.reduce((total, item) => total + words(item.content), 0).toLocaleString()} words`; |
| byId("rubric-prompt-size").textContent = `${rubricMessages.length} messages · ${rubricMessages.reduce((total, item) => total + words(item.content), 0).toLocaleString()} words`; |
| } |
|
|
| function renderRubric(rubric, summary, record) { |
| const generated = rubric.generated || rubric.generated_rubric || rubric.parsed || rubric.parsed_rubric || ""; |
| const parsed = rubric.parsed || rubric.parsed_rubric || generated; |
| renderStructured(byId("rubric-text"), generated); |
| renderStructured(byId("parsed-rubric"), parsed); |
| const exact = Boolean(parsed); |
| byId("rubric-status").textContent = exact ? "Exact logged rubric" : "Rubric unavailable"; |
| byId("rubric-status").className = `status-badge ${exact ? "valid" : ""}`; |
| byId("rubric-eyebrow").textContent = `Rubric ${summary.rubric_idx ?? record.rubric_idx} · shared by this judge path`; |
| byId("parsed-rubric-size").textContent = `${words(parsed).toLocaleString()} words`; |
| byId("parsed-rubric-details").hidden = parsed === generated; |
| resetClamp("rubric-text", "rubric-toggle", "Show full rubric", "Collapse rubric"); |
| } |
|
|
| function resetClamp(contentId, buttonId, collapsedLabel, expandedLabel) { |
| const content = byId(contentId); |
| const button = byId(buttonId); |
| content.classList.add("is-clamped"); |
| button.setAttribute("aria-expanded", "false"); |
| button.textContent = collapsedLabel; |
| button.dataset.collapsedLabel = collapsedLabel; |
| button.dataset.expandedLabel = expandedLabel; |
| } |
|
|
| function renderRoundTrack(rounds) { |
| const track = byId("round-track"); |
| track.replaceChildren(); |
| rounds.forEach((round, index) => { |
| const button = document.createElement("button"); |
| const tie = Number(round.selection_weight || 0) > 0; |
| button.type = "button"; |
| button.className = `round-button${index === state.activeRound ? " active" : ""}${tie ? " tie" : ""}`; |
| button.setAttribute("role", "option"); |
| button.setAttribute("aria-selected", String(index === state.activeRound)); |
| button.dataset.round = String(index); |
| const top = document.createElement("div"); |
| top.className = "round-button-top"; |
| const label = document.createElement("span"); |
| label.textContent = `Round ${round.round ?? index}`; |
| const mark = document.createElement("span"); |
| mark.className = "round-button-mark"; |
| mark.textContent = round.selected_final ? "returned" : tie ? "top" : ""; |
| top.append(label, mark); |
| const scores = document.createElement("div"); |
| scores.className = "round-button-scores"; |
| const proxy = document.createElement("span"); |
| proxy.textContent = `P ${formatScore(round.proxy_score, 2)}`; |
| const gold = document.createElement("span"); |
| gold.textContent = `G ${formatScore(round.gold_score, 2)}`; |
| scores.append(proxy, gold); |
| const delta = index ? Number(round.gold_score) - Number(rounds[index - 1].gold_score) : 0; |
| const change = document.createElement("div"); |
| change.className = `round-button-delta ${deltaClass(delta)}`.trim(); |
| change.textContent = index ? `${formatSigned(delta, 2)} gold` : "initial state"; |
| button.append(top, scores, change); |
| button.addEventListener("click", () => setActiveRound(index)); |
| track.appendChild(button); |
| }); |
| updateRoundControls(rounds.length); |
| } |
|
|
| function updateRoundControls(length) { |
| byId("previous-round").disabled = state.activeRound <= 0; |
| byId("next-round").disabled = state.activeRound >= length - 1; |
| } |
|
|
| function setActiveRound(index) { |
| const rounds = state.payload?.rounds || []; |
| if (!rounds.length) return; |
| state.activeRound = Math.max(0, Math.min(index, rounds.length - 1)); |
| renderRoundTrack(rounds); |
| renderRoundDetail(rounds); |
| renderHistory(rounds); |
| } |
|
|
| function roundBadges(round) { |
| const fragment = document.createDocumentFragment(); |
| if (round.selected_final) fragment.appendChild(badge("Returned representative", "selected")); |
| if (Number(round.selection_weight || 0) > 0) { |
| const ties = (state.payload?.rounds || []).filter((item) => Number(item.selection_weight || 0) > 0).length; |
| fragment.appendChild(badge(ties > 1 ? `Reward weight ${formatScore(round.selection_weight, 3)}` : "Top proxy round", ties > 1 ? "tie" : "selected")); |
| } |
| if (!round.proxy_valid) fragment.appendChild(badge("Invalid proxy output", "invalid")); |
| return fragment; |
| } |
|
|
| function responseScores(round, previous = null) { |
| const container = document.createElement("div"); |
| container.className = "response-scores"; |
| const proxy = document.createElement("div"); |
| proxy.className = "response-score proxy"; |
| proxy.append(Object.assign(document.createElement("span"), { textContent: "Proxy" }), Object.assign(document.createElement("strong"), { textContent: formatScore(round.proxy_score) })); |
| const gold = document.createElement("div"); |
| gold.className = "response-score gold"; |
| gold.append(Object.assign(document.createElement("span"), { textContent: "Gold" }), Object.assign(document.createElement("strong"), { textContent: formatScore(round.gold_score) })); |
| const delta = previous && finite(round.gold_score) && finite(previous.gold_score) ? Number(round.gold_score) - Number(previous.gold_score) : 0; |
| const change = document.createElement("div"); |
| change.className = `response-score ${previous ? deltaClass(delta) : ""}`.trim(); |
| change.append(Object.assign(document.createElement("span"), { textContent: previous ? "Gold vs prior" : "Gold change" }), Object.assign(document.createElement("strong"), { textContent: previous ? formatSigned(delta) : "Initial" })); |
| container.append(proxy, gold, change); |
| return container; |
| } |
|
|
| function renderRoundDetail(rounds) { |
| const index = state.activeRound; |
| const current = rounds[index]; |
| const next = rounds[index + 1] || null; |
| if (!current) return; |
|
|
| const feedback = current.feedback_used || current.feedback || FALLBACK_FEEDBACK; |
| const fallback = Boolean(current.feedback_fallback) || (!current.feedback && !current.proxy_valid); |
| byId("feedback-eyebrow").textContent = fallback ? "Fallback feedback after invalid judge output" : `Judge feedback for round ${current.round ?? index}`; |
| byId("feedback-title").textContent = next ? `Instruction used to generate round ${next.round ?? index + 1}` : "Terminal feedback (no later revision)"; |
| byId("feedback-text").textContent = feedback; |
| byId("feedback-note").textContent = next |
| ? "The candidate generator also received the full earlier candidate, feedback, and proxy-score history. Gold rewards were hidden from it." |
| : "This feedback was scored and logged, but the configured round limit ended before another candidate was generated."; |
| byId("feedback-note").className = `feedback-note${fallback ? " fallback" : ""}`; |
| const pills = byId("feedback-score-pills"); |
| pills.replaceChildren( |
| scorePill("Current proxy", formatScore(current.proxy_score), "proxy"), |
| scorePill("Current gold", formatScore(current.gold_score), "gold"), |
| ); |
| if (next) { |
| pills.append( |
| scorePill("Next proxy change", finite(current.proxy_score) && finite(next.proxy_score) ? formatSigned(Number(next.proxy_score) - Number(current.proxy_score)) : "N/A", "proxy"), |
| scorePill("Next gold change", formatSigned(Number(next.gold_score) - Number(current.gold_score)), "gold"), |
| ); |
| } |
|
|
| byId("current-response-title").textContent = `Round ${current.round ?? index}`; |
| byId("current-badges").replaceChildren(roundBadges(current)); |
| const currentScores = responseScores(current, rounds[index - 1] || null); |
| currentScores.id = "current-scores"; |
| document.querySelector(".current-pane .response-scores")?.replaceWith(currentScores); |
| renderStructured(byId("current-response"), current.candidate || current.response || ""); |
|
|
| const nextPane = document.querySelector(".next-pane"); |
| if (next) { |
| nextPane.classList.remove("empty"); |
| byId("next-response-title").textContent = `Round ${next.round ?? index + 1}`; |
| byId("next-badges").replaceChildren(roundBadges(next)); |
| const nextScores = responseScores(next, current); |
| nextScores.id = "next-scores"; |
| document.querySelector(".next-pane .response-scores")?.replaceWith(nextScores); |
| renderStructured(byId("next-response"), next.candidate || next.response || ""); |
| } else { |
| nextPane.classList.add("empty"); |
| byId("next-response-title").textContent = "No subsequent round"; |
| byId("next-badges").replaceChildren(); |
| const emptyScores = document.createElement("div"); |
| emptyScores.className = "response-scores"; |
| emptyScores.id = "next-scores"; |
| document.querySelector(".next-pane .response-scores")?.replaceWith(emptyScores); |
| renderStructured(byId("next-response"), "The configured round limit was reached, so this feedback did not produce another logged response."); |
| } |
| updateRoundControls(rounds.length); |
| } |
|
|
| function renderHistory(rounds) { |
| const body = byId("history-body"); |
| body.replaceChildren(); |
| rounds.forEach((round, index) => { |
| const row = document.createElement("tr"); |
| if (index === state.activeRound) row.className = "active"; |
| row.tabIndex = 0; |
| row.addEventListener("click", () => setActiveRound(index)); |
| row.addEventListener("keydown", (event) => { |
| if (event.key === "Enter" || event.key === " ") setActiveRound(index); |
| }); |
| const roundCell = document.createElement("td"); |
| const roundButton = document.createElement("button"); |
| roundButton.type = "button"; |
| roundButton.textContent = `Round ${round.round ?? index}`; |
| roundCell.appendChild(roundButton); |
| const proxy = document.createElement("td"); |
| proxy.className = "numeric proxy-value"; |
| proxy.textContent = formatScore(round.proxy_score); |
| const gold = document.createElement("td"); |
| gold.className = "numeric gold-value"; |
| gold.textContent = formatScore(round.gold_score); |
| const changeValue = index ? Number(round.gold_score) - Number(rounds[index - 1].gold_score) : 0; |
| const change = document.createElement("td"); |
| change.className = `numeric ${deltaClass(changeValue)}`.trim(); |
| change.textContent = index ? formatSigned(changeValue) : "Initial"; |
| const decision = document.createElement("td"); |
| decision.textContent = [round.decision, round.preference].filter(Boolean).join(" · ") || "N/A"; |
| const selection = document.createElement("td"); |
| selection.textContent = round.selected_final |
| ? `Returned${Number(round.selection_weight || 0) < 1 ? ` · weight ${formatScore(round.selection_weight, 3)}` : ""}` |
| : Number(round.selection_weight || 0) > 0 |
| ? `Tie weight ${formatScore(round.selection_weight, 3)}` |
| : ""; |
| const feedback = document.createElement("td"); |
| feedback.className = "feedback-preview"; |
| feedback.textContent = round.feedback_used || round.feedback || (round.proxy_valid ? "" : FALLBACK_FEEDBACK); |
| row.append(roundCell, proxy, gold, change, decision, selection, feedback); |
| body.appendChild(row); |
| }); |
| } |
|
|
| function toggleClamp(button, content) { |
| const expanded = button.getAttribute("aria-expanded") === "true"; |
| button.setAttribute("aria-expanded", String(!expanded)); |
| content.classList.toggle("is-clamped", expanded); |
| button.textContent = expanded ? button.dataset.collapsedLabel : button.dataset.expandedLabel; |
| } |
|
|
| function switchView(view) { |
| state.view = view; |
| document.querySelectorAll(".tab").forEach((tab) => { |
| const active = tab.dataset.view === view; |
| tab.classList.toggle("active", active); |
| tab.setAttribute("aria-selected", String(active)); |
| }); |
| byId("trajectory-view").hidden = view !== "trajectory"; |
| byId("overview-view").hidden = view !== "overview"; |
| if (view === "overview") renderOverview(); |
| } |
|
|
| function summaryMetric(summary, ...names) { |
| for (const name of names) if (summary && summary[name] !== undefined) return summary[name]; |
| return null; |
| } |
|
|
| function renderOverview() { |
| const summary = state.index.summary || {}; |
| const records = state.trajectories; |
| const initial = summaryMetric(summary, "initial_gold_mean") ?? mean(records.map((row) => row.initial_gold)); |
| const selected = summaryMetric(summary, "selected_gold_mean") ?? mean(records.map((row) => row.selected_gold)); |
| const last = summaryMetric(summary, "last_gold_mean") ?? mean(records.map((row) => row.last_gold)); |
| const best = summaryMetric(summary, "best_gold_mean", "oracle_best_gold_mean") ?? mean(records.map((row) => row.best_gold)); |
| const spearman = summaryMetric(summary, "proxy_gold_spearman_mean", "mean_proxy_gold_spearman") ?? mean(records.map((row) => row.proxy_gold_spearman)); |
| const overview = byId("overview-summary"); |
| overview.replaceChildren( |
| metricCell("Initial gold mean", formatScore(initial), `${records.length} paths`, "gold"), |
| metricCell("Selected gold mean", formatScore(selected), "tie-weighted", "gold"), |
| metricCell("Selected change", formatSigned(finite(selected) && finite(initial) ? Number(selected) - Number(initial) : null), "vs initial", deltaClass(finite(selected) && finite(initial) ? Number(selected) - Number(initial) : null)), |
| metricCell("Last gold mean", formatScore(last), "round 8", "gold"), |
| metricCell("Oracle best mean", formatScore(best), "best state per path", "gold"), |
| metricCell("Mean rank agreement", formatScore(spearman), "trajectory Spearman", "proxy"), |
| ); |
| renderOutcomeChart(summary); |
| renderRoundChart(summary.per_round || summary.rounds || []); |
| renderCheckpointTable(); |
| } |
|
|
| function renderOutcomeChart(summary) { |
| const supplied = summary.outcome_counts || {}; |
| const counts = { |
| improved: Number(supplied.improved ?? state.trajectories.filter((row) => row.outcome === "improved").length), |
| unchanged: Number(supplied.unchanged ?? state.trajectories.filter((row) => row.outcome === "unchanged").length), |
| worsened: Number(supplied.worsened ?? state.trajectories.filter((row) => row.outcome === "worsened").length), |
| }; |
| const total = Object.values(counts).reduce((sum, value) => sum + value, 0) || 1; |
| const container = byId("outcome-chart"); |
| container.replaceChildren(); |
| const bar = document.createElement("div"); |
| bar.className = "outcome-bar"; |
| Object.entries(counts).forEach(([name, count]) => { |
| const segment = document.createElement("div"); |
| segment.className = `outcome-segment ${name}`; |
| segment.style.width = `${(count / total) * 100}%`; |
| const label = document.createElement("span"); |
| label.textContent = `${name} ${count}`; |
| segment.appendChild(label); |
| segment.title = `${name}: ${count} (${formatPercent(count / total)})`; |
| bar.appendChild(segment); |
| }); |
| const legend = document.createElement("div"); |
| legend.className = "outcome-legend"; |
| Object.entries(counts).forEach(([name, count]) => { |
| const item = document.createElement("span"); |
| item.append(document.createTextNode(`${name[0].toUpperCase()}${name.slice(1)}`)); |
| const strong = document.createElement("strong"); |
| strong.textContent = `${count} · ${formatPercent(count / total)}`; |
| item.appendChild(strong); |
| legend.appendChild(item); |
| }); |
| container.append(bar, legend); |
| } |
|
|
| function svgElement(name, attributes = {}) { |
| const element = document.createElementNS("http://www.w3.org/2000/svg", name); |
| Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, String(value))); |
| return element; |
| } |
|
|
| function renderRoundChart(rows) { |
| const container = byId("round-chart"); |
| container.replaceChildren(); |
| if (!rows.length) { |
| const empty = document.createElement("p"); |
| empty.textContent = "Per-round aggregates were not exported."; |
| container.appendChild(empty); |
| return; |
| } |
| const width = 1000; |
| const height = 310; |
| const left = 54; |
| const right = 24; |
| const top = 20; |
| const bottom = 42; |
| const plotWidth = width - left - right; |
| const plotHeight = height - top - bottom; |
| const x = (index) => left + (rows.length === 1 ? 0 : (index / (rows.length - 1)) * plotWidth); |
| const y = (value) => top + (1 - Math.max(0, Math.min(1, Number(value)))) * plotHeight; |
| const svg = svgElement("svg", { viewBox: `0 0 ${width} ${height}`, role: "img", "aria-label": "Mean proxy and gold scores by feedback round" }); |
| [0, 0.25, 0.5, 0.75, 1].forEach((value) => { |
| svg.appendChild(svgElement("line", { x1: left, y1: y(value), x2: width - right, y2: y(value), class: "chart-grid" })); |
| const label = svgElement("text", { x: left - 10, y: y(value) + 4, "text-anchor": "end", class: "chart-axis-label" }); |
| label.textContent = value.toFixed(2); |
| svg.appendChild(label); |
| }); |
| rows.forEach((row, index) => { |
| const label = svgElement("text", { x: x(index), y: height - 16, "text-anchor": "middle", class: "chart-axis-label" }); |
| label.textContent = `Round ${row.round ?? index}`; |
| svg.appendChild(label); |
| }); |
| const proxyValues = rows.map((row) => number(row.proxy_mean ?? row.mean_proxy)); |
| const goldValues = rows.map((row) => number(row.gold_mean ?? row.mean_gold)); |
| const makePath = (values) => values.map((value, index) => `${index ? "L" : "M"} ${x(index)} ${y(value)}`).join(" "); |
| if (proxyValues.every(finite)) svg.appendChild(svgElement("path", { d: makePath(proxyValues), class: "chart-proxy" })); |
| if (goldValues.every(finite)) svg.appendChild(svgElement("path", { d: makePath(goldValues), class: "chart-gold" })); |
| proxyValues.forEach((value, index) => { |
| if (!finite(value)) return; |
| const dot = svgElement("circle", { cx: x(index), cy: y(value), r: 4, class: "chart-dot-proxy" }); |
| const title = svgElement("title"); |
| title.textContent = `Round ${rows[index].round ?? index}: proxy ${formatScore(value)}`; |
| dot.appendChild(title); |
| svg.appendChild(dot); |
| }); |
| goldValues.forEach((value, index) => { |
| if (!finite(value)) return; |
| const dot = svgElement("circle", { cx: x(index), cy: y(value), r: 4, class: "chart-dot-gold" }); |
| const title = svgElement("title"); |
| title.textContent = `Round ${rows[index].round ?? index}: gold ${formatScore(value)}`; |
| dot.appendChild(title); |
| svg.appendChild(dot); |
| }); |
| container.appendChild(svg); |
| } |
|
|
| function checkpointStatistics(checkpoint) { |
| const records = state.trajectories.filter((record) => checkpointId(record) === checkpoint.id); |
| return { |
| count: records.length, |
| initial: checkpoint.initial_gold_mean ?? mean(records.map((row) => row.initial_gold)), |
| selected: checkpoint.selected_gold_mean ?? mean(records.map((row) => row.selected_gold)), |
| best: checkpoint.best_gold_mean ?? checkpoint.oracle_best_gold_mean ?? mean(records.map((row) => row.best_gold)), |
| }; |
| } |
|
|
| function renderCheckpointTable() { |
| const body = byId("checkpoint-body"); |
| body.replaceChildren(); |
| state.checkpoints.forEach((checkpoint) => { |
| const stats = checkpointStatistics(checkpoint); |
| const row = document.createElement("tr"); |
| row.addEventListener("click", () => { |
| byId("checkpoint-select").value = checkpoint.id; |
| applyTrajectoryFilter(); |
| switchView("trajectory"); |
| loadSelectedTrajectory(); |
| }); |
| const cells = [ |
| checkpoint.step, |
| checkpoint.title || checkpoint.context_label || checkpoint.uid, |
| stats.count, |
| formatScore(stats.initial), |
| formatScore(stats.selected), |
| formatScore(stats.best), |
| ]; |
| cells.forEach((value, index) => { |
| const cell = document.createElement("td"); |
| cell.textContent = String(value ?? "N/A"); |
| if (index >= 2) cell.className = "numeric"; |
| row.appendChild(cell); |
| }); |
| body.appendChild(row); |
| }); |
| } |
|
|
| function moveTrajectory(offset) { |
| const current = state.visible.findIndex((record) => trajectoryId(record) === byId("trajectory-select").value); |
| const next = Math.max(0, Math.min(current + offset, state.visible.length - 1)); |
| if (next === current || !state.visible[next]) return; |
| byId("trajectory-select").value = trajectoryId(state.visible[next]); |
| loadSelectedTrajectory(); |
| } |
|
|
| function refreshIcons() { |
| if (window.lucide && typeof window.lucide.createIcons === "function") window.lucide.createIcons(); |
| } |
|
|
| function bindEvents() { |
| document.querySelectorAll(".tab").forEach((tab) => tab.addEventListener("click", () => switchView(tab.dataset.view))); |
| byId("checkpoint-select").addEventListener("change", () => { |
| applyTrajectoryFilter(); |
| loadSelectedTrajectory(); |
| }); |
| byId("outcome-select").addEventListener("change", () => { |
| applyTrajectoryFilter(); |
| loadSelectedTrajectory(); |
| }); |
| byId("trajectory-select").addEventListener("change", loadSelectedTrajectory); |
| byId("previous-trajectory").addEventListener("click", () => moveTrajectory(-1)); |
| byId("next-trajectory").addEventListener("click", () => moveTrajectory(1)); |
| byId("previous-round").addEventListener("click", () => setActiveRound(state.activeRound - 1)); |
| byId("next-round").addEventListener("click", () => setActiveRound(state.activeRound + 1)); |
| byId("context-toggle").addEventListener("click", () => toggleClamp(byId("context-toggle"), byId("context-text"))); |
| byId("rubric-toggle").addEventListener("click", () => toggleClamp(byId("rubric-toggle"), byId("rubric-text"))); |
| document.addEventListener("keydown", (event) => { |
| if (state.view !== "trajectory" || ["INPUT", "SELECT", "TEXTAREA", "SUMMARY"].includes(document.activeElement?.tagName)) return; |
| if (event.key === "ArrowLeft") setActiveRound(state.activeRound - 1); |
| if (event.key === "ArrowRight") setActiveRound(state.activeRound + 1); |
| }); |
| } |
|
|
| async function initialize() { |
| bindEvents(); |
| refreshIcons(); |
| try { |
| const response = await fetch("data/index.json"); |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); |
| state.index = await response.json(); |
| state.trajectories = state.index.trajectories || state.index.records || []; |
| if (!state.trajectories.length) throw new Error("the snapshot contains no trajectories"); |
| byId("page-title").textContent = state.index.title || "Feedback trajectories"; |
| byId("strategy-label").textContent = state.index.strategy || "self_reflection_best"; |
| const counts = state.index.counts || {}; |
| byId("dataset-count").textContent = `${counts.trajectories || state.trajectories.length} trajectories · ${counts.checkpoints || new Set(state.trajectories.map(checkpointId)).size} checkpoint examples`; |
| buildCheckpointIndex(); |
| populateCheckpointSelect(); |
| applyTrajectoryFilter(); |
| renderOverview(); |
| await loadSelectedTrajectory(); |
| } catch (error) { |
| showError(`Could not load the feedback snapshot (${error.message}).`); |
| } |
| } |
|
|
| initialize(); |
|
|