| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> |
| <title>Trace Personality Bulletin</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=IBM+Plex+Mono:wght@400;500;600&family=Alfa+Slab+One&display=swap" |
| rel="stylesheet" |
| /> |
| <style> |
| :root { |
| --ink: #353e60; |
| --ink-soft: rgba(53, 62, 96, 0.7); |
| --ink-faint: rgba(53, 62, 96, 0.18); |
| --surface: #fff8de; |
| --surface-deep: #f6eecf; |
| --accent: #00704a; |
| --accent2: #ff9d00; |
| --red: #db3328; |
| --yellow: #ffd21e; |
| } |
| * { box-sizing: border-box; } |
| html, body { |
| margin: 0; |
| padding: 0; |
| background: var(--surface); |
| color: var(--ink); |
| font-family: 'Source Sans 3', -apple-system, system-ui, sans-serif; |
| min-height: 100vh; |
| } |
| |
| body { |
| background-image: |
| radial-gradient(var(--ink-faint) 1px, transparent 1.4px); |
| background-size: 22px 22px; |
| } |
| |
| .page { |
| max-width: 880px; |
| margin: 0 auto; |
| padding: 48px 24px 80px; |
| } |
| |
| |
| .panel { |
| position: relative; |
| background: var(--surface); |
| color: var(--ink); |
| padding: 44px 56px 40px; |
| box-shadow: |
| 0 24px 60px rgba(0, 0, 0, 0.18), |
| 0 6px 16px rgba(0, 0, 0, 0.10); |
| border-radius: 6px; |
| isolation: isolate; |
| } |
| .panel::before, |
| .panel::after { |
| content: ""; |
| position: absolute; |
| pointer-events: none; |
| } |
| .panel::before { |
| inset: 14px; |
| border: 2px solid var(--ink); |
| border-radius: 3px; |
| } |
| .panel::after { |
| inset: 22px; |
| border: 1px solid var(--ink); |
| border-radius: 2px; |
| } |
| .corner { |
| position: absolute; |
| width: 22px; |
| height: 22px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| background: var(--surface); |
| color: var(--ink); |
| font-size: 14px; |
| font-weight: 700; |
| z-index: 2; |
| } |
| .corner.tl { top: 6px; left: 6px; } |
| .corner.tr { top: 6px; right: 6px; } |
| .corner.bl { bottom: 6px; left: 6px; } |
| .corner.br { bottom: 6px; right: 6px; } |
| |
| .masthead { |
| text-align: center; |
| position: relative; |
| } |
| .masthead .tiny { |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 11px; |
| letter-spacing: 0.34em; |
| text-transform: uppercase; |
| color: var(--ink-soft); |
| margin-bottom: 10px; |
| } |
| .masthead h1 { |
| font-family: 'Alfa Slab One', serif; |
| font-weight: 400; |
| font-size: clamp(28px, 5vw, 40px); |
| letter-spacing: 0.04em; |
| line-height: 1; |
| text-transform: uppercase; |
| color: var(--ink); |
| margin: 0; |
| } |
| .masthead h1 .accent { |
| color: var(--accent); |
| text-shadow: 3px 3px 0 var(--ink), -2px -2px 0 var(--accent2); |
| } |
| .rule { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin: 14px auto 0; |
| max-width: 540px; |
| color: var(--ink-soft); |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 12px; |
| letter-spacing: 0.32em; |
| } |
| .rule .line { |
| flex: 1; |
| height: 1px; |
| background: currentColor; |
| opacity: 0.5; |
| } |
| .lede { |
| margin: 18px auto 0; |
| text-align: center; |
| max-width: 580px; |
| font-style: italic; |
| font-size: 16px; |
| line-height: 1.45; |
| color: var(--ink-soft); |
| } |
| |
| |
| .form { |
| margin-top: 34px; |
| display: flex; |
| flex-direction: column; |
| gap: 26px; |
| } |
| |
| .field-label { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| gap: 12px; |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 11px; |
| letter-spacing: 0.28em; |
| text-transform: uppercase; |
| color: var(--ink); |
| margin-bottom: 10px; |
| } |
| .field-label .value { |
| font-family: 'Alfa Slab One', serif; |
| font-size: 22px; |
| line-height: 1; |
| letter-spacing: 0.02em; |
| color: var(--accent); |
| text-shadow: 2px 2px 0 var(--ink); |
| } |
| |
| |
| .repo-input { |
| width: 100%; |
| background: var(--surface-deep); |
| color: var(--ink); |
| border: 1.5px solid var(--ink); |
| border-radius: 4px; |
| padding: 14px 16px; |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 16px; |
| letter-spacing: 0.02em; |
| box-shadow: 4px 4px 0 var(--ink); |
| transition: transform 80ms ease, box-shadow 80ms ease; |
| } |
| .repo-input::placeholder { |
| color: var(--ink-soft); |
| font-style: italic; |
| } |
| .repo-input:focus { |
| outline: none; |
| transform: translate(-1px, -1px); |
| box-shadow: 5px 5px 0 var(--accent2); |
| } |
| |
| |
| .slider-wrap { padding: 4px 4px 6px; } |
| .slider { |
| -webkit-appearance: none; |
| appearance: none; |
| width: 100%; |
| height: 22px; |
| background: transparent; |
| margin: 0; |
| cursor: pointer; |
| } |
| .slider:focus { outline: none; } |
| .slider::-webkit-slider-runnable-track { |
| height: 6px; |
| background: |
| linear-gradient(to right, var(--accent2) 0%, var(--accent2) var(--pct, 60%), var(--ink-faint) var(--pct, 60%), var(--ink-faint) 100%); |
| border: 1.5px solid var(--ink); |
| border-radius: 999px; |
| } |
| .slider::-moz-range-track { |
| height: 6px; |
| background: var(--ink-faint); |
| border: 1.5px solid var(--ink); |
| border-radius: 999px; |
| } |
| .slider::-moz-range-progress { |
| height: 6px; |
| background: var(--accent2); |
| border-radius: 999px 0 0 999px; |
| } |
| .slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| appearance: none; |
| width: 22px; |
| height: 22px; |
| border-radius: 50%; |
| background: var(--surface); |
| border: 2px solid var(--ink); |
| box-shadow: 2px 2px 0 var(--ink); |
| margin-top: -10px; |
| cursor: grab; |
| } |
| .slider::-webkit-slider-thumb:active { cursor: grabbing; transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--ink); } |
| .slider::-moz-range-thumb { |
| width: 22px; |
| height: 22px; |
| border-radius: 50%; |
| background: var(--surface); |
| border: 2px solid var(--ink); |
| box-shadow: 2px 2px 0 var(--ink); |
| cursor: grab; |
| } |
| .slider-ticks { |
| display: flex; |
| justify-content: space-between; |
| margin-top: 6px; |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 10px; |
| letter-spacing: 0.18em; |
| color: var(--ink-soft); |
| } |
| |
| |
| .submit-row { |
| display: flex; |
| justify-content: center; |
| margin-top: 6px; |
| } |
| .submit { |
| appearance: none; |
| border: 2px solid var(--ink); |
| background: var(--ink); |
| color: var(--surface); |
| font-family: 'Alfa Slab One', serif; |
| font-weight: 400; |
| font-size: 18px; |
| letter-spacing: 0.18em; |
| text-transform: uppercase; |
| padding: 14px 30px; |
| border-radius: 4px; |
| cursor: pointer; |
| box-shadow: 6px 6px 0 var(--accent2); |
| transition: transform 90ms ease, box-shadow 90ms ease, background 120ms; |
| } |
| .submit:hover { background: #2a3251; } |
| .submit:active { |
| transform: translate(3px, 3px); |
| box-shadow: 3px 3px 0 var(--accent2); |
| } |
| .submit:disabled { |
| cursor: not-allowed; |
| opacity: 0.65; |
| box-shadow: 6px 6px 0 var(--ink-faint); |
| } |
| .submit .glyph { color: var(--accent2); margin: 0 10px; } |
| |
| .status { |
| margin-top: 4px; |
| text-align: center; |
| min-height: 24px; |
| font-family: 'IBM Plex Mono', monospace; |
| font-size: 13px; |
| letter-spacing: 0.06em; |
| color: var(--ink-soft); |
| } |
| .status.error { color: var(--red); } |
| |
| |
| .output-wrap { |
| margin-top: 40px; |
| } |
| |
| @media (max-width: 600px) { |
| .page { padding: 24px 12px 60px; } |
| .panel { padding: 36px 22px 30px; } |
| .masthead h1 { font-size: 28px; } |
| } |
| </style> |
| </head> |
| <body> |
| <main class="page"> |
| <section class="panel" aria-labelledby="title"> |
| <span class="corner tl">✺</span> |
| <span class="corner tr">✺</span> |
| <span class="corner bl">✺</span> |
| <span class="corner br">✺</span> |
|
|
| <div class="masthead"> |
| <div class="tiny">★ Presented by Hugging Face ★ Anno MMXXVI ★</div> |
| <h1 id="title">Trace <span class="accent">Personality</span> Bulletin</h1> |
| <div class="rule"><span class="line"></span>✺ ✺ ✺<span class="line"></span></div> |
| <p class="lede"> |
| Drop a Hugging Face agent-traces dataset. The Roastery reads your |
| sessions and issues a printed bulletin of what the model thinks of you. Make sure to export your dataset with <a href="https://github.com/badlogic/pi-share-hf">this tool</a> |
| to remove secrets/PII before uploading the traces. |
| </p> |
| </div> |
|
|
| <form class="form" id="bulletin-form" autocomplete="off"> |
| <div> |
| <div class="field-label"> |
| <span>Dataset repo</span> |
| <span style="font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.2em;opacity:.6;">owner/name</span> |
| </div> |
| <input |
| id="repo" |
| class="repo-input" |
| type="text" |
| value="julien-c/pi-sessions" |
| spellcheck="false" |
| autocapitalize="off" |
| placeholder="owner/name" |
| /> |
| </div> |
|
|
| <div> |
| <div class="field-label"> |
| <span>Max sessions</span> |
| <span class="value" id="n-value">30</span> |
| </div> |
| <div class="slider-wrap"> |
| <input id="n" class="slider" type="range" min="1" max="50" step="1" value="30" /> |
| <div class="slider-ticks"> |
| <span>1</span><span>10</span><span>20</span><span>30</span><span>40</span><span>50</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="submit-row"> |
| <button class="submit" id="submit" type="submit"> |
| <span class="glyph">✺</span>Issue my bulletin<span class="glyph">✺</span> |
| </button> |
| </div> |
|
|
| <div class="status" id="status" role="status" aria-live="polite">Awaiting bulletin…</div> |
| </form> |
| </section> |
|
|
| <div class="output-wrap" id="output"></div> |
| </main> |
|
|
| <script type="module"> |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| const $ = (id) => document.getElementById(id); |
| const form = $("bulletin-form"); |
| const repoInput = $("repo"); |
| const nInput = $("n"); |
| const nValue = $("n-value"); |
| const submitBtn = $("submit"); |
| const statusEl = $("status"); |
| const outputEl = $("output"); |
| |
| function paintSlider() { |
| const min = +nInput.min, max = +nInput.max; |
| const pct = ((+nInput.value - min) * 100) / (max - min); |
| nInput.style.setProperty("--pct", pct + "%"); |
| nValue.textContent = nInput.value; |
| } |
| nInput.addEventListener("input", paintSlider); |
| paintSlider(); |
| |
| function setStatus(text, isError = false) { |
| statusEl.textContent = text || ""; |
| statusEl.classList.toggle("error", !!isError); |
| } |
| |
| let clientPromise = null; |
| function getClient() { |
| if (!clientPromise) clientPromise = Client.connect(window.location.origin); |
| return clientPromise; |
| } |
| |
| form.addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const repo_id = repoInput.value.trim(); |
| const max_sessions = parseInt(nInput.value, 10); |
| if (!repo_id) { |
| setStatus("Enter a dataset repo first.", true); |
| return; |
| } |
| submitBtn.disabled = true; |
| setStatus("Connecting…"); |
| outputEl.innerHTML = ""; |
| |
| try { |
| const client = await getClient(); |
| const job = client.submit("/generate_bulletin", { |
| repo_id, |
| max_sessions, |
| }); |
| |
| let lastHtml = ""; |
| for await (const msg of job) { |
| if (msg.type !== "data") continue; |
| const [status, html] = msg.data || []; |
| if (typeof status === "string") { |
| setStatus(status, status.startsWith("❌")); |
| } |
| if (typeof html === "string") { |
| lastHtml = html; |
| } |
| } |
| if (lastHtml) { |
| outputEl.innerHTML = lastHtml; |
| |
| outputEl.querySelectorAll("script").forEach((old) => { |
| const s = document.createElement("script"); |
| for (const a of old.attributes) s.setAttribute(a.name, a.value); |
| s.text = old.text; |
| old.replaceWith(s); |
| }); |
| } |
| } catch (err) { |
| console.error(err); |
| setStatus("❌ " + (err && err.message ? err.message : String(err)), true); |
| } finally { |
| submitBtn.disabled = false; |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|