Butanium's picture
Static DuckDB-wasm sample explorer (full 176K corpus)
763e57f verified
Raw
History Blame Contribute Delete
11.9 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Persona-negation sample explorer</title>
<style>
:root {
--bg: #fbfbfa; --fg: #1a1a1a; --sub: #6b6b6b; --card: #ffffff;
--border: #e3e3e0; --accent: #3b5bdb; --link: #1a56db;
}
@media (prefers-color-scheme: dark) {
:root { --bg: #17181a; --fg: #e6e6e6; --sub: #9a9a9a; --card: #222427;
--border: #333; --accent: #748ffc; --link: #91a7ff; }
}
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, sans-serif; background: var(--bg);
color: var(--fg); line-height: 1.5; }
header { padding: 22px 24px 8px; max-width: 1100px; margin: 0 auto; }
h1 { margin: 0 0 4px; font-size: 1.5rem; }
header p { margin: 4px 0; color: var(--sub); font-size: 0.92rem; max-width: 68ch; }
header a { color: var(--link); }
.wrap { max-width: 1100px; margin: 0 auto; padding: 0 24px 60px; }
.tabs { display: flex; gap: 4px; border-bottom: 2px solid var(--border); margin: 16px 0 0; }
.tab-btn { border: none; background: none; color: var(--sub); font-size: 0.95rem; padding: 10px 16px;
cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px; }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.panel { display: none; padding-top: 18px; }
.panel.active { display: block; }
.controls { display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: flex-end; }
.ctrl { display: flex; flex-direction: column; gap: 3px; }
.ctrl label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sub); }
select, input[type=number] { background: var(--card); color: var(--fg); border: 1px solid var(--border);
border-radius: 6px; padding: 5px 8px; font-size: 0.88rem; min-width: 90px; }
.rangebox { display: flex; align-items: center; gap: 5px; }
.rangebox input { width: 58px; }
button.draw { background: var(--accent); color: #fff; border: none; border-radius: 6px;
padding: 8px 16px; font-size: 0.9rem; cursor: pointer; font-weight: 600; }
button.draw:disabled { opacity: 0.5; cursor: default; }
.count { color: var(--sub); font-size: 0.88rem; margin: 14px 0 8px; font-variant-numeric: tabular-nums; }
.cards { display: flex; flex-direction: column; gap: 10px; }
.card { background: var(--card); border: 1px solid var(--border); border-left-width: 5px;
border-radius: 6px; padding: 10px 14px; }
.card .meta { font-size: 0.76rem; color: var(--sub); margin-bottom: 5px; font-variant-numeric: tabular-nums; }
.card .prompt { font-size: 0.82rem; font-style: italic; color: var(--sub); margin-bottom: 6px; }
.card .text { white-space: pre-wrap; font-size: 0.9rem; cursor: pointer; }
.card .text .more { color: var(--link); font-size: 0.82rem; }
#status { padding: 40px 0; color: var(--sub); font-size: 0.95rem; }
.spin { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--border);
border-top-color: var(--accent); border-radius: 50%; animation: s 0.8s linear infinite;
vertical-align: middle; margin-right: 8px; }
@keyframes s { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<header>
<h1>Persona-negation sample explorer</h1>
<p>Raw model completions behind the v3.1 report. Amplifying or negating a personality-trait
LoRA (the <code>weight</code> control) shifts what a model says about itself and how it handles
unsafe requests. Filter, then draw random samples. Full corpus — all 176K samples.</p>
<p>Judgments dataset: <a href="https://huggingface.co/datasets/Butanium/persona-negation-judgments">Butanium/persona-negation-judgments</a></p>
</header>
<div class="wrap">
<div id="status"><span class="spin"></span>Loading DuckDB + data…</div>
<div class="tabs" id="tabs" hidden></div>
<div id="panels" hidden></div>
</div>
<script type="module">
import * as duckdb from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm';
const EXP_COLORS = { human_specific:"#d62728", ai_specific:"#2ca02c",
human_specific_and_ai_specific:"#9467bd", ambiguous:"#ff7f0e", none:"#7f7f7f" };
const COMPLIANCE_COLORS = { refused:"#43a047", partial_vague:"#fdd835",
partial_disclaimer:"#ff9800", complied:"#e53935" };
const TABS = [
{ key:"v3", title:"Identity", file:"v3_explorer.parquet",
colorBy:"v3_experience_type", colorMap:EXP_COLORS,
cats:[["model","Model"],["organism","Organism"],["weight","Weight"],
["v3_experience_type","Experience type"],["v3_ai_self_reference","AI self-ref"],
["v3_biographical_identity","Biographical"]],
nums:[["coherence","Coherence"]],
meta:["model","organism","weight","v3_experience_type","v3_ai_self_reference","v3_biographical_identity","coherence"] },
{ key:"sf", title:"Safety", file:"safety_explorer.parquet",
colorBy:"compliance", colorMap:COMPLIANCE_COLORS,
cats:[["model","Model"],["organism","Organism"],["weight","Weight"],
["layer_range","Layer range"],["compliance","Compliance"],["prompt_short","Prompt"]],
nums:[["harmfulness","Harmfulness"],["coherence","Coherence"]],
meta:["model","organism","weight","layer_range","compliance","harmfulness","coherence"] },
{ key:"sp", title:"System prompt", file:"sysprompt_explorer.parquet",
colorBy:"v3_experience_type", colorMap:EXP_COLORS,
cats:[["model","Model"],["organism","Organism"],["weight","Weight"],
["sysprompt","System prompt"],["v3_experience_type","Experience type"]],
nums:[["coherence","Coherence"]],
meta:["model","organism","weight","sysprompt","v3_experience_type","coherence"] },
];
const esc = s => String(s).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
let conn;
async function initDB() {
const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
const worker = await duckdb.createWorker(bundle.mainWorker);
const db = new duckdb.AsyncDuckDB(new duckdb.ConsoleLogger(), worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
for (const t of TABS) {
const url = new URL(t.file, location.href).href;
await db.registerFileURL(t.file, url, duckdb.DuckDBDataProtocol.HTTP, false);
}
conn = await db.connect();
}
const rows = async sql => (await conn.query(sql)).toArray().map(r => r.toJSON());
function whereClause(cfg) {
const parts = [];
for (const [col] of cfg.cats) {
const v = document.getElementById(`${cfg.key}-${col}`).value;
if (v !== "All") parts.push(`CAST(${col} AS VARCHAR) = '${v.replace(/'/g,"''")}'`);
}
for (const [col] of cfg.nums) {
const lo = document.getElementById(`${cfg.key}-${col}-lo`).value;
const hi = document.getElementById(`${cfg.key}-${col}-hi`).value;
parts.push(`(${col} IS NULL OR (${col} >= ${lo} AND ${col} <= ${hi}))`);
}
return parts.length ? "WHERE " + parts.join(" AND ") : "";
}
function renderCards(cfg, data) {
if (!data.length) return "<p style='color:var(--sub)'>No samples match these filters.</p>";
return `<div class="cards">${data.map(r => {
const color = cfg.colorMap[r[cfg.colorBy]] || "#888";
const meta = cfg.meta.filter(c => r[c] != null && r[c] !== "")
.map(c => `${c.replace(/^v3_/,"").replace(/_/g," ")}=${esc(r[c])}`).join(" · ");
const prompt = r.prompt_text || r.prompt_short || "";
const text = String(r.completion_text || "");
const long = text.length > 500;
const short = esc(text.slice(0,500)) + (long ? "…" : "");
const body = long
? `<div class="text" data-full="${esc(text)}" data-short="${short}">${short} <span class="more">[expand]</span></div>`
: `<div class="text">${esc(text)}</div>`;
return `<div class="card" style="border-left-color:${color}">
<div class="meta">${meta}</div>
${prompt ? `<div class="prompt">Prompt: ${esc(prompt)}</div>` : ""}
${body}</div>`;
}).join("")}</div>`;
}
async function draw(cfg) {
const btn = document.getElementById(`${cfg.key}-draw`);
btn.disabled = true; btn.textContent = "Drawing…";
const where = whereClause(cfg);
const n = document.getElementById(`${cfg.key}-n`).value;
const [{c}] = await rows(`SELECT COUNT(*)::INT AS c FROM '${cfg.file}' ${where}`);
const data = await rows(`SELECT * FROM '${cfg.file}' ${where} USING SAMPLE ${n} ROWS`);
document.getElementById(`${cfg.key}-count`).textContent =
`${c.toLocaleString()} samples match — showing ${data.length}.`;
document.getElementById(`${cfg.key}-out`).innerHTML = renderCards(cfg, data);
btn.disabled = false; btn.textContent = "🎲 Draw random samples";
}
async function buildTab(cfg) {
const distinct = {};
for (const [col] of cfg.cats) {
const vals = await rows(`SELECT DISTINCT ${col} AS v FROM '${cfg.file}' WHERE ${col} IS NOT NULL ORDER BY v`);
distinct[col] = vals.map(r => r.v);
}
const ranges = {};
for (const [col] of cfg.nums) {
const [{lo, hi}] = await rows(`SELECT MIN(${col})::DOUBLE AS lo, MAX(${col})::DOUBLE AS hi FROM '${cfg.file}'`);
ranges[col] = [lo ?? 0, hi ?? 5];
}
const catHtml = cfg.cats.map(([col, label]) => `
<div class="ctrl"><label>${label}</label>
<select id="${cfg.key}-${col}">
<option>All</option>${distinct[col].map(v => `<option>${esc(v)}</option>`).join("")}
</select></div>`).join("");
const numHtml = cfg.nums.map(([col, label]) => {
const [lo, hi] = ranges[col];
return `<div class="ctrl"><label>${label}</label><div class="rangebox">
<input type="number" id="${cfg.key}-${col}-lo" value="${lo}" min="${lo}" max="${hi}" step="1">
<span>–</span>
<input type="number" id="${cfg.key}-${col}-hi" value="${hi}" min="${lo}" max="${hi}" step="1">
</div></div>`; }).join("");
const panel = document.createElement("div");
panel.className = "panel"; panel.id = `panel-${cfg.key}`;
panel.innerHTML = `<div class="controls">${catHtml}${numHtml}
<div class="ctrl"><label>Draw</label>
<input type="number" id="${cfg.key}-n" value="5" min="1" max="30" step="1"></div>
<button class="draw" id="${cfg.key}-draw">🎲 Draw random samples</button></div>
<div class="count" id="${cfg.key}-count"></div>
<div id="${cfg.key}-out"></div>`;
document.getElementById("panels").appendChild(panel);
document.getElementById(`${cfg.key}-draw`).onclick = () => draw(cfg);
panel.querySelector("#" + `${cfg.key}-out`).addEventListener("click", e => {
const t = e.target.closest(".text"); if (!t || !t.dataset.full) return;
const expanded = t.classList.toggle("exp");
t.innerHTML = expanded ? esc(t.dataset.full) + ' <span class="more">[collapse]</span>'
: t.dataset.short + ' <span class="more">[expand]</span>';
});
}
(async () => {
try {
await initDB();
const tabsEl = document.getElementById("tabs");
for (const [i, cfg] of TABS.entries()) {
const b = document.createElement("button");
b.className = "tab-btn" + (i === 0 ? " active" : "");
b.textContent = cfg.title;
b.onclick = () => {
document.querySelectorAll(".tab-btn").forEach(x => x.classList.remove("active"));
document.querySelectorAll(".panel").forEach(x => x.classList.remove("active"));
b.classList.add("active");
document.getElementById(`panel-${cfg.key}`).classList.add("active");
};
tabsEl.appendChild(b);
await buildTab(cfg);
}
document.querySelector(".panel").classList.add("active");
document.getElementById("status").hidden = true;
tabsEl.hidden = false;
document.getElementById("panels").hidden = false;
for (const cfg of TABS) draw(cfg);
} catch (e) {
document.getElementById("status").innerHTML = `<b>Failed to load:</b> ${esc(e.message)}`;
throw e;
}
})();
</script>
</body>
</html>