GautamKishore commited on
Commit
00fb1fb
·
verified ·
1 Parent(s): 576ab03

Upload docs/demo.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. docs/demo.html +497 -0
docs/demo.html ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Pico-Type Demo — In-Browser Content Classifier</title>
7
+ <meta name="description" content="Try Pico-Type in your browser: a 1.5M-parameter byte-level multi-head classifier that identifies code language, content type, and more — no server needed." />
8
+ <meta property="og:title" content="Pico-Type — In-Browser Demo" />
9
+ <meta property="og:description" content="Classifies content into 7 categories from raw bytes. Runs entirely in your browser via ONNX Runtime Web." />
10
+ <link rel="icon" type="image/x-icon" href="favicon.ico" />
11
+ <link rel="icon" type="image/png" href="logo.png" />
12
+ <style>
13
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
14
+ :root {
15
+ --accent: #6366f1;
16
+ --accent-light: #818cf8;
17
+ --bg: #0f0f1a;
18
+ --bg-card: #1a1a2e;
19
+ --text: #e2e8f0;
20
+ --text-muted: #94a3b8;
21
+ --border: #2d2d4a;
22
+ --success: #34d399;
23
+ --warn: #fbbf24;
24
+ }
25
+ html { scroll-behavior: smooth; }
26
+ body {
27
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ line-height: 1.6;
31
+ -webkit-font-smoothing: antialiased;
32
+ min-height: 100vh;
33
+ display: flex;
34
+ flex-direction: column;
35
+ }
36
+ .container { max-width: 1100px; margin: 0 auto; padding: 0 24px; width: 100%; }
37
+
38
+ header {
39
+ padding: 24px 0;
40
+ border-bottom: 1px solid var(--border);
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 12px;
44
+ }
45
+ header img { height: 32px; }
46
+ header h1 { font-size: 20px; font-weight: 700; }
47
+ header h1 a { color: var(--text); text-decoration: none; }
48
+ header h1 a:hover { color: var(--accent-light); }
49
+ header .subtitle { color: var(--text-muted); font-size: 14px; margin-left: auto; }
50
+
51
+ .hero {
52
+ text-align: center;
53
+ padding: 48px 24px 32px;
54
+ }
55
+ .hero h2 { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
56
+ .hero p { color: var(--text-muted); font-size: 15px; max-width: 600px; margin: 0 auto; }
57
+ .hero .badge {
58
+ display: inline-block;
59
+ padding: 4px 12px;
60
+ border-radius: 999px;
61
+ background: rgba(99, 102, 241, 0.15);
62
+ color: var(--accent-light);
63
+ font-size: 12px;
64
+ font-weight: 500;
65
+ margin-bottom: 16px;
66
+ border: 1px solid rgba(99, 102, 241, 0.25);
67
+ }
68
+
69
+ .demo-grid {
70
+ display: grid;
71
+ grid-template-columns: 1fr 1fr;
72
+ gap: 24px;
73
+ padding: 0 24px 48px;
74
+ max-width: 1100px;
75
+ margin: 0 auto;
76
+ width: 100%;
77
+ }
78
+
79
+ .input-panel {
80
+ background: var(--bg-card);
81
+ border: 1px solid var(--border);
82
+ border-radius: 14px;
83
+ padding: 24px;
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 16px;
87
+ }
88
+ .input-panel label { font-size: 14px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
89
+ .input-panel textarea {
90
+ width: 100%;
91
+ min-height: 220px;
92
+ background: var(--bg);
93
+ border: 1px solid var(--border);
94
+ border-radius: 10px;
95
+ padding: 16px;
96
+ color: var(--text);
97
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
98
+ font-size: 13px;
99
+ line-height: 1.5;
100
+ resize: vertical;
101
+ }
102
+ .input-panel textarea:focus { outline: none; border-color: var(--accent); }
103
+ .input-panel textarea::placeholder { color: #475569; }
104
+
105
+ .controls {
106
+ display: flex;
107
+ gap: 12px;
108
+ align-items: center;
109
+ flex-wrap: wrap;
110
+ }
111
+ .controls select {
112
+ background: var(--bg);
113
+ border: 1px solid var(--border);
114
+ border-radius: 8px;
115
+ padding: 10px 14px;
116
+ color: var(--text);
117
+ font-size: 14px;
118
+ cursor: pointer;
119
+ }
120
+ .controls select:focus { outline: none; border-color: var(--accent); }
121
+ .btn {
122
+ display: inline-flex; align-items: center; gap: 6px;
123
+ padding: 10px 24px; border-radius: 8px; font-size: 14px; font-weight: 600;
124
+ border: none; cursor: pointer; transition: all 0.2s;
125
+ }
126
+ .btn-primary { background: var(--accent); color: #fff; }
127
+ .btn-primary:hover { background: #4f46e5; }
128
+ .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
129
+ .btn-outline {
130
+ background: transparent; color: var(--text); border: 1px solid var(--border);
131
+ }
132
+ .btn-outline:hover { border-color: var(--accent); }
133
+
134
+ .status {
135
+ font-size: 13px;
136
+ color: var(--text-muted);
137
+ padding: 8px 12px;
138
+ background: var(--bg);
139
+ border-radius: 8px;
140
+ }
141
+ .status.loading { color: var(--accent-light); }
142
+ .status.ready { color: var(--success); }
143
+ .status.error { color: #f87171; }
144
+ .status .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--border); border-top-color: var(--accent-light); border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: middle; margin-right: 6px; }
145
+ @keyframes spin { to { transform: rotate(360deg); } }
146
+
147
+ .results-panel {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: 12px;
151
+ }
152
+ .result-card {
153
+ background: var(--bg-card);
154
+ border: 1px solid var(--border);
155
+ border-radius: 12px;
156
+ padding: 16px;
157
+ }
158
+ .result-card .head-name {
159
+ font-size: 12px;
160
+ font-weight: 600;
161
+ text-transform: uppercase;
162
+ letter-spacing: 0.05em;
163
+ color: var(--text-muted);
164
+ margin-bottom: 4px;
165
+ }
166
+ .result-card .prediction {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 12px;
170
+ }
171
+ .result-card .label {
172
+ font-size: 18px;
173
+ font-weight: 700;
174
+ color: var(--accent-light);
175
+ }
176
+ .result-card .confidence {
177
+ font-size: 13px;
178
+ color: var(--text-muted);
179
+ }
180
+ .result-card .bar {
181
+ margin-top: 6px;
182
+ height: 4px;
183
+ background: var(--bg);
184
+ border-radius: 2px;
185
+ overflow: hidden;
186
+ }
187
+ .result-card .bar-fill {
188
+ height: 100%;
189
+ background: var(--accent);
190
+ border-radius: 2px;
191
+ transition: width 0.3s ease;
192
+ }
193
+ .result-card.risk .label { font-size: 14px; }
194
+ .result-card.risk .risk-item {
195
+ display: flex;
196
+ justify-content: space-between;
197
+ padding: 3px 0;
198
+ font-size: 13px;
199
+ }
200
+ .result-card.risk .risk-item .rlabel { color: var(--text-muted); }
201
+ .result-card.risk .risk-item .rscore { color: var(--accent-light); font-weight: 600; }
202
+
203
+ .empty-state {
204
+ text-align: center;
205
+ padding: 48px 24px;
206
+ color: var(--text-muted);
207
+ }
208
+ .empty-state .icon { font-size: 40px; margin-bottom: 12px; }
209
+ .empty-state p { font-size: 14px; }
210
+
211
+ .examples {
212
+ display: flex;
213
+ flex-wrap: wrap;
214
+ gap: 8px;
215
+ }
216
+ .examples button {
217
+ background: var(--bg);
218
+ border: 1px solid var(--border);
219
+ border-radius: 6px;
220
+ padding: 6px 12px;
221
+ color: var(--text-muted);
222
+ font-size: 12px;
223
+ cursor: pointer;
224
+ transition: all 0.2s;
225
+ }
226
+ .examples button:hover { border-color: var(--accent); color: var(--text); }
227
+
228
+ footer {
229
+ text-align: center;
230
+ padding: 24px;
231
+ border-top: 1px solid var(--border);
232
+ color: var(--text-muted);
233
+ font-size: 13px;
234
+ margin-top: auto;
235
+ }
236
+ footer a { color: var(--accent-light); text-decoration: none; }
237
+
238
+ @media (max-width: 768px) {
239
+ .demo-grid { grid-template-columns: 1fr; }
240
+ .hero h2 { font-size: 22px; }
241
+ header .subtitle { display: none; }
242
+ }
243
+ </style>
244
+ </head>
245
+ <body>
246
+
247
+ <header>
248
+ <div class="container" style="display:flex;align-items:center;gap:12px;">
249
+ <a href="https://github.com/eulogik/pico-type" target="_blank" rel="noopener" style="display:flex;">
250
+ <img src="logo.png" alt="pico-type logo" style="height:32px;" />
251
+ </a>
252
+ <h1><a href="https://github.com/eulogik/pico-type" target="_blank" rel="noopener">pico-type</a></h1>
253
+ <span class="subtitle">In-browser inference via ONNX Runtime Web</span>
254
+ </div>
255
+ </header>
256
+
257
+ <div class="hero">
258
+ <div class="badge">&#9889; Runs entirely in your browser &mdash; no server</div>
259
+ <h2>Try Pico-Type</h2>
260
+ <p>Paste any text, code, or file content below. The model classifies it into 7 categories from raw bytes, right in your browser.</p>
261
+ </div>
262
+
263
+ <div class="demo-grid">
264
+ <div>
265
+ <div class="input-panel">
266
+ <label for="input-text">Input Content</label>
267
+ <textarea id="input-text" placeholder="Paste or type content to classify... (up to 1024 bytes)"></textarea>
268
+
269
+ <div class="controls">
270
+ <select id="tier-select">
271
+ <option value="tiny">Tiny (16d)</option>
272
+ <option value="small">Small (64d)</option>
273
+ <option value="base" selected>Base (192d)</option>
274
+ <option value="pro">Pro (576d)</option>
275
+ </select>
276
+ <button class="btn btn-primary" id="classify-btn" disabled>Classify</button>
277
+ <button class="btn btn-outline" id="clear-btn">Clear</button>
278
+ </div>
279
+
280
+ <div class="status" id="load-status">
281
+ <span class="spinner"></span>
282
+ Loading model (~9MB)... <span id="load-progress">0%</span>
283
+ </div>
284
+
285
+ <div class="examples">
286
+ <button data-text='def hello():\n print("Hello, world!")'>Python</button>
287
+ <button data-text='<html><body><h1>Hi</h1></body></html>'>HTML</button>
288
+ <button data-text='{"name": "pico", "version": "0.1.9"}'>JSON</button>
289
+ <button data-text='The quick brown fox jumps over the lazy dog.'>English</button>
290
+ <button data-text='fn main() {\n println!("Hello!");\n}'>Rust</button>
291
+ <button data-text='#!/bin/bash\necho "hello"'>Bash</button>
292
+ <button data-text='SELECT * FROM users WHERE id = 1;'>SQL</button>
293
+ </div>
294
+ </div>
295
+ </div>
296
+
297
+ <div>
298
+ <div class="results-panel" id="results">
299
+ <div class="empty-state">
300
+ <div class="icon">&#128225;</div>
301
+ <p>Enter content and click <strong>Classify</strong> to see results across 7 heads.</p>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ <footer>
308
+ <p>
309
+ <a href="https://eulogik.com" target="_blank" rel="noopener">Eulogik</a>
310
+ &nbsp;&middot;&nbsp;
311
+ <a href="https://github.com/eulogik/pico-type" target="_blank" rel="noopener">GitHub</a>
312
+ &nbsp;&middot;&nbsp;
313
+ <a href="https://pypi.org/project/pico-type/" target="_blank" rel="noopener">PyPI</a>
314
+ &nbsp;&middot;&nbsp;
315
+ <a href="https://huggingface.co/eulogik/pico-type" target="_blank" rel="noopener">HuggingFace</a>
316
+ </p>
317
+ </footer>
318
+
319
+ <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/ort.min.js"></script>
320
+ <script>
321
+ ort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
322
+
323
+ const LABELS = {
324
+ coarse: ["text","code","link","image","file","config","markup","data","error","secret","archive","binary"],
325
+ modality: ["textual","binary_image","binary_archive","binary_executable","binary_document","binary_audio","binary_video","binary_other"],
326
+ subtype: ["json","yaml","toml","ini","csv","tsv","xml","html","markdown","rst","asciidoc","tex","sql","graphql","protobuf","msgpack","log","diff","patch","env","shell","makefile","dockerfile","gitignore"],
327
+ code_lang: ["python","javascript","typescript","jsx","tsx","java","kotlin","scala","groovy","clojure","c","cpp","csharp","fsharp","objectivec","go","rust","zig","ruby","php","perl","lua","tcl","swift","dart","julia","nim","crystal","haskell","ocaml","elm","erlang","elixir","lisp","scheme","racket","r","matlab","octave","sas","stata","sql","plsql","tsql","html","css","scss","sass","less","bash","zsh","fish","powershell","vim","fortran","cobol","ada","pascal","delphi","vb","prolog","vhdl"],
328
+ text_lang: ["en","es","fr","de","it","pt","nl","sv","no","da","fi","pl","cs","sk","hu","ro","el","tr","ru","uk","bg","sr","hr","zh","ja","ko","vi","th","id","hi"],
329
+ file_mime: ["application/pdf","application/zip","application/gzip","application/x-tar","application/x-7z-compressed","application/x-rar-compressed","application/x-bzip2","application/x-xz","application/json","application/xml","application/yaml","application/octet-stream","application/x-executable","application/x-mach-binary","application/x-elf","application/x-deb","application/x-rpm","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application/vnd.openxmlformats-officedocument.presentationml.presentation","application/vnd.ms-excel","application/vnd.ms-powerpoint","application/msword","application/rtf","application/epub+zip","application/x-ndjson","text/plain","text/csv","text/html","text/xml","text/markdown","image/png","image/jpeg","image/gif","image/webp","image/svg+xml","image/bmp","image/tiff","image/heic","image/heif","image/avif","image/x-icon","image/vnd.adobe.photoshop","video/mp4","video/webm","video/x-matroska","video/quicktime","video/x-msvideo","video/x-flv","video/x-mpeg","audio/mpeg","audio/ogg","audio/wav","audio/flac","audio/aac","audio/x-m4a","audio/webm","audio/midi","font/ttf","font/otf","font/woff","font/woff2","application/x-sqlite3","application/x-parquet","application/x-protobuf","application/x-flatbuffers","application/x-cpio","application/x-iso9660-image","application/vnd.android.package-archive","application/x-jar","application/x-python-bytecode","application/x-archive","application/pgp-encrypted","application/pgp-signature","application/x-x509-ca-cert","application/x-pem-file","application/vnd.tcpdump.pcap","application/java-vm","application/x-matlab-data","application/x-shockwave-flash","application/x-font-ttf","application/x-font-otf","application/wasm","application/x-ruby","application/javascript","application/ecmascript","application/x-bittorrent","application/x-dvi","chemical/x-mdl-sdfile","application/x-lzma"],
330
+ risk: ["api_key","jwt","ssh_key","password","email","phone"]
331
+ };
332
+ const HEAD_NAMES = {coarse:"Coarse Type",modality:"Modality",subtype:"Subtype",code_lang:"Code Language",text_lang:"Text Language",file_mime:"File MIME",risk:"Risk Flags"};
333
+ const HEADS = ["coarse","modality","subtype","code_lang","text_lang","file_mime","risk"];
334
+
335
+ let session = null;
336
+ let isLoading = false;
337
+
338
+ function softmax(logits) {
339
+ const max = logits.reduce((a,b) => Math.max(a,b), -Infinity);
340
+ const exps = logits.map(x => Math.exp(x - max));
341
+ const sum = exps.reduce((a,b) => a + b, 0);
342
+ return exps.map(x => x / sum);
343
+ }
344
+
345
+ function textToIds(text) {
346
+ const encoder = new TextEncoder();
347
+ const bytes = encoder.encode(text).slice(0, 1024);
348
+ const ids = new Array(1024).fill(0);
349
+ const mask = new Array(1024).fill(false);
350
+ for (let i = 0; i < bytes.length; i++) {
351
+ ids[i] = bytes[i];
352
+ mask[i] = true;
353
+ }
354
+ return { ids, mask };
355
+ }
356
+
357
+ async function loadModel(tier) {
358
+ const statusEl = document.getElementById('load-status');
359
+ const btn = document.getElementById('classify-btn');
360
+ const progress = document.getElementById('load-progress');
361
+
362
+ if (session) {
363
+ try { session.release(); } catch(e) {}
364
+ session = null;
365
+ }
366
+
367
+ statusEl.className = 'status loading';
368
+ statusEl.innerHTML = '<span class="spinner"></span> Loading model (~9MB)... <span id="load-progress">0%</span>';
369
+ btn.disabled = true;
370
+
371
+ const modelUrl = `https://huggingface.co/eulogik/pico-type/resolve/main/picotype_${tier}.onnx`;
372
+
373
+ try {
374
+ session = await ort.InferenceSession.create(modelUrl, {
375
+ executionProviders: ['wasm'],
376
+ enableProfiling: false,
377
+ logSeverityLevel: 3,
378
+ });
379
+ statusEl.className = 'status ready';
380
+ statusEl.textContent = `\u2705 Model loaded (${tier}) \u2014 ready to classify`;
381
+ btn.disabled = false;
382
+ } catch (err) {
383
+ statusEl.className = 'status error';
384
+ statusEl.textContent = `\u274c Failed to load: ${err.message}`;
385
+ btn.disabled = true;
386
+ }
387
+ }
388
+
389
+ async function classify() {
390
+ if (!session) return;
391
+ const textEl = document.getElementById('input-text');
392
+ const text = textEl.value;
393
+ if (!text.trim()) return;
394
+
395
+ const btn = document.getElementById('classify-btn');
396
+ btn.disabled = true;
397
+ btn.textContent = 'Classifying...';
398
+
399
+ try {
400
+ const { ids, mask } = textToIds(text);
401
+ const inputIds = new ort.Tensor('int64', ids, [1, 1024]);
402
+ const attnMask = new ort.Tensor('bool', mask, [1, 1024]);
403
+
404
+ const feeds = { input_ids: inputIds, attention_mask: attnMask };
405
+ const results = await session.run(feeds);
406
+
407
+ const resultData = {};
408
+ for (const head of HEADS) {
409
+ const logits = results[head].data;
410
+ const probs = softmax(Array.from(logits));
411
+ const labels = LABELS[head];
412
+ resultData[head] = labels.map((l, i) => ({ label: l, prob: probs[i] }))
413
+ .sort((a, b) => b.prob - a.prob);
414
+ }
415
+
416
+ renderResults(resultData);
417
+ } catch (err) {
418
+ const resultsEl = document.getElementById('results');
419
+ resultsEl.innerHTML = `<div class="result-card" style="text-align:center;padding:24px;color:#f87171;">
420
+ <div style="font-size:24px;margin-bottom:8px;">\u274c</div>
421
+ <p>Inference failed: ${err.message}</p>
422
+ </div>`;
423
+ } finally {
424
+ btn.disabled = false;
425
+ btn.textContent = 'Classify';
426
+ }
427
+ }
428
+
429
+ function renderResults(data) {
430
+ const el = document.getElementById('results');
431
+ let html = '';
432
+ for (const head of HEADS) {
433
+ const items = data[head];
434
+ const top = items[0];
435
+ const isRisk = head === 'risk';
436
+
437
+ html += `<div class="result-card${isRisk ? ' risk' : ''}">`;
438
+ html += `<div class="head-name">${HEAD_NAMES[head]}</div>`;
439
+
440
+ if (isRisk) {
441
+ html += '<div>';
442
+ for (const item of items) {
443
+ html += `<div class="risk-item"><span class="rlabel">${item.label}</span><span class="rscore">${(item.prob * 100).toFixed(1)}%</span></div>`;
444
+ }
445
+ html += '</div>';
446
+ } else {
447
+ html += `<div class="prediction">`;
448
+ html += `<span class="label">${top.label}</span>`;
449
+ html += `<span class="confidence">${(top.prob * 100).toFixed(1)}%</span>`;
450
+ html += `</div>`;
451
+ html += `<div class="bar"><div class="bar-fill" style="width:${(top.prob * 100).toFixed(1)}%"></div></div>`;
452
+ }
453
+
454
+ html += `</div>`;
455
+ }
456
+ el.innerHTML = html;
457
+ }
458
+
459
+ document.addEventListener('DOMContentLoaded', () => {
460
+ const textEl = document.getElementById('input-text');
461
+ const tierSelect = document.getElementById('tier-select');
462
+ const classifyBtn = document.getElementById('classify-btn');
463
+ const clearBtn = document.getElementById('clear-btn');
464
+
465
+ // Example buttons
466
+ document.querySelectorAll('.examples button').forEach(btn => {
467
+ btn.addEventListener('click', () => {
468
+ textEl.value = btn.dataset.text;
469
+ });
470
+ });
471
+
472
+ classifyBtn.addEventListener('click', classify);
473
+
474
+ clearBtn.addEventListener('click', () => {
475
+ textEl.value = '';
476
+ document.getElementById('results').innerHTML = `<div class="empty-state">
477
+ <div class="icon">&#128225;</div>
478
+ <p>Enter content and click <strong>Classify</strong> to see results across 7 heads.</p>
479
+ </div>`;
480
+ });
481
+
482
+ textEl.addEventListener('keydown', (e) => {
483
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
484
+ classify();
485
+ }
486
+ });
487
+
488
+ tierSelect.addEventListener('change', () => {
489
+ loadModel(tierSelect.value);
490
+ });
491
+
492
+ // Start loading the default model
493
+ loadModel(tierSelect.value);
494
+ });
495
+ </script>
496
+ </body>
497
+ </html>