Spaces:
Sleeping
Sleeping
| <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; | |
| } | |
| /* Faint dotted texture across the whole page */ | |
| 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; | |
| } | |
| /* ---- Input panel ("retro newsprint card") ---- */ | |
| .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 ---- */ | |
| .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); | |
| } | |
| /* Text input — looks like a typewritten form line */ | |
| .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 — custom retro track + dial */ | |
| .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 button */ | |
| .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 area */ | |
| .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; | |
| // Execute inline scripts injected by the response (bulletin uses iframe srcdoc, so usually a no-op). | |
| 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> | |