av-codes commited on
Commit
875ab20
·
verified ·
1 Parent(s): f929c62

Static space with in-browser Transformers.js inference

Browse files
Files changed (3) hide show
  1. README.md +6 -6
  2. index.html +469 -18
  3. worker.js +92 -0
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: Supra 50m Instruct
3
- emoji: 👀
4
- colorFrom: green
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Supra 50M Instruct
3
+ emoji:
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ license: apache-2.0
9
+ short_description: In-browser inference with Transformers.js
10
  ---
 
 
index.html CHANGED
@@ -1,19 +1,470 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>Supra 50M Instruct</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12
+ background: #0f0f13;
13
+ color: #e4e4e7;
14
+ height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ }
18
+
19
+ header {
20
+ padding: 16px 24px;
21
+ border-bottom: 1px solid #27272a;
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 12px;
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ header h1 {
29
+ font-size: 18px;
30
+ font-weight: 600;
31
+ }
32
+
33
+ .badge {
34
+ background: #3b0764;
35
+ color: #c084fc;
36
+ font-size: 11px;
37
+ padding: 2px 8px;
38
+ border-radius: 9999px;
39
+ font-weight: 500;
40
+ }
41
+
42
+ #status-bar {
43
+ padding: 8px 24px;
44
+ font-size: 13px;
45
+ color: #a1a1aa;
46
+ border-bottom: 1px solid #27272a;
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 8px;
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ #progress-bar {
54
+ flex: 1;
55
+ max-width: 300px;
56
+ height: 4px;
57
+ background: #27272a;
58
+ border-radius: 2px;
59
+ overflow: hidden;
60
+ display: none;
61
+ }
62
+
63
+ #progress-fill {
64
+ height: 100%;
65
+ background: #8b5cf6;
66
+ width: 0%;
67
+ transition: width 0.2s;
68
+ }
69
+
70
+ #chat {
71
+ flex: 1;
72
+ overflow-y: auto;
73
+ padding: 24px;
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 16px;
77
+ }
78
+
79
+ .message {
80
+ max-width: 720px;
81
+ width: 100%;
82
+ margin: 0 auto;
83
+ }
84
+
85
+ .message-user {
86
+ background: #18181b;
87
+ border: 1px solid #27272a;
88
+ border-radius: 12px;
89
+ padding: 12px 16px;
90
+ }
91
+
92
+ .message-user .label {
93
+ font-size: 11px;
94
+ text-transform: uppercase;
95
+ letter-spacing: 0.05em;
96
+ color: #8b5cf6;
97
+ margin-bottom: 4px;
98
+ font-weight: 600;
99
+ }
100
+
101
+ .message-assistant {
102
+ background: transparent;
103
+ padding: 4px 0;
104
+ }
105
+
106
+ .message-assistant .label {
107
+ font-size: 11px;
108
+ text-transform: uppercase;
109
+ letter-spacing: 0.05em;
110
+ color: #22c55e;
111
+ margin-bottom: 4px;
112
+ font-weight: 600;
113
+ }
114
+
115
+ .message-text {
116
+ font-size: 15px;
117
+ line-height: 1.6;
118
+ white-space: pre-wrap;
119
+ word-break: break-word;
120
+ }
121
+
122
+ .cursor {
123
+ display: inline-block;
124
+ width: 2px;
125
+ height: 1em;
126
+ background: #8b5cf6;
127
+ margin-left: 2px;
128
+ vertical-align: text-bottom;
129
+ animation: blink 0.8s infinite;
130
+ }
131
+
132
+ @keyframes blink {
133
+ 0%, 50% { opacity: 1; }
134
+ 51%, 100% { opacity: 0; }
135
+ }
136
+
137
+ #input-area {
138
+ padding: 16px 24px;
139
+ border-top: 1px solid #27272a;
140
+ flex-shrink: 0;
141
+ }
142
+
143
+ #input-row {
144
+ max-width: 720px;
145
+ margin: 0 auto;
146
+ display: flex;
147
+ gap: 8px;
148
+ }
149
+
150
+ #prompt-input {
151
+ flex: 1;
152
+ background: #18181b;
153
+ border: 1px solid #27272a;
154
+ border-radius: 8px;
155
+ padding: 10px 14px;
156
+ color: #e4e4e7;
157
+ font-size: 15px;
158
+ font-family: inherit;
159
+ resize: none;
160
+ outline: none;
161
+ min-height: 44px;
162
+ max-height: 120px;
163
+ }
164
+
165
+ #prompt-input:focus { border-color: #8b5cf6; }
166
+ #prompt-input::placeholder { color: #52525b; }
167
+
168
+ #send-btn {
169
+ background: #7c3aed;
170
+ color: white;
171
+ border: none;
172
+ border-radius: 8px;
173
+ padding: 10px 20px;
174
+ font-size: 14px;
175
+ font-weight: 500;
176
+ cursor: pointer;
177
+ white-space: nowrap;
178
+ align-self: flex-end;
179
+ }
180
+
181
+ #send-btn:hover { background: #6d28d9; }
182
+ #send-btn:disabled { background: #3f3f46; color: #71717a; cursor: not-allowed; }
183
+
184
+ #stop-btn {
185
+ background: #dc2626;
186
+ color: white;
187
+ border: none;
188
+ border-radius: 8px;
189
+ padding: 10px 16px;
190
+ font-size: 14px;
191
+ font-weight: 500;
192
+ cursor: pointer;
193
+ white-space: nowrap;
194
+ align-self: flex-end;
195
+ display: none;
196
+ }
197
+
198
+ #stop-btn:hover { background: #b91c1c; }
199
+
200
+ #settings-toggle {
201
+ background: none;
202
+ border: 1px solid #27272a;
203
+ color: #a1a1aa;
204
+ border-radius: 8px;
205
+ padding: 10px 12px;
206
+ cursor: pointer;
207
+ font-size: 14px;
208
+ align-self: flex-end;
209
+ }
210
+
211
+ #settings-toggle:hover { border-color: #8b5cf6; color: #e4e4e7; }
212
+
213
+ #settings-panel {
214
+ display: none;
215
+ max-width: 720px;
216
+ margin: 8px auto 0;
217
+ padding: 12px 16px;
218
+ background: #18181b;
219
+ border: 1px solid #27272a;
220
+ border-radius: 8px;
221
+ }
222
+
223
+ #settings-panel.open { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
224
+
225
+ .setting {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 4px;
229
+ }
230
+
231
+ .setting label {
232
+ font-size: 12px;
233
+ color: #a1a1aa;
234
+ }
235
+
236
+ .setting input {
237
+ background: #0f0f13;
238
+ border: 1px solid #27272a;
239
+ border-radius: 4px;
240
+ padding: 6px 8px;
241
+ color: #e4e4e7;
242
+ font-size: 13px;
243
+ width: 100%;
244
+ }
245
+
246
+ .welcome {
247
+ text-align: center;
248
+ margin: auto;
249
+ max-width: 480px;
250
+ }
251
+
252
+ .welcome h2 {
253
+ font-size: 24px;
254
+ margin-bottom: 8px;
255
+ }
256
+
257
+ .welcome p {
258
+ font-size: 14px;
259
+ color: #71717a;
260
+ line-height: 1.5;
261
+ }
262
+
263
+ footer {
264
+ padding: 8px;
265
+ text-align: center;
266
+ font-size: 11px;
267
+ color: #52525b;
268
+ flex-shrink: 0;
269
+ }
270
+
271
+ footer a { color: #8b5cf6; text-decoration: none; }
272
+ </style>
273
+ </head>
274
+ <body>
275
+ <header>
276
+ <h1>Supra 50M Instruct</h1>
277
+ <span class="badge">ONNX / In-Browser</span>
278
+ </header>
279
+
280
+ <div id="status-bar">
281
+ <span id="status-text">Initializing...</span>
282
+ <div id="progress-bar"><div id="progress-fill"></div></div>
283
+ </div>
284
+
285
+ <div id="chat">
286
+ <div class="welcome">
287
+ <h2>Supra 50M</h2>
288
+ <p>A tiny instruction-tuned LLM running entirely in your browser via ONNX Runtime + Transformers.js. No server needed.</p>
289
+ </div>
290
+ </div>
291
+
292
+ <div id="input-area">
293
+ <div id="input-row">
294
+ <textarea id="prompt-input" placeholder="Ask something..." rows="1" disabled></textarea>
295
+ <button id="settings-toggle" title="Settings">&#9881;</button>
296
+ <button id="send-btn" disabled>Send</button>
297
+ <button id="stop-btn">Stop</button>
298
+ </div>
299
+ <div id="settings-panel">
300
+ <div class="setting">
301
+ <label>Max tokens</label>
302
+ <input type="number" id="param-max-tokens" value="256" min="1" max="512" />
303
+ </div>
304
+ <div class="setting">
305
+ <label>Temperature</label>
306
+ <input type="number" id="param-temperature" value="0.7" min="0" max="2" step="0.1" />
307
+ </div>
308
+ <div class="setting">
309
+ <label>Top-K</label>
310
+ <input type="number" id="param-top-k" value="50" min="1" max="200" />
311
+ </div>
312
+ <div class="setting">
313
+ <label>Repetition penalty</label>
314
+ <input type="number" id="param-rep-penalty" value="1.15" min="1" max="2" step="0.05" />
315
+ </div>
316
+ </div>
317
+ </div>
318
+
319
+ <footer>
320
+ Powered by <a href="https://huggingface.co/docs/transformers.js" target="_blank">Transformers.js</a>
321
+ &middot; Model: <a href="https://huggingface.co/SupraLabs/Supra-50M-Instruct" target="_blank">SupraLabs/Supra-50M-Instruct</a>
322
+ </footer>
323
+
324
+ <script type="module">
325
+ const worker = new Worker("worker.js", { type: "module" });
326
+
327
+ const chatEl = document.getElementById("chat");
328
+ const inputEl = document.getElementById("prompt-input");
329
+ const sendBtn = document.getElementById("send-btn");
330
+ const statusText = document.getElementById("status-text");
331
+ const progressBar = document.getElementById("progress-bar");
332
+ const progressFill = document.getElementById("progress-fill");
333
+ const stopBtn = document.getElementById("stop-btn");
334
+ const settingsToggle = document.getElementById("settings-toggle");
335
+ const settingsPanel = document.getElementById("settings-panel");
336
+
337
+ let isGenerating = false;
338
+ let currentResponseEl = null;
339
+
340
+ settingsToggle.addEventListener("click", () => {
341
+ settingsPanel.classList.toggle("open");
342
+ });
343
+
344
+ function getParams() {
345
+ return {
346
+ max_new_tokens: parseInt(document.getElementById("param-max-tokens").value) || 256,
347
+ temperature: parseFloat(document.getElementById("param-temperature").value) || 0.7,
348
+ top_k: parseInt(document.getElementById("param-top-k").value) || 50,
349
+ top_p: 0.9,
350
+ repetition_penalty: parseFloat(document.getElementById("param-rep-penalty").value) || 1.15,
351
+ };
352
+ }
353
+
354
+ function addMessage(role, text) {
355
+ const welcome = chatEl.querySelector(".welcome");
356
+ if (welcome) welcome.remove();
357
+
358
+ const msg = document.createElement("div");
359
+ msg.className = `message message-${role}`;
360
+
361
+ const label = document.createElement("div");
362
+ label.className = "label";
363
+ label.textContent = role === "user" ? "You" : "Supra 50M";
364
+
365
+ const content = document.createElement("div");
366
+ content.className = "message-text";
367
+ content.textContent = text || "";
368
+
369
+ msg.appendChild(label);
370
+ msg.appendChild(content);
371
+ chatEl.appendChild(msg);
372
+ chatEl.scrollTop = chatEl.scrollHeight;
373
+
374
+ return content;
375
+ }
376
+
377
+ function send() {
378
+ const text = inputEl.value.trim();
379
+ if (!text || isGenerating) return;
380
+
381
+ addMessage("user", text);
382
+ currentResponseEl = addMessage("assistant", "");
383
+
384
+ const cursor = document.createElement("span");
385
+ cursor.className = "cursor";
386
+ currentResponseEl.appendChild(cursor);
387
+
388
+ isGenerating = true;
389
+ sendBtn.style.display = "none";
390
+ stopBtn.style.display = "block";
391
+ inputEl.value = "";
392
+ inputEl.style.height = "auto";
393
+
394
+ worker.postMessage({
395
+ type: "generate",
396
+ instruction: text,
397
+ params: getParams(),
398
+ });
399
+ }
400
+
401
+ sendBtn.addEventListener("click", send);
402
+
403
+ stopBtn.addEventListener("click", () => {
404
+ worker.postMessage({ type: "stop" });
405
+ });
406
+
407
+ inputEl.addEventListener("keydown", (e) => {
408
+ if (e.key === "Enter" && !e.shiftKey) {
409
+ e.preventDefault();
410
+ send();
411
+ }
412
+ });
413
+
414
+ inputEl.addEventListener("input", () => {
415
+ inputEl.style.height = "auto";
416
+ inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + "px";
417
+ });
418
+
419
+ worker.onmessage = (e) => {
420
+ const { type } = e.data;
421
+
422
+ if (type === "status") {
423
+ statusText.textContent = e.data.message;
424
+ } else if (type === "progress") {
425
+ progressBar.style.display = "block";
426
+ progressFill.style.width = e.data.percent.toFixed(0) + "%";
427
+ statusText.textContent = `Loading model... ${e.data.percent.toFixed(0)}%`;
428
+ } else if (type === "ready") {
429
+ progressBar.style.display = "none";
430
+ statusText.textContent = "Ready";
431
+ inputEl.disabled = false;
432
+ sendBtn.disabled = false;
433
+ inputEl.focus();
434
+ } else if (type === "token") {
435
+ if (currentResponseEl) {
436
+ const cursor = currentResponseEl.querySelector(".cursor");
437
+ if (cursor) cursor.remove();
438
+ currentResponseEl.textContent += e.data.text;
439
+ const newCursor = document.createElement("span");
440
+ newCursor.className = "cursor";
441
+ currentResponseEl.appendChild(newCursor);
442
+ chatEl.scrollTop = chatEl.scrollHeight;
443
+ }
444
+ } else if (type === "done") {
445
+ if (currentResponseEl) {
446
+ const cursor = currentResponseEl.querySelector(".cursor");
447
+ if (cursor) cursor.remove();
448
+ }
449
+ isGenerating = false;
450
+ stopBtn.style.display = "none";
451
+ sendBtn.style.display = "block";
452
+ sendBtn.disabled = false;
453
+ inputEl.focus();
454
+ } else if (type === "error") {
455
+ if (currentResponseEl) {
456
+ const cursor = currentResponseEl.querySelector(".cursor");
457
+ if (cursor) cursor.remove();
458
+ currentResponseEl.textContent += "\n[Error: " + e.data.message + "]";
459
+ }
460
+ isGenerating = false;
461
+ stopBtn.style.display = "none";
462
+ sendBtn.style.display = "block";
463
+ sendBtn.disabled = false;
464
+ }
465
+ };
466
+
467
+ worker.postMessage({ type: "load" });
468
+ </script>
469
+ </body>
470
  </html>
worker.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ env,
3
+ AutoTokenizer,
4
+ AutoModelForCausalLM,
5
+ TextStreamer,
6
+ InterruptableStoppingCriteria,
7
+ } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3";
8
+
9
+ env.allowLocalModels = false;
10
+
11
+ const MODEL_ID = "av-codes/Supra-50M-Instruct-ONNX";
12
+
13
+ let tokenizer = null;
14
+ let model = null;
15
+ let generating = false;
16
+ const stopping = new InterruptableStoppingCriteria();
17
+
18
+ function formatPrompt(instruction) {
19
+ return (
20
+ "Below is an instruction that describes a task. " +
21
+ "Write a response that appropriately completes the request.\n\n" +
22
+ "### Instruction:\n" +
23
+ instruction +
24
+ "\n\n### Response:\n"
25
+ );
26
+ }
27
+
28
+ async function load() {
29
+ self.postMessage({ type: "status", message: "Loading tokenizer..." });
30
+
31
+ tokenizer = await AutoTokenizer.from_pretrained(MODEL_ID);
32
+
33
+ self.postMessage({ type: "status", message: "Loading model (50 MB)..." });
34
+
35
+ model = await AutoModelForCausalLM.from_pretrained(MODEL_ID, {
36
+ dtype: "q8",
37
+ progress_callback: (progress) => {
38
+ if (progress.status === "progress") {
39
+ self.postMessage({
40
+ type: "progress",
41
+ percent: progress.progress,
42
+ file: progress.file,
43
+ });
44
+ }
45
+ },
46
+ });
47
+
48
+ self.postMessage({ type: "ready" });
49
+ }
50
+
51
+ async function generate(instruction, params) {
52
+ if (!model || !tokenizer || generating) return;
53
+ generating = true;
54
+ stopping.reset();
55
+
56
+ const prompt = formatPrompt(instruction);
57
+ const inputs = tokenizer(prompt);
58
+
59
+ const streamer = new TextStreamer(tokenizer, {
60
+ skip_prompt: true,
61
+ skip_special_tokens: true,
62
+ callback_function: (text) => {
63
+ self.postMessage({ type: "token", text });
64
+ },
65
+ });
66
+
67
+ try {
68
+ await model.generate({
69
+ ...inputs,
70
+ max_new_tokens: params.max_new_tokens || 256,
71
+ temperature: params.temperature || 0.7,
72
+ top_k: params.top_k || 50,
73
+ top_p: params.top_p || 0.9,
74
+ repetition_penalty: params.repetition_penalty || 1.15,
75
+ do_sample: params.temperature > 0,
76
+ streamer,
77
+ stopping_criteria: [stopping],
78
+ });
79
+ } catch (e) {
80
+ self.postMessage({ type: "error", message: e.message });
81
+ }
82
+
83
+ generating = false;
84
+ self.postMessage({ type: "done" });
85
+ }
86
+
87
+ self.onmessage = (e) => {
88
+ const { type, instruction, params } = e.data;
89
+ if (type === "load") load();
90
+ else if (type === "generate") generate(instruction, params);
91
+ else if (type === "stop") stopping.interrupt();
92
+ };