ICAExplorer / server /static /component.html
sida's picture
Add Umami analytics
83ebad7
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Component Examples - GPT-2 Explorer</title>
<style>
:root {
--bg: #f6f7f9;
--panel: #fff;
--text: #151922;
--muted: #647084;
--border: #cbd3df;
--accent: #1f6feb;
--positive: #b91c1c;
--negative: #1d4ed8;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--shadow: 0 1px 2px rgb(20 25 34 / .08), 0 10px 30px rgb(20 25 34 / .06);
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
header {
position: sticky;
top: 0;
z-index: 5;
background: var(--panel);
border-bottom: 1px solid var(--border);
box-shadow: var(--shadow);
padding: 12px 18px;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
}
h1 { margin: 0; font-size: 19px; letter-spacing: 0; }
.nav {
display: flex;
align-items: center;
gap: 14px;
}
a {
color: var(--accent);
font-weight: 750;
text-decoration: none;
}
a:hover { text-decoration: underline; }
main {
width: 100%;
margin: 0;
padding: 12px;
}
.summary {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 14px;
margin-bottom: 12px;
color: var(--muted);
font-size: 12px;
font-weight: 750;
}
.band {
margin-bottom: 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--shadow);
overflow: hidden;
}
.band-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
margin: 0;
padding: 9px 12px;
border-bottom: 1px solid #e5eaf2;
border-left: 0;
border-right: 0;
border-top: 0;
font-size: 14px;
font-weight: 850;
letter-spacing: 0;
background: #fbfcfe;
color: var(--text);
text-align: left;
cursor: pointer;
font-family: inherit;
}
.band-toggle:hover { background: #f1f5f9; }
.band-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.band-title { min-width: 0; overflow-wrap: anywhere; }
.band-state { color: var(--muted); font-size: 12px; white-space: nowrap; }
.band.collapsed .examples { display: none; }
.examples {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 0;
}
.example {
min-width: 0;
border-right: 1px solid #e5eaf2;
border-bottom: 1px solid #e5eaf2;
padding: 10px 12px;
}
.example-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 6px;
color: var(--muted);
font-size: 12px;
font-weight: 750;
}
.example-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.doc-link { color: var(--accent); text-decoration: none; font-weight: 850; }
.doc-link:hover { text-decoration: underline; }
.score {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.score.positive { color: var(--positive); }
.score.negative { color: var(--negative); }
.token {
display: inline-block;
margin-bottom: 6px;
padding: 2px 6px;
border: 1px solid var(--border);
border-radius: 5px;
background: #f8fafc;
font-weight: 850;
font-family: var(--mono);
font-size: 12px;
overflow-wrap: anywhere;
}
.token.positive { color: var(--positive); border-color: #fecaca; background: #fff1f2; }
.token.negative { color: var(--negative); border-color: #bfdbfe; background: #eff6ff; }
.context {
margin: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
font-family: var(--mono);
font-size: 12px;
line-height: 1.55;
}
.context-token {
border-radius: 3px;
padding: 0 1px;
}
.context-token.target {
box-shadow: inset 0 -2px 0 currentColor;
font-weight: 850;
}
.context-token.positive { color: var(--positive); }
.context-token.negative { color: var(--negative); }
.empty, .error {
border: 1px dashed var(--border);
border-radius: 8px;
padding: 18px;
color: var(--muted);
background: #fff;
}
.error { color: #a41414; border-color: #f0b9b9; background: #fff7f7; }
@media (max-width: 760px) {
main { padding: 8px; }
header { padding: 10px 12px; }
.top { align-items: flex-start; flex-direction: column; }
.examples { grid-template-columns: 1fr; }
}
</style>
<script defer src="https://analytics.liusida.com/umami/script.js" data-website-id="64322a37-ae7f-4635-ac78-8869ef79997b"></script>
</head>
<body>
<header>
<div class="top">
<h1 id="title">Component Examples</h1>
<nav class="nav" aria-label="Primary">
<a href="/">Explorer</a>
<a href="/sae-explorer">SAE Explorer</a>
<a href="/stats">Stats</a>
<a href="/annotate">Annotate</a>
<a href="/random-components">Random</a>
</nav>
</div>
</header>
<main>
<div id="summary" class="summary">Loading component examples...</div>
<div id="message" class="empty">Loading...</div>
<div id="content" hidden></div>
</main>
<script>
const els = {
title: document.getElementById("title"),
summary: document.getElementById("summary"),
message: document.getElementById("message"),
content: document.getElementById("content"),
};
async function api(path) {
const res = await fetch(path, { headers: { "content-type": "application/json" } });
if (!res.ok) {
let detail = res.statusText;
try { detail = (await res.json()).detail || detail; } catch {}
throw new Error(detail);
}
return res.json();
}
async function init() {
const params = new URLSearchParams(location.search);
const model = params.get("model") || "gpt2";
const layer = params.get("layer") || "";
const component = params.get("component") || "";
if (!layer || !component) {
showError("Missing layer or component.");
return;
}
try {
const query = new URLSearchParams({ model, layer, component });
const data = await api(`/api/component-examples?${query.toString()}`);
render(data);
} catch (err) {
showError(err.message);
}
}
function render(data) {
const bands = data.bands || [];
const total = bands.reduce((count, band) => count + (band.examples || []).length, 0);
const kurtosis = Number.isFinite(Number(data.excess_kurtosis)) ? `kurtosis ${Number(data.excess_kurtosis).toFixed(3)}` : "kurtosis n/a";
els.title.textContent = `${data.layer} C${data.component}`;
els.summary.textContent = `${data.model_name} - ${data.layer} component ${data.component} - ${kurtosis} - ${bands.length} bands - ${total} examples`;
if (!bands.length) {
els.message.hidden = false;
els.message.className = "empty";
els.message.textContent = "No examples found for this component.";
els.content.hidden = true;
return;
}
els.message.hidden = true;
els.content.hidden = false;
els.content.innerHTML = bands.map(renderBand).join("");
wireBandToggles();
}
function renderBand(band) {
const examples = band.examples || [];
const collapsed = shouldCollapseBand(band.region);
return `
<section class="band ${collapsed ? "collapsed" : ""}">
<button class="band-toggle" type="button" aria-expanded="${collapsed ? "false" : "true"}">
<span class="band-title">${escapeHtml(band.region)}</span>
<span class="band-state">${collapsed ? "show" : "hide"}</span>
</button>
<div class="examples">${examples.map(renderExample).join("")}</div>
</section>
`;
}
function wireBandToggles() {
els.content.querySelectorAll(".band-toggle").forEach(button => {
button.addEventListener("click", () => {
const band = button.closest(".band");
const collapsed = band.classList.toggle("collapsed");
button.setAttribute("aria-expanded", String(!collapsed));
const state = button.querySelector(".band-state");
if (state) state.textContent = collapsed ? "show" : "hide";
});
});
}
function shouldCollapseBand(region) {
const name = String(region || "").toLowerCase().replace(/[_-]+/g, " ");
return name.includes("opposite") || name.includes("near zero") || name.includes("nearzero");
}
function renderExample(example) {
const score = Number(example.source_score);
const scoreClass = score > 0 ? "positive" : score < 0 ? "negative" : "";
return `
<article class="example">
<div class="example-head">
<span class="example-meta"><span>#${escapeHtml(example.rank)}</span>${docPositionLink(example)}</span>
<span class="score ${scoreClass}">${formatScore(score)}</span>
</div>
<div class="token ${scoreClass}">${escapeHtml(visibleToken(example.token))}</div>
<p class="context">${renderContext(example)}</p>
</article>
`;
}
function docPositionLink(example) {
if (example.doc_id == null || Number(example.doc_id) < 0) {
return example.position == null ? "" : `<span>pos ${escapeHtml(example.position)}</span>`;
}
const query = new URLSearchParams({ model: state.model, doc_id: String(example.doc_id) });
if (example.position != null) query.set("position", String(example.position));
const target = visibleToken(example.token);
if (target) query.set("target", target);
const text = `doc ${example.doc_id}${example.position == null ? "" : ` · pos ${example.position}`}`;
return `<a class="doc-link" href="/context?${escapeAttr(query.toString())}" target="_blank" rel="noopener" title="Open full document context">${escapeHtml(text)}</a>`;
}
function renderContext(example) {
const tokens = Array.isArray(example.context_token_scores) ? example.context_token_scores : [];
if (!tokens.length) return escapeHtml(example.context_to_target || example.context || "");
const maxAbs = Math.max(
1e-9,
Number(example.context_score_max_abs || 0),
...tokens.map(token => Math.abs(Number(token.source_score || 0)))
);
return tokens.map(token => {
const score = Number(token.source_score || 0);
const alpha = tokenAlpha(score, maxAbs);
const color = alpha === 0 ? "transparent" : score > 0 ? `rgba(220,38,38,${alpha})` : `rgba(37,99,235,${alpha})`;
const signClass = score > 0 ? "positive" : score < 0 ? "negative" : "";
const title = `score=${formatScore(score)}`;
return `<span class="context-token ${token.is_target ? "target" : ""} ${signClass}" style="background:${color}" title="${escapeAttr(title)}">${escapeHtml(visibleContextToken(token.token))}</span>`;
}).join("");
}
function tokenAlpha(score, maxAbs) {
const magnitude = Math.abs(Number(score || 0));
if (magnitude < 1e-6) return 0;
return Math.min(0.55, 0.47 * Math.log1p(magnitude) / Math.log1p(maxAbs));
}
function visibleToken(value) {
const text = String(value || "");
if (text === " ") return "[space]";
if (text === "\n") return "[newline]";
if (text === "\f") return "\\f";
return text.replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\f/g, "\\f");
}
function visibleContextToken(value) {
return String(value || "").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\f/g, "\\f");
}
function formatScore(value) {
return Number.isFinite(value) ? value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "") : "n/a";
}
function showError(message) {
els.summary.textContent = "";
els.message.hidden = false;
els.message.className = "error";
els.message.textContent = message;
els.content.hidden = true;
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, char => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[char]));
}
function escapeAttr(value) {
return escapeHtml(value);
}
init();
</script>
</body>
</html>