jobtracker-classifier / index.html
yadava5's picture
Upload index.html with huggingface_hub
bb42ea8 verified
Raw
History Blame Contribute Delete
7.85 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#0a0a0b" />
<title>JobTracker classifier — runs in your browser</title>
<meta name="description" content="A fine-tuned 3-layer email classifier — rules, e5 embeddings, SetFit — running entirely in your browser via quantized ONNX. No server. 0.979 macro-F1, CI-gated at 0.95." />
<style>
:root{color-scheme:dark;--bg:#0a0a0b;--surface:#0f1011;--surface2:#141517;--fg:#d6d8db;--strong:#f7f8f8;--muted:#8a8f98;--dim:#63666c;--line:rgb(255 255 255/.09);--line-soft:rgb(255 255 255/.055);--line-strong:rgb(255 255 255/.2);--green:#4ade80;--amber:#f59e0b;--mono:ui-monospace,'SF Mono',Menlo,monospace}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;-webkit-font-smoothing:antialiased}
.wrap{max-width:1060px;margin:0 auto;padding:0 20px 60px}
header{display:flex;align-items:center;justify-content:space-between;height:56px;border-bottom:1px solid var(--line-soft)}
.brand{font-family:var(--mono);font-weight:600;font-size:.9rem;color:var(--strong)}
.brand em{font-style:normal;color:var(--dim)}
.pill{display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--line);border-radius:999px;padding:.3rem .8rem;font-family:var(--mono);font-size:.7rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
.dot{width:6px;height:6px;border-radius:99px;background:var(--dim)}
.dot[data-on]{background:var(--green);box-shadow:0 0 8px rgb(74 222 128/.5)}
h1{margin:2.4rem 0 .6rem;font-size:clamp(1.8rem,4.5vw,2.6rem);letter-spacing:-.02em;color:var(--strong);text-wrap:balance}
h1 span{color:var(--muted)}
.sub{margin:0 0 1.8rem;max-width:640px;color:var(--muted);line-height:1.6}
.label{font-family:var(--mono);font-size:.66rem;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.frame{border:1px solid var(--line);border-radius:14px;background:var(--surface);overflow:hidden;box-shadow:0 28px 110px rgb(0 0 0/.5)}
.titlebar{display:flex;flex-wrap:wrap;gap:.5rem 1rem;align-items:center;justify-content:space-between;padding:.7rem 1.1rem;border-bottom:1px solid var(--line-soft);background:rgb(19 20 22/.65);font-family:var(--mono);font-size:.74rem;color:var(--muted)}
.grid{display:grid;grid-template-columns:1.1fr .9fr}
.grid>div{padding:1.3rem}
.grid>div:first-child{border-right:1px solid var(--line-soft)}
input,textarea{width:100%;border:1px solid var(--line);border-radius:10px;background:#0c0d0e;color:var(--strong);padding:.65rem .8rem;font:inherit;font-size:.92rem}
textarea{min-height:150px;resize:vertical}
input:focus,textarea:focus{outline:none;border-color:var(--line-strong)}
.btn{display:inline-flex;align-items:center;gap:.4rem;margin-top:.8rem;border:0;border-radius:9px;background:linear-gradient(180deg,#f7f8f8,#e2e2e2);color:#0a0a0b;font-weight:600;font-size:.92rem;padding:.6rem 1.2rem;cursor:pointer;transition:transform .16s}
.btn:hover{transform:translateY(-1px)}
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
.ghost{display:inline-block;border:1px solid var(--line);border-radius:8px;background:none;color:var(--muted);font-family:var(--mono);font-size:.68rem;letter-spacing:.04em;text-transform:uppercase;padding:.4rem .7rem;margin:.25rem .25rem 0 0;cursor:pointer;transition:color .16s,border-color .16s}
.ghost:hover{color:var(--strong);border-color:var(--line-strong)}
.verdict{display:grid;place-items:center;gap:.2rem;min-height:7.4rem;border:1px solid var(--line-soft);border-radius:10px;background:rgb(255 255 255/.02);padding:1rem;text-align:center}
.verdict b{font-size:1.9rem;letter-spacing:-.02em;color:var(--strong);text-transform:capitalize}
.verdict small{font-family:var(--mono);font-size:.78rem;color:var(--muted)}
.trace{display:grid;gap:.4rem;margin-top:1rem}
.trow{display:flex;align-items:baseline;gap:.6rem;border:1px solid var(--line-soft);border-radius:9px;padding:.55rem .8rem;font-family:var(--mono);font-size:.76rem;color:var(--muted)}
.trow b{color:var(--strong);font-weight:600}
.trow[data-state=answered]{border-color:rgb(74 222 128/.35)}
.trow[data-state=answered] .st{color:var(--green)}
.trow[data-state=skipped]{opacity:.55}
.trow .ms{margin-left:auto;color:var(--dim)}
.review{color:var(--amber)}
.progress{height:4px;border-radius:99px;background:rgb(255 255 255/.06);overflow:hidden;margin-top:.6rem}
.progress i{display:block;height:100%;width:0;background:var(--strong);transition:width .2s}
.facts{display:grid;grid-template-columns:repeat(4,1fr);border-top:1px solid var(--line-soft);background:rgb(19 20 22/.65)}
.facts div{padding:.8rem 1.1rem;border-right:1px solid var(--line-soft)}
.facts div:last-child{border-right:0}
.facts dt{font-family:var(--mono);font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.facts dd{margin:.2rem 0 0;font-family:var(--mono);font-size:.8rem;font-weight:600;color:var(--strong)}
footer{margin-top:2.2rem;font-family:var(--mono);font-size:.7rem;color:var(--dim);line-height:1.8}
footer a{color:var(--muted)}
@media(max-width:760px){.grid{grid-template-columns:1fr}.grid>div:first-child{border-right:0;border-bottom:1px solid var(--line-soft)}.facts{grid-template-columns:repeat(2,1fr)}}
</style>
</head>
<body>
<div class="wrap">
<header>
<span class="brand">job<em>_</em>tracker <em>/</em> classifier</span>
<span class="pill"><i class="dot" id="dot"></i><span id="state">loading model</span></span>
</header>
<h1>The classifier left the server.<br/><span>It runs here, in your browser.</span></h1>
<p class="sub">The 3-layer pipeline behind JobTracker — regex rules, fine-tuned e5 embeddings, a SetFit head —
ported to quantized ONNX and executing on your machine. Nothing you paste leaves this page. Verified
output-identical to the Python pipeline; 0.979 macro-F1 on the committed eval set, CI-gated at 0.95.</p>
<div class="frame">
<div class="titlebar">
<span>paste an email · classified locally</span>
<span id="timing"></span>
</div>
<div class="grid">
<div>
<div class="label" style="margin-bottom:.5rem">01 · email</div>
<input id="subject" placeholder="Subject — e.g. Interview availability" />
<div style="height:.6rem"></div>
<textarea id="body" placeholder="Body — paste any job-pipeline email…"></textarea>
<div class="progress" id="prog" hidden><i id="progbar"></i></div>
<button class="btn" id="go" disabled>Classify</button>
<div style="margin-top:.9rem" class="label">or a synthetic example</div>
<div id="examples"></div>
</div>
<div>
<div class="label" style="margin-bottom:.5rem">02 · verdict</div>
<div class="verdict" id="verdict"><b style="opacity:.4">?</b><small>the model downloads once (22.8MB), then everything is local</small></div>
<div class="label" style="margin:1rem 0 .4rem">03 · three layers, in order</div>
<div class="trace" id="trace"></div>
</div>
</div>
<dl class="facts">
<div><dt>macro-F1</dt><dd>0.979 / gate 0.95</dd></div>
<div><dt>model</dt><dd>e5-small · int8</dd></div>
<div><dt>weights</dt><dd>22.8MB once</dd></div>
<div><dt>server calls</dt><dd>zero</dd></div>
</dl>
</div>
<footer>
Same rules, same fine-tuned embeddings, same SetFit head as production — exported, quantized, and verified
output-identical (6/6 agreement suite). Model card: <a href="https://huggingface.co/yadava5/jobtracker-setfit-classifier">yadava5/jobtracker-setfit-classifier</a>.
Part of JobTracker by Ayush Yadav · no inbox is read, nothing leaves your browser.
</footer>
</div>
<script type="module" src="./app.js"></script>
</body>
</html>