slyfox / buddy.html
ArthurZ's picture
ArthurZ HF Staff
Strip duplicate page nav + add AI emotions sub-tab to /mirror (#6)
d207a98
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Slyfox · Buddy</title>
<style>
:root {
--bg: #0d1117; --panel: #161b22; --panel-2: #1c2128; --border: #30363d;
--text: #e6edf3; --muted: #8b949e; --accent: #2f81f7;
--ok: #3fb950; --warn: #d29922; --err: #f85149;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
* { box-sizing: border-box; }
html, body { background: var(--bg); color: var(--text); margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; }
header { display: flex; align-items: center; justify-content: space-between; padding: 14px 24px; border-bottom: 1px solid var(--border); }
header h1 { font-size: 16px; margin: 0; font-weight: 600; letter-spacing: 0.2px; }
header .nav a { color: var(--muted); margin-left: 18px; font-size: 14px; }
header .nav a.active { color: var(--text); }
main { max-width: 1100px; margin: 0 auto; padding: 24px; }
body.embed header { display: none; }
body.embed main { max-width: none; padding: 16px; }
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 18px 20px; margin-bottom: 18px; }
.panel h2 { margin: 0 0 12px; font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.panel h2 .hint { color: var(--muted); font-weight: 400; font-size: 12px; }
.panel p { color: var(--muted); font-size: 13px; line-height: 1.6; margin: 0 0 10px; }
label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
input[type=text], select, textarea { width: 100%; background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: 8px 10px; font-size: 13px; font-family: inherit; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
button { background: var(--accent); color: white; border: 0; border-radius: 6px; padding: 8px 14px; font-size: 13px; cursor: pointer; font-weight: 500; }
button:hover { filter: brightness(1.1); }
button.secondary { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); margin-right: 6px; }
.badge.warn { color: var(--warn); border-color: #543b00; }
/* Persona scroller */
.persona-scroll { display: flex; gap: 12px; overflow-x: auto; padding: 4px 2px 14px; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.persona-scroll::-webkit-scrollbar { height: 8px; }
.persona-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.persona-card { flex: 0 0 180px; height: 220px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 14px; cursor: pointer; transition: all 0.15s; display: flex; flex-direction: column; align-items: center; gap: 10px; position: relative; }
.persona-card:hover { border-color: var(--muted); transform: translateY(-2px); }
.persona-card.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 24px -4px var(--accent); }
.persona-card .name { font-size: 13px; font-weight: 600; }
.persona-card .repo { font-family: var(--mono); font-size: 10px; color: var(--muted); text-align: center; }
.persona-card .vibe { font-size: 11px; color: var(--muted); text-align: center; line-height: 1.4; margin-top: 2px; }
.persona-card .check { position: absolute; top: 8px; right: 10px; color: var(--accent); font-size: 12px; opacity: 0; transition: opacity 0.15s; }
.persona-card.selected .check { opacity: 1; }
/* Orb */
.orb { width: 64px; height: 64px; border-radius: 50%; background: radial-gradient(circle at 30% 30%, var(--orb-hi, #58a6ff), var(--orb-lo, #1f2d4a) 70%); box-shadow: 0 0 24px -4px var(--orb-hi, #58a6ff), inset 0 0 12px rgba(0,0,0,0.4); flex-shrink: 0; transition: background 0.3s, box-shadow 0.3s; }
.orb.mini { width: 24px; height: 24px; box-shadow: 0 0 10px -2px var(--orb-hi, #58a6ff), inset 0 0 6px rgba(0,0,0,0.4); }
.orb.big { width: 88px; height: 88px; box-shadow: 0 0 36px -4px var(--orb-hi, #58a6ff), inset 0 0 16px rgba(0,0,0,0.4); }
/* Controls */
.ctrl-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 720px) { .ctrl-row { grid-template-columns: 1fr; } }
.slider-wrap { display: flex; align-items: center; gap: 12px; }
.slider-wrap input[type=range] { flex: 1; -webkit-appearance: none; appearance: none; height: 6px; background: linear-gradient(90deg, var(--accent) 0%, var(--accent) var(--pct, 0%), var(--panel-2) var(--pct, 0%), var(--panel-2) 100%); border-radius: 3px; outline: none; border: 1px solid var(--border); }
.slider-wrap input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--text); cursor: pointer; border: 2px solid var(--accent); box-shadow: 0 0 8px -2px var(--accent); }
.slider-wrap input[type=range]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--text); cursor: pointer; border: 2px solid var(--accent); }
.slider-val { font-family: var(--mono); font-size: 14px; font-weight: 600; min-width: 48px; text-align: right; }
.metrics { display: flex; gap: 18px; margin-top: 14px; flex-wrap: wrap; }
.metric { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; flex: 1; min-width: 140px; }
.metric .label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; }
.metric .value { font-family: var(--mono); font-size: 18px; font-weight: 600; margin-top: 2px; }
.metric .value.ok { color: var(--ok); } .metric .value.warn { color: var(--warn); }
.metric .bar { height: 4px; background: var(--panel); border-radius: 2px; margin-top: 6px; overflow: hidden; }
.metric .bar > span { display: block; height: 100%; background: var(--accent); transition: width 0.15s, background 0.15s; }
/* Chat */
.chat { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; max-height: 460px; overflow-y: auto; padding: 14px; }
.msg { display: flex; gap: 10px; margin-bottom: 14px; }
.msg .who { font-size: 11px; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.4px; }
.msg .bubble { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; font-size: 13px; line-height: 1.55; flex: 1; }
.msg.user { flex-direction: row-reverse; }
.msg.user .bubble { background: #1a2a44; border-color: #2a4470; }
.msg .col { flex: 1; min-width: 0; }
.chat-input { display: flex; gap: 8px; margin-top: 12px; }
.chat-input input { flex: 1; }
/* Code block */
.code-wrap { position: relative; }
pre.code { background: #0a0d12; border: 1px solid var(--border); padding: 14px; border-radius: 6px; font-family: var(--mono); font-size: 12px; overflow: auto; color: #c9d1d9; line-height: 1.5; margin: 0; }
.code-tabs { display: flex; gap: 6px; margin-bottom: 8px; }
.code-tabs button { background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); padding: 4px 12px; font-size: 12px; }
.code-tabs button.active { color: var(--text); border-color: var(--accent); }
.copy-btn { position: absolute; top: 10px; right: 10px; background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); padding: 4px 10px; font-size: 11px; border-radius: 4px; }
.copy-btn:hover { color: var(--text); }
.copy-btn.copied { color: var(--ok); border-color: var(--ok); }
/* Persona summary + provenance */
.persona-summary { display: flex; gap: 16px; align-items: center; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 14px; margin-bottom: 14px; }
.persona-summary .info { flex: 1; }
.persona-summary h3 { margin: 0; font-size: 15px; font-weight: 600; }
.persona-summary .repo { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; }
.persona-summary .traits { font-size: 12px; color: var(--muted); margin-top: 6px; line-height: 1.5; }
.provenance { color: var(--muted); font-size: 12px; line-height: 1.6; }
.provenance code { font-family: var(--mono); color: var(--text); background: var(--panel-2); padding: 1px 6px; border-radius: 4px; }
</style>
</head>
<body>
<script>if (new URLSearchParams(location.search).has("embed")) document.body.classList.add("embed");</script>
<main>
<section class="panel">
<h2>Buddy Steering <span class="hint">load a maintainer's emotion fingerprint as activation steering on a base model</span></h2>
<p>
Pick a maintainer, pick a base model, dial the intensity. The chat preview below shows how a
steered model would respond — same weights, additive residual-stream nudges at the probe layer.
<span class="badge warn">backend coming soon</span>
<span class="badge">mock data</span>
</p>
</section>
<!-- 1. Persona selector -->
<section class="panel">
<h2>1 · Pick a buddy <span class="hint">click a maintainer to load their emotion fingerprint</span></h2>
<div class="persona-scroll" id="persona-scroll"></div>
</section>
<!-- 2 + 3. Model + Intensity -->
<section class="panel">
<h2>2 · Model & intensity <span class="hint">how much of the buddy do you want?</span></h2>
<div class="persona-summary" id="persona-summary"></div>
<div class="ctrl-row">
<div>
<label for="model-sel">base model</label>
<select id="model-sel"></select>
</div>
<div>
<label for="intensity">intensity <span style="color: var(--muted)">(5% steps)</span></label>
<div class="slider-wrap">
<input id="intensity" type="range" min="0" max="100" step="5" value="0" />
<span class="slider-val" id="intensity-val">0%</span>
</div>
</div>
</div>
<div class="metrics">
<div class="metric">
<div class="label">persona distance</div>
<div class="value" id="m-dist">0.000</div>
<div class="bar"><span id="m-dist-bar" style="width: 0%"></span></div>
</div>
<div class="metric">
<div class="label">persona fit</div>
<div class="value" id="m-fit"></div>
<div class="bar"><span id="m-fit-bar" style="width: 0%; background: var(--ok)"></span></div>
</div>
<div class="metric">
<div class="label">steered orb</div>
<div style="display: flex; align-items: center; gap: 10px; margin-top: 4px;">
<div class="orb mini" id="orb-steered"></div>
<div style="font-family: var(--mono); font-size: 11px; color: var(--muted);" id="orb-state">neutral</div>
</div>
</div>
</div>
</section>
<!-- 4. Live preview chat -->
<section class="panel">
<h2>3 · Live preview <span class="hint">how the steered model would respond</span></h2>
<div class="chat" id="chat"></div>
<div class="chat-input">
<input id="chat-text" type="text" placeholder='try: "fix this bug", "explain this code", "review my PR"' />
<button id="chat-send">Send</button>
<button class="secondary" id="chat-reset">Reset</button>
</div>
</section>
<!-- 5. Inference endpoint snippet -->
<section class="panel">
<h2>4 · Use this endpoint <span class="hint">apply the same steering from your own code</span></h2>
<p>
<span class="badge warn">coming soon — backend not wired yet</span>
The chat endpoint will accept <code style="font-family: var(--mono); color: var(--text);">persona</code>
and <code style="font-family: var(--mono); color: var(--text);">intensity</code> query params and apply
additive steering at the probe layer.
</p>
<div class="code-tabs">
<button data-tab="curl" class="active">curl</button>
<button data-tab="python">python</button>
<button data-tab="js">javascript</button>
</div>
<div class="code-wrap">
<pre class="code" id="code-block"></pre>
<button class="copy-btn" id="copy-btn">copy</button>
</div>
</section>
<!-- 6. Provenance -->
<section class="panel">
<h2>Provenance <span class="hint">where these vectors came from</span></h2>
<div class="provenance" id="provenance"></div>
</section>
</main>
<script>
"use strict";
// ============================================================================
// MOCK DATA — replace with real persona vectors from /api/personas once wired
// ============================================================================
const PERSONAS = [
{ id: "ArthurZ", name: "Arthur Zucker", repo: "huggingface/transformers",
vibe: "terse · playful · ships", orbHi: "#f0b429", orbLo: "#5e3b00", state: "focused-hype",
avgFit: { "google/gemma-2-2b-it": 0.91, "Qwen/Qwen2.5-7B-Instruct": 0.86, "meta-llama/Llama-3.1-8B-Instruct": 0.79 },
traits: "Short sentences. Dry humor. Will ship before you finish reading the diff. Strong opinions about generation configs.",
samples: {
fix: [
"yeah that's the cache mask, swap it out — should be 1 line.",
"nope. you're hitting `_get_logits_processor`. patch there, ship it.",
"okay so. the issue is past_key_values shape. fix it, push, I'll review."],
explain: [
"it's just the rotary embeddings, but inverted. nothing wild.",
"this whole block is doing what `apply_chat_template` does in 4 lines. read it top-down.",
"tl;dr: KV cache + sliding window. The rest is bookkeeping."],
review: [
"LGTM with 2 nits. don't squash before I look again.",
"merge it. tests pass, I trust you.",
"one comment inline, fix and I'll re-approve."] } },
{ id: "Lysandre", name: "Lysandre Debut", repo: "huggingface/transformers",
vibe: "methodical · calm · encouraging", orbHi: "#58a6ff", orbLo: "#1f3a6e", state: "steady-clear",
avgFit: { "google/gemma-2-2b-it": 0.84, "Qwen/Qwen2.5-7B-Instruct": 0.89, "meta-llama/Llama-3.1-8B-Instruct": 0.92 },
traits: "Walks you through the why before the how. Always has a doc link. Patient — even when the answer is obvious.",
samples: {
fix: [
"Let's look at this step by step. The error trace suggests the tokenizer is on a different device than the model — could you share the device map?",
"Good catch — this is a known edge case. I'd suggest pinning `transformers>=4.45` and re-running. Want me to point you at the PR that fixed it?",
"Two things to verify first: (1) the config has `use_cache=True`, (2) the input tensor isn't padded. Both common, both quick to check."],
explain: [
"Sure — at a high level, this module routes attention through a sliding window. The key intuition is that you trade global context for memory; the docs page on long-context has a great diagram.",
"Happy to walk through it. The function lives in three layers: registration, dispatch, execution. Let's start with registration and work down.",
"Conceptually this is the same as a standard self-attention block, with one twist: the QK projection is shared. That choice is documented in the original paper, section 3.2."],
review: [
"Thanks for putting this together. A few thoughts inline — none blocking. The structure is clean; I especially like the test you added.",
"Really nice work. One suggestion on the API surface, and then I think we're good to merge.",
"Great PR. Could you add a one-line docstring on the new helper? After that, happy to approve."] } },
{ id: "hanouticelina", name: "Celina Hanouti", repo: "huggingface/huggingface_hub",
vibe: "detail-oriented · curious", orbHi: "#bc8cff", orbLo: "#3d2462", state: "investigative",
avgFit: { "google/gemma-2-2b-it": 0.78, "Qwen/Qwen2.5-7B-Instruct": 0.92, "meta-llama/Llama-3.1-8B-Instruct": 0.83 },
traits: "Reproduces every bug. Reads the stack trace twice. Will find the actual root cause, not just a workaround.",
samples: {
fix: [
"Could you share the exact `hf_hub_download` call and the traceback? I want to repro before patching — I suspect it's the etag check, but want to be sure.",
"Hmm — interesting. Is this on the latest `huggingface_hub`? I'm seeing a similar report in #2891 that turned out to be a proxy issue.",
"Let me reproduce locally. If it's what I think (cache key collision with revision pinning), the fix is in `_cache_commit_hash_for_specific_revision`."],
explain: [
"So this function is doing three things, and the naming hides one of them. Let me unpack: (1) it resolves the URL, (2) checks the cache, (3) writes a symlink. The symlink step is the one most people miss.",
"Good question — the reason for the double `try/except` is historical. There was a race in 0.17 where two threads could both miss the cache. The second except handles that path.",
"The trick here is the `_chunk_file_to_resume` helper. It's not just resuming downloads — it's also validating prior partial bytes. Look at lines 240-260."],
review: [
"Thanks! Left a few comments. Mostly clarifying — the logic is correct. One concern: the test doesn't cover the offline path. Could you add one?",
"Nice fix. I'd suggest one small refactor to keep the cache-key logic in one place; otherwise this looks great.",
"Could you double-check this against the test_hf_api suite? I'd be more confident merging if those pass too."] } },
{ id: "sgugger", name: "Sylvain Gugger", repo: "huggingface/accelerate",
vibe: "rigorous · educational · warm", orbHi: "#3fb950", orbLo: "#1d3a23", state: "teaching",
avgFit: { "google/gemma-2-2b-it": 0.88, "Qwen/Qwen2.5-7B-Instruct": 0.81, "meta-llama/Llama-3.1-8B-Instruct": 0.86 },
traits: "Always shows the simpler version first. Allergic to magic. Will rewrite your function with one fewer abstraction.",
samples: {
fix: [
"This will be simpler if you don't use the wrapper. Try calling `accelerator.prepare(model, optimizer, dataloader)` directly — three lines instead of seven.",
"The issue is you're calling `.to(device)` after `prepare`. Swap the order, you don't need both.",
"Quick fix: remove the manual `gradient_accumulation_steps` loop. Accelerate handles it if you pass it to the constructor."],
explain: [
"The cleanest way to read this is to imagine you removed all the distributed logic — what would the single-GPU version look like? Start there, then add the wrappers back one at a time.",
"Think of `accelerator.prepare` as a no-op on a single device. Everything else is the same training loop you'd write by hand.",
"Two concepts: device placement and gradient sync. Read those two methods first, the rest builds on them."],
review: [
"Looks good. One suggestion: the helper you wrote is nearly identical to `find_batch_size` — could you reuse that instead?",
"Clean PR. I'd just rename `_inner_func` to something descriptive, and add a one-line example to the docstring.",
"Approving. The test could be a bit tighter (drop the for-loop, use `parametrize`), but not blocking."] } },
{ id: "Wauplin", name: "Lucain Pouget", repo: "huggingface/huggingface_hub",
vibe: "DX-obsessed · friendly · pragmatic", orbHi: "#f78166", orbLo: "#5e2a1b", state: "warm-helpful",
avgFit: { "google/gemma-2-2b-it": 0.82, "Qwen/Qwen2.5-7B-Instruct": 0.87, "meta-llama/Llama-3.1-8B-Instruct": 0.84 },
traits: "Thinks in user journeys. Will rewrite an error message six times to make it kinder. Owns the SDK ergonomics.",
samples: {
fix: [
"Ah, classic — the token isn't being picked up. Try `huggingface-cli login` again, or set `HF_TOKEN`. Let me know if that doesn't unblock you!",
"We should improve the error here. As a workaround, you can pass `token=` explicitly. I'll open an issue to make the message clearer.",
"Got it, this is a known papercut. Quickest unblock: pin to 0.24.5 for now, the fix is in main and shipping next week."],
explain: [
"The mental model: `HfApi` is the verb-shaped interface, `Repository` is the noun-shaped one. Most of the time you want `HfApi`. The other exists for legacy git-LFS workflows.",
"Three layers: HTTP client → API methods → high-level helpers. Each layer adds ergonomics; you can drop down whenever you want.",
"It's basically a wrapper around `requests` with auth + retry baked in. Once you see that, the rest follows."],
review: [
"Love it. Tiny suggestion: could we add a CLI alias for this? Makes it more discoverable for folks not reading the API docs.",
"Nice work — the error messages are a huge improvement. One nit: let's match the casing convention in `errors.py`.",
"Thanks for the PR! The behavior is right, just one DX tweak: the default value should be `None` instead of `False`, so users can tell if they set it."] } },
{ id: "sanchit-gandhi", name: "Sanchit Gandhi", repo: "huggingface/transformers · audio",
vibe: "experimental · audio-pilled · spirited", orbHi: "#f85149", orbLo: "#5e1c1c", state: "energetic-curious",
avgFit: { "google/gemma-2-2b-it": 0.74, "Qwen/Qwen2.5-7B-Instruct": 0.79, "meta-llama/Llama-3.1-8B-Instruct": 0.81 },
traits: "Speaks fluent log-mel. Will sneak a Whisper benchmark into any conversation. Always shipping a notebook.",
samples: {
fix: [
"Ahh classic Whisper gotcha — you need `return_timestamps=True` on the pipeline. Try that and ping me if it still misbehaves.",
"The audio is probably resampled wrong. Quick check: print `inputs.input_features.shape` — should be (1, 80, 3000).",
"Sounds like the language token isn't being forced. Pass `forced_decoder_ids=processor.get_decoder_prompt_ids(language='en', task='transcribe')`."],
explain: [
"So this is the log-mel filterbank — 80 channels by default, computed on 30-second windows. The encoder doesn't care about the input length, it pads to 3000 frames.",
"Think of it as: audio → spectrogram → transformer encoder → text decoder. Same pattern as a vision encoder, just on log-mel instead of patches.",
"The interesting bit is the cross-attention. It's where text decoder queries the encoder for acoustic context. Look at `WhisperAttention.forward`."],
review: [
"Cool PR! Quick question: did you benchmark on the long-form audio path? That's where Whisper usually regresses.",
"Nice — I love the new test. Could you add one more case for the streaming path? That's the one I'd be nervous about.",
"Approving once CI is green. Solid work — the notebook in the description sold it for me."] } }
];
const MODELS = [
{ id: "google/gemma-2-2b-it", label: "google/gemma-2-2b-it" },
{ id: "Qwen/Qwen2.5-7B-Instruct", label: "Qwen/Qwen2.5-7B-Instruct" },
{ id: "meta-llama/Llama-3.1-8B-Instruct", label: "meta-llama/Llama-3.1-8B-Instruct" }
];
const BASE_ORB = { hi: "#58a6ff", lo: "#1f2d4a" }; // neutral base model orb
// ============================================================================
// State
// ============================================================================
let state = {
personaId: PERSONAS[0].id,
modelId: MODELS[0].id,
intensity: 0,
chatHistory: [], // {role, text, orbHi, orbLo, state}
codeTab: "curl"
};
function persona() { return PERSONAS.find(p => p.id === state.personaId); }
// ============================================================================
// Rendering
// ============================================================================
function renderPersonas() {
const root = document.getElementById("persona-scroll");
root.innerHTML = PERSONAS.map(p => `
<div class="persona-card ${p.id === state.personaId ? "selected" : ""}" data-id="${p.id}">
<span class="check">●</span>
<div class="orb" style="--orb-hi: ${p.orbHi}; --orb-lo: ${p.orbLo};"></div>
<div class="name">${p.name}</div>
<div class="repo">${p.repo}</div>
<div class="vibe">${p.vibe}</div>
</div>
`).join("");
root.querySelectorAll(".persona-card").forEach(el => {
el.addEventListener("click", () => {
state.personaId = el.dataset.id;
state.chatHistory = []; // reset chat when buddy changes
renderAll();
});
});
}
function renderPersonaSummary() {
const p = persona();
document.getElementById("persona-summary").innerHTML = `
<div class="orb big" style="--orb-hi: ${p.orbHi}; --orb-lo: ${p.orbLo};"></div>
<div class="info">
<h3>${p.name}</h3>
<div class="repo">${p.repo}</div>
<div class="traits">${p.traits}</div>
</div>
`;
}
function renderModelSelect() {
const sel = document.getElementById("model-sel");
const p = persona();
sel.innerHTML = MODELS.map(m => {
const fit = Math.round((p.avgFit[m.id] || 0.5) * 100);
return `<option value="${m.id}" ${m.id === state.modelId ? "selected" : ""}>${m.label}${fit}% fit</option>`;
}).join("");
}
// Lerp a hex color toward another. Used to morph the base-model orb toward the persona's orb.
function lerpHex(a, b, t) {
const pa = parseInt(a.slice(1), 16), pb = parseInt(b.slice(1), 16);
const ar = (pa >> 16) & 0xff, ag = (pa >> 8) & 0xff, ab = pa & 0xff;
const br = (pb >> 16) & 0xff, bg = (pb >> 8) & 0xff, bb = pb & 0xff;
const r = Math.round(ar + (br - ar) * t);
const g = Math.round(ag + (bg - ag) * t);
const bl = Math.round(ab + (bb - ab) * t);
return "#" + ((r << 16) | (g << 8) | bl).toString(16).padStart(6, "0");
}
function steeredOrb() {
const p = persona();
const t = state.intensity / 100;
return {
hi: lerpHex(BASE_ORB.hi, p.orbHi, t),
lo: lerpHex(BASE_ORB.lo, p.orbLo, t),
state: t < 0.05 ? "neutral" : t < 0.5 ? "drift → " + p.state : t < 0.9 ? p.state : p.state + " (saturated)"
};
}
function renderMetrics() {
const p = persona();
const t = state.intensity / 100;
// Mock cosine-similarity-style curve — sigmoid-ish toward 1.0
const dist = +(t * (0.5 + 0.5 * t)).toFixed(3);
const fitPct = Math.round((p.avgFit[state.modelId] || 0.5) * 100);
const orb = steeredOrb();
document.getElementById("m-dist").textContent = dist.toFixed(3);
document.getElementById("m-dist-bar").style.width = (dist * 100) + "%";
document.getElementById("m-dist-bar").style.background = lerpHex("#2f81f7", p.orbHi, t);
document.getElementById("m-fit").textContent = fitPct + "%";
document.getElementById("m-fit").className = "value " + (fitPct >= 85 ? "ok" : fitPct >= 75 ? "warn" : "");
document.getElementById("m-fit-bar").style.width = fitPct + "%";
const orbEl = document.getElementById("orb-steered");
orbEl.style.setProperty("--orb-hi", orb.hi);
orbEl.style.setProperty("--orb-lo", orb.lo);
document.getElementById("orb-state").textContent = orb.state;
}
function renderIntensity() {
document.getElementById("intensity-val").textContent = state.intensity + "%";
const slider = document.getElementById("intensity");
slider.style.setProperty("--pct", state.intensity + "%");
}
// ----------------------------------------------------------------------------
// Chat
// ----------------------------------------------------------------------------
function classifyIntent(text) {
const t = text.toLowerCase();
if (/(fix|bug|error|broken|crash|fails|exception|traceback)/.test(t)) return "fix";
if (/(explain|how does|how do|what is|what does|walk me through|understand)/.test(t)) return "explain";
if (/(review|pr |pull request|merge|looks good|feedback|approve)/.test(t)) return "review";
return "fix"; // default — most maintainer messages are bug-shaped
}
// Generate a maintainer-styled response, blended with neutral text by intensity.
// At 0%, the response is generic. At 100%, it's pure maintainer voice.
const NEUTRAL_RESPONSES = {
fix: "I can help you debug this. Could you share the full error message and what you've tried so far?",
explain: "Sure, I can walk you through this. Let me know which part you'd like to focus on first.",
review: "Thanks for sharing this PR. Overall it looks reasonable; I'd want to check a few details before approving."
};
function generateResponse(userText) {
const p = persona();
const intent = classifyIntent(userText);
const samples = p.samples[intent];
const personaLine = samples[Math.floor(Math.random() * samples.length)];
const neutralLine = NEUTRAL_RESPONSES[intent];
const t = state.intensity / 100;
// Blend: at low t, mostly neutral; at high t, mostly persona.
// We pick which one, with a "hybrid" middle band that prefixes neutral with a persona-tinted hook.
if (t < 0.15) return neutralLine;
if (t < 0.45) {
// light steering — neutral phrasing with one persona-flavored opener
const opener = pickOpener(p, intent, "light");
return opener + " " + neutralLine;
}
if (t < 0.75) {
// medium — persona phrasing, slightly softened
return personaLine;
}
// strong — full persona, sometimes a second sentence pulled from the bank
if (Math.random() < 0.4 && samples.length > 1) {
const second = samples[(samples.indexOf(personaLine) + 1) % samples.length];
return personaLine + " " + second;
}
return personaLine;
}
function pickOpener(p, intent, _strength) {
// Tiny hook phrases that hint at the persona without going full voice.
const HOOKS = {
ArthurZ: ["Quick one:", "Right —", "Okay so —"],
Lysandre: ["Happy to help.", "Sure —", "Let me think with you."],
hanouticelina: ["Interesting —", "Let me check —", "Hmm, one sec."],
sgugger: ["Short version:", "Simpler approach:", "Here's the cleaner way:"],
Wauplin: ["Ah okay!", "Yeah — small thing:", "Got you."],
"sanchit-gandhi": ["Oh nice —", "Wait —", "Cool, so —"]
};
const list = HOOKS[p.id] || ["Sure —"];
return list[Math.floor(Math.random() * list.length)];
}
function renderChat() {
const root = document.getElementById("chat");
if (!state.chatHistory.length) {
root.innerHTML = `<div style="color: var(--muted); font-size: 12px; padding: 20px; text-align: center;">
Send a message to see how <b style="color: var(--text)">${persona().name}</b>-style steering changes the response.
<br/><span style="font-size: 11px;">try: "fix this bug", "explain this code", "review my PR"</span>
</div>`;
return;
}
root.innerHTML = state.chatHistory.map(m => {
if (m.role === "user") {
return `<div class="msg user">
<div class="col">
<div class="who" style="text-align: right;">you</div>
<div class="bubble">${escapeHtml(m.text)}</div>
</div>
</div>`;
}
return `<div class="msg assistant">
<div class="orb mini" style="--orb-hi: ${m.orbHi}; --orb-lo: ${m.orbLo};" title="${m.state}"></div>
<div class="col">
<div class="who">steered · ${escapeHtml(m.state)}</div>
<div class="bubble">${escapeHtml(m.text)}</div>
</div>
</div>`;
}).join("");
root.scrollTop = root.scrollHeight;
}
function sendMessage() {
const input = document.getElementById("chat-text");
const text = input.value.trim();
if (!text) return;
input.value = "";
state.chatHistory.push({ role: "user", text });
renderChat();
// Fake "typing" delay so the wow-moment feels alive.
const orb = steeredOrb();
setTimeout(() => {
const reply = generateResponse(text);
state.chatHistory.push({ role: "assistant", text: reply, orbHi: orb.hi, orbLo: orb.lo, state: orb.state });
renderChat();
}, 350 + Math.random() * 300);
}
// ----------------------------------------------------------------------------
// Code snippet (5)
// ----------------------------------------------------------------------------
function snippet(tab, personaId, modelId, intensity) {
const t = (intensity / 100).toFixed(2);
const url = "https://slyfox-buddy.hf.space/v1/chat/completions";
if (tab === "curl") {
return `# coming soon — endpoint not live yet
curl -s ${url} \\
-H "Authorization: Bearer $HF_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{
"model": "${modelId}",
"persona": "${personaId}",
"intensity": ${t},
"messages": [
{"role": "user", "content": "fix this bug"}
]
}'`;
}
if (tab === "python") {
return `# coming soon — endpoint not live yet
from openai import OpenAI
client = OpenAI(
base_url="${url.replace("/chat/completions", "")}",
api_key=os.environ["HF_TOKEN"],
)
resp = client.chat.completions.create(
model="${modelId}",
messages=[{"role": "user", "content": "fix this bug"}],
extra_body={"persona": "${personaId}", "intensity": ${t}},
)
print(resp.choices[0].message.content)`;
}
return `// coming soon — endpoint not live yet
const resp = await fetch("${url}", {
method: "POST",
headers: {
"Authorization": \`Bearer \${process.env.HF_TOKEN}\`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "${modelId}",
persona: "${personaId}",
intensity: ${t},
messages: [{ role: "user", content: "fix this bug" }]
})
});
const data = await resp.json();
console.log(data.choices[0].message.content);`;
}
function renderCode() {
document.getElementById("code-block").textContent =
snippet(state.codeTab, state.personaId, state.modelId, state.intensity);
document.querySelectorAll(".code-tabs button").forEach(b => {
b.classList.toggle("active", b.dataset.tab === state.codeTab);
});
}
// ----------------------------------------------------------------------------
// Provenance (6)
// ----------------------------------------------------------------------------
function renderProvenance() {
const p = persona();
document.getElementById("provenance").innerHTML = `
Persona vectors for <b style="color: var(--text)">${p.name}</b> were derived from a trace bundle on
<code>HF-slyfox/traces / ${p.id.toLowerCase()}-${p.repo.split('/')[1] || 'repo'}</code>,
analyzed against <code>${state.modelId}</code> at the residual probe layer.
<br/>
Bundle hash: <code>${mockHash(p.id + state.modelId)}</code> ·
<a href="/community">view analysis run →</a>
<br/>
<span style="font-size: 11px;">
The 20 emotion direction vectors per model follow the AidanZach / EmotionScope methodology.
Steering is additive at the probe layer; no weights are modified.
</span>
`;
}
function mockHash(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
return "sha256:" + Math.abs(h).toString(16).padStart(8, "0") + "…";
}
// ----------------------------------------------------------------------------
// Wire up
// ----------------------------------------------------------------------------
function renderAll() {
renderPersonas();
renderPersonaSummary();
renderModelSelect();
renderIntensity();
renderMetrics();
renderChat();
renderCode();
renderProvenance();
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
document.getElementById("model-sel").addEventListener("change", e => {
state.modelId = e.target.value;
renderMetrics();
renderCode();
renderProvenance();
});
// Live-update on input (not just change) so the slider feels alive while dragging.
const slider = document.getElementById("intensity");
slider.addEventListener("input", e => {
state.intensity = parseInt(e.target.value, 10);
renderIntensity();
renderMetrics();
renderCode();
});
document.getElementById("chat-send").addEventListener("click", sendMessage);
document.getElementById("chat-text").addEventListener("keydown", e => {
if (e.key === "Enter") sendMessage();
});
document.getElementById("chat-reset").addEventListener("click", () => {
state.chatHistory = [];
renderChat();
});
document.querySelectorAll(".code-tabs button").forEach(b => {
b.addEventListener("click", () => { state.codeTab = b.dataset.tab; renderCode(); });
});
document.getElementById("copy-btn").addEventListener("click", () => {
const text = document.getElementById("code-block").textContent;
navigator.clipboard.writeText(text).then(() => {
const btn = document.getElementById("copy-btn");
btn.textContent = "copied!";
btn.classList.add("copied");
setTimeout(() => { btn.textContent = "copy"; btn.classList.remove("copied"); }, 1400);
});
});
renderAll();
// Pre-seed one assistant turn so the chat doesn't look empty on first load —
// helps the screenshot land. Intent = "fix", at current intensity (0).
(function preseed() {
state.chatHistory.push({ role: "user", text: "the model keeps generating empty strings, what's up?" });
const orb = steeredOrb();
state.chatHistory.push({
role: "assistant",
text: generateResponse("fix this bug"),
orbHi: orb.hi, orbLo: orb.lo, state: orb.state
});
renderChat();
})();
</script>
</body>
</html>