staeiou commited on
Commit
365084a
·
verified ·
1 Parent(s): 8cef9e2

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +438 -0
index.html ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, maximum-scale=1.0, user-scalable=no">
6
+ <title>BartlebyGPT</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #fafafa;
12
+ --surface: #ffffff;
13
+ --border: #d4d4d4;
14
+ --border-focus: #888;
15
+ --text: #1a1a1a;
16
+ --text-muted: #666;
17
+ --user-bg: #e8e8e8;
18
+ --bot-bg: #f0f0f0;
19
+ --accent: #333;
20
+ --accent-text: #fff;
21
+ --input-bg: #fff;
22
+ }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ :root {
26
+ --bg: #111;
27
+ --surface: #1a1a1a;
28
+ --border: #333;
29
+ --border-focus: #666;
30
+ --text: #e5e5e5;
31
+ --text-muted: #999;
32
+ --user-bg: #2a2a2a;
33
+ --bot-bg: #222;
34
+ --accent: #e5e5e5;
35
+ --accent-text: #111;
36
+ --input-bg: #1a1a1a;
37
+ }
38
+ }
39
+
40
+ html, body {
41
+ height: 100%;
42
+ overflow: hidden;
43
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
44
+ font-size: 15px;
45
+ line-height: 1.5;
46
+ background: var(--bg);
47
+ color: var(--text);
48
+ }
49
+
50
+ #app {
51
+ display: flex;
52
+ flex-direction: column;
53
+ height: 100dvh;
54
+ max-width: 720px;
55
+ margin: 0 auto;
56
+ padding: 8px;
57
+ }
58
+
59
+ /* -- Header / Model Loader -- */
60
+ #header {
61
+ flex: 0 0 auto;
62
+ padding: 8px 0;
63
+ text-align: center;
64
+ }
65
+
66
+ #header h1 {
67
+ font-size: 1.1em;
68
+ font-weight: 600;
69
+ margin-bottom: 6px;
70
+ }
71
+
72
+ #model-loader {
73
+ display: flex;
74
+ gap: 6px;
75
+ align-items: center;
76
+ }
77
+
78
+ #model-loader input {
79
+ flex: 1;
80
+ padding: 6px 10px;
81
+ border: 1.5px solid var(--border);
82
+ border-radius: 6px;
83
+ background: var(--input-bg);
84
+ color: var(--text);
85
+ font-size: 13px;
86
+ outline: none;
87
+ }
88
+
89
+ #model-loader input:focus {
90
+ border-color: var(--border-focus);
91
+ }
92
+
93
+ #load-btn {
94
+ padding: 6px 14px;
95
+ border: none;
96
+ border-radius: 6px;
97
+ background: var(--accent);
98
+ color: var(--accent-text);
99
+ font-size: 13px;
100
+ font-weight: 600;
101
+ cursor: pointer;
102
+ white-space: nowrap;
103
+ }
104
+
105
+ #load-btn:disabled {
106
+ opacity: 0.5;
107
+ cursor: not-allowed;
108
+ }
109
+
110
+ #status {
111
+ font-size: 12px;
112
+ color: var(--text-muted);
113
+ margin-top: 4px;
114
+ min-height: 1.4em;
115
+ }
116
+
117
+ /* -- Chat Area -- */
118
+ #chat {
119
+ flex: 1 1 auto;
120
+ min-height: 0;
121
+ overflow-y: auto;
122
+ padding: 12px 0;
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 10px;
126
+ }
127
+
128
+ .msg-row {
129
+ display: flex;
130
+ max-width: 100%;
131
+ }
132
+
133
+ .msg-row.user { justify-content: flex-start; }
134
+ .msg-row.bot { justify-content: flex-end; }
135
+
136
+ .msg-bubble {
137
+ max-width: 85%;
138
+ padding: 10px 14px;
139
+ border-radius: 14px;
140
+ font-size: 14px;
141
+ line-height: 1.5;
142
+ white-space: pre-wrap;
143
+ word-break: break-word;
144
+ }
145
+
146
+ .msg-row.user .msg-bubble {
147
+ background: var(--user-bg);
148
+ border-bottom-left-radius: 4px;
149
+ }
150
+
151
+ .msg-row.bot .msg-bubble {
152
+ background: var(--bot-bg);
153
+ border-bottom-right-radius: 4px;
154
+ }
155
+
156
+ /* -- Input Area -- */
157
+ #input-area {
158
+ flex: 0 0 auto;
159
+ padding: 6px 0;
160
+ display: flex;
161
+ gap: 6px;
162
+ }
163
+
164
+ #msg-input {
165
+ flex: 1;
166
+ padding: 8px 12px;
167
+ border: 1.5px solid var(--border);
168
+ border-radius: 8px;
169
+ background: var(--input-bg);
170
+ color: var(--text);
171
+ font-size: 15px;
172
+ outline: none;
173
+ resize: none;
174
+ height: 2.6em;
175
+ line-height: 1.25;
176
+ }
177
+
178
+ #msg-input:focus {
179
+ border-color: var(--border-focus);
180
+ }
181
+
182
+ #send-btn {
183
+ padding: 8px 18px;
184
+ border: none;
185
+ border-radius: 8px;
186
+ background: var(--accent);
187
+ color: var(--accent-text);
188
+ font-size: 15px;
189
+ font-weight: 600;
190
+ cursor: pointer;
191
+ }
192
+
193
+ #send-btn:disabled {
194
+ opacity: 0.4;
195
+ cursor: not-allowed;
196
+ }
197
+
198
+ /* -- Params Accordion -- */
199
+ #params-toggle {
200
+ flex: 0 0 auto;
201
+ padding: 4px 0;
202
+ }
203
+
204
+ #params-toggle summary {
205
+ cursor: pointer;
206
+ font-size: 13px;
207
+ color: var(--text-muted);
208
+ user-select: none;
209
+ }
210
+
211
+ #params-content {
212
+ display: flex;
213
+ gap: 12px;
214
+ flex-wrap: wrap;
215
+ padding: 8px 0;
216
+ }
217
+
218
+ .param-group {
219
+ display: flex;
220
+ flex-direction: column;
221
+ gap: 2px;
222
+ flex: 1;
223
+ min-width: 120px;
224
+ }
225
+
226
+ .param-group label {
227
+ font-size: 12px;
228
+ color: var(--text-muted);
229
+ }
230
+
231
+ .param-group input[type="range"] {
232
+ width: 100%;
233
+ }
234
+
235
+ .param-group .param-val {
236
+ font-size: 11px;
237
+ color: var(--text-muted);
238
+ text-align: right;
239
+ }
240
+
241
+ /* -- Generating indicator -- */
242
+ .generating::after {
243
+ content: "▌";
244
+ animation: blink 0.6s infinite;
245
+ }
246
+
247
+ @keyframes blink {
248
+ 0%, 100% { opacity: 1; }
249
+ 50% { opacity: 0; }
250
+ }
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <div id="app">
255
+
256
+ <div id="header">
257
+ <h1>BartlebyGPT</h1>
258
+ <div id="model-loader">
259
+ <input type="text" id="repo-input" value="staeiou/bartleby-qwen3-4b-2507_v2_onnx"
260
+ placeholder="HF ONNX repo ID">
261
+ <button id="load-btn" onclick="loadModel()">Load Model</button>
262
+ </div>
263
+ <div id="status">Enter a Hugging Face ONNX model repo and click Load Model.</div>
264
+ </div>
265
+
266
+ <div id="chat"></div>
267
+
268
+ <div id="input-area">
269
+ <input type="text" id="msg-input" placeholder="What do you want?"
270
+ disabled autocomplete="off">
271
+ <button id="send-btn" onclick="sendMessage()" disabled>Send</button>
272
+ </div>
273
+
274
+ <details id="params-toggle">
275
+ <summary>Params</summary>
276
+ <div id="params-content">
277
+ <div class="param-group">
278
+ <label>Max tokens</label>
279
+ <input type="range" id="p-max-tokens" min="1" max="512" value="256"
280
+ oninput="this.nextElementSibling.textContent=this.value">
281
+ <span class="param-val">256</span>
282
+ </div>
283
+ <div class="param-group">
284
+ <label>Temperature</label>
285
+ <input type="range" id="p-temp" min="0" max="2" step="0.05" value="0.7"
286
+ oninput="this.nextElementSibling.textContent=this.value">
287
+ <span class="param-val">0.7</span>
288
+ </div>
289
+ <div class="param-group">
290
+ <label>Top-p</label>
291
+ <input type="range" id="p-top-p" min="0.1" max="1" step="0.05" value="0.9"
292
+ oninput="this.nextElementSibling.textContent=this.value">
293
+ <span class="param-val">0.9</span>
294
+ </div>
295
+ </div>
296
+ </details>
297
+
298
+ </div>
299
+
300
+ <script type="module">
301
+ import { AutoTokenizer, AutoModelForCausalLM, TextStreamer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3";
302
+
303
+ let tokenizer = null;
304
+ let model = null;
305
+ let generating = false;
306
+
307
+ const chatEl = document.getElementById("chat");
308
+ const msgInput = document.getElementById("msg-input");
309
+ const sendBtn = document.getElementById("send-btn");
310
+ const loadBtn = document.getElementById("load-btn");
311
+ const repoInput = document.getElementById("repo-input");
312
+ const statusEl = document.getElementById("status");
313
+
314
+ function setStatus(msg) {
315
+ statusEl.textContent = msg;
316
+ }
317
+
318
+ function setInputEnabled(enabled) {
319
+ msgInput.disabled = !enabled;
320
+ sendBtn.disabled = !enabled;
321
+ }
322
+
323
+ function addMessage(role, text) {
324
+ const row = document.createElement("div");
325
+ row.className = `msg-row ${role}`;
326
+ const bubble = document.createElement("div");
327
+ bubble.className = "msg-bubble";
328
+ bubble.textContent = text;
329
+ row.appendChild(bubble);
330
+ chatEl.appendChild(row);
331
+ chatEl.scrollTop = chatEl.scrollHeight;
332
+ return bubble;
333
+ }
334
+
335
+ window.loadModel = async function () {
336
+ const repo = repoInput.value.trim();
337
+ if (!repo) return;
338
+
339
+ loadBtn.disabled = true;
340
+ setInputEnabled(false);
341
+ setStatus("Loading tokenizer...");
342
+
343
+ try {
344
+ tokenizer = await AutoTokenizer.from_pretrained(repo);
345
+ setStatus("Loading model (this may take a while for large models)...");
346
+ model = await AutoModelForCausalLM.from_pretrained(repo, {
347
+ dtype: "q8",
348
+ use_external_data_format: 2,
349
+ progress_callback: (progress) => {
350
+ if (progress.status === "progress" && progress.progress != null) {
351
+ setStatus(`Loading model... ${Math.round(progress.progress)}%`);
352
+ }
353
+ },
354
+ });
355
+ setStatus("Model loaded. Start chatting!");
356
+ setInputEnabled(true);
357
+ msgInput.focus();
358
+ } catch (e) {
359
+ setStatus(`Error: ${e.message}`);
360
+ console.error(e);
361
+ } finally {
362
+ loadBtn.disabled = false;
363
+ }
364
+ };
365
+
366
+ window.sendMessage = async function () {
367
+ if (!model || !tokenizer || generating) return;
368
+
369
+ let userText = (msgInput.value || "").trim();
370
+ if (!userText) return;
371
+
372
+ // Truncate to 2000 chars
373
+ if (userText.length > 2000) userText = userText.slice(0, 2000);
374
+
375
+ msgInput.value = "";
376
+ addMessage("user", userText);
377
+
378
+ // Only send last message (not full history)
379
+ const messages = [{ role: "user", content: userText }];
380
+
381
+ const text = tokenizer.apply_chat_template(messages, {
382
+ tokenize: false,
383
+ add_generation_prompt: true,
384
+ });
385
+
386
+ const inputs = tokenizer(text, { return_tensors: "pt" });
387
+ const inputLen = inputs.input_ids.dims[1];
388
+
389
+ const botBubble = addMessage("bot", "");
390
+ botBubble.classList.add("generating");
391
+
392
+ generating = true;
393
+ setInputEnabled(false);
394
+
395
+ const maxTokens = parseInt(document.getElementById("p-max-tokens").value);
396
+ const temperature = parseFloat(document.getElementById("p-temp").value);
397
+ const topP = parseFloat(document.getElementById("p-top-p").value);
398
+
399
+ try {
400
+ // Streamer that updates the bubble token by token
401
+ const streamer = new TextStreamer(tokenizer, {
402
+ skip_prompt: true,
403
+ skip_special_tokens: true,
404
+ callback_function: (text) => {
405
+ botBubble.textContent += text;
406
+ chatEl.scrollTop = chatEl.scrollHeight;
407
+ },
408
+ });
409
+
410
+ await model.generate({
411
+ ...inputs,
412
+ max_new_tokens: maxTokens,
413
+ do_sample: temperature > 0,
414
+ temperature: temperature > 0 ? temperature : undefined,
415
+ top_p: topP,
416
+ streamer,
417
+ });
418
+ } catch (e) {
419
+ botBubble.textContent += `\n[Error: ${e.message}]`;
420
+ console.error(e);
421
+ } finally {
422
+ botBubble.classList.remove("generating");
423
+ generating = false;
424
+ setInputEnabled(true);
425
+ msgInput.focus();
426
+ }
427
+ };
428
+
429
+ // Enter to send
430
+ msgInput.addEventListener("keydown", (e) => {
431
+ if (e.key === "Enter" && !e.shiftKey) {
432
+ e.preventDefault();
433
+ window.sendMessage();
434
+ }
435
+ });
436
+ </script>
437
+ </body>
438
+ </html>