akhaliq HF Staff commited on
Commit
dd033ba
Β·
1 Parent(s): 570a26b

Redesign: Claude-inspired UI with WeiboAI brand colors and hummingbird logo

Browse files
Files changed (1) hide show
  1. index.html +1058 -1123
index.html CHANGED
@@ -3,31 +3,32 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>VibeThinker-3B - Verifiable Reasoning SLM</title>
7
-
 
8
  <!-- Fonts -->
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
-
13
- <!-- FontAwesome Icons -->
14
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
-
16
- <!-- Prism.js Tomorrow Theme for Code Highlighting -->
17
- <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
18
 
19
- <!-- Marked.js for Markdown rendering -->
 
 
 
20
  <script src="https://cdn.jsdelivr.net/npm/marked@9.1.6/marked.min.js"></script>
21
 
22
- <!-- Prism.js core + common languages for syntax highlighting -->
23
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
24
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"></script>
25
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script>
26
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
27
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-cpp.min.js"></script>
28
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-java.min.js"></script>
29
-
30
- <!-- MathJax Configuration for LaTeX Math -->
31
  <script>
32
  window.MathJax = {
33
  tex: {
@@ -35,1417 +36,1351 @@
35
  displayMath: [['$$', '$$'], ['\\[', '\\]']],
36
  processEscapes: true
37
  },
38
- options: {
39
- skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
40
- }
41
  };
42
  </script>
43
  <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
44
 
45
  <style>
46
- /* Premium Modern CSS Style Guide */
47
  :root {
48
- --bg-dark: #070a13;
49
- --bg-light-dark: #0f1524;
50
- --primary: #8b5cf6;
51
- --primary-hover: #7c3aed;
52
- --primary-glow: rgba(139, 92, 246, 0.4);
53
- --secondary: #6366f1;
54
- --accent: #06b6d4;
55
- --accent-glow: rgba(6, 182, 212, 0.3);
56
- --text-main: #f8fafc;
57
- --text-muted: #94a3b8;
58
- --border-color: rgba(255, 255, 255, 0.08);
59
- --card-bg: rgba(15, 21, 36, 0.7);
60
- --sidebar-width: 320px;
61
- }
62
-
63
- * {
64
- box-sizing: border-box;
65
- margin: 0;
66
- padding: 0;
67
- }
68
-
69
- body {
70
- font-family: 'Outfit', sans-serif;
71
- background-color: var(--bg-dark);
72
- color: var(--text-main);
73
- height: 100vh;
74
- overflow: hidden;
75
- display: flex;
76
- position: relative;
77
- }
78
-
79
- /* Ambient background glowing blobs */
80
- .glow-blob {
81
- position: absolute;
82
- width: 500px;
83
- height: 500px;
84
- border-radius: 50%;
85
- background: radial-gradient(circle, var(--primary-glow) 0%, rgba(0,0,0,0) 70%);
86
- top: -150px;
87
- right: -150px;
88
- z-index: 0;
89
- pointer-events: none;
90
- filter: blur(80px);
91
- }
92
-
93
- .glow-blob-2 {
94
- position: absolute;
95
- width: 600px;
96
- height: 600px;
97
- border-radius: 50%;
98
- background: radial-gradient(circle, var(--accent-glow) 0%, rgba(0,0,0,0) 70%);
99
- bottom: -200px;
100
- left: -200px;
101
- z-index: 0;
102
- pointer-events: none;
103
- filter: blur(100px);
104
  }
105
 
106
- /* Layout Wrapper */
107
- .app-container {
108
  display: flex;
109
- width: 100%;
110
  height: 100vh;
111
- z-index: 1;
112
- position: relative;
113
  }
114
 
115
- /* Sidebar Styling */
116
  .sidebar {
117
- width: var(--sidebar-width);
118
- background-color: rgba(15, 21, 36, 0.85);
119
- border-right: 1px solid var(--border-color);
120
- backdrop-filter: blur(20px);
121
- padding: 24px;
122
  display: flex;
123
  flex-direction: column;
124
- justify-content: space-between;
125
- overflow-y: auto;
126
- transition: transform 0.3s ease;
127
  }
128
 
129
- .brand-section {
 
 
 
 
130
  display: flex;
131
  align-items: center;
132
- gap: 12px;
133
- margin-bottom: 32px;
134
  }
135
 
136
  .brand-logo {
137
- font-size: 32px;
138
- animation: float 3s ease-in-out infinite;
 
 
 
139
  }
140
 
141
- .brand-title {
 
142
  font-weight: 700;
143
- font-size: 22px;
144
- background: linear-gradient(135deg, #fff 30%, var(--primary) 100%);
145
- -webkit-background-clip: text;
146
- -webkit-text-fill-color: transparent;
147
- letter-spacing: -0.5px;
148
  }
149
 
150
- .brand-badge {
151
- background: rgba(139, 92, 246, 0.2);
152
- border: 1px solid var(--primary);
153
- color: #c084fc;
154
- font-size: 11px;
155
- padding: 2px 8px;
156
- border-radius: 12px;
157
  font-weight: 600;
 
 
 
 
 
 
158
  }
159
 
160
- .sidebar-section {
161
- margin-bottom: 24px;
 
 
 
162
  }
163
 
164
- .section-title {
165
- font-size: 12px;
 
 
 
166
  font-weight: 600;
167
  text-transform: uppercase;
168
- letter-spacing: 1.5px;
169
  color: var(--text-muted);
170
- margin-bottom: 16px;
171
- display: flex;
172
- align-items: center;
173
- gap: 8px;
174
  }
175
 
176
- /* Config Sliders */
177
- .parameter-group {
178
- margin-bottom: 18px;
179
- }
180
 
181
- .parameter-label {
182
  display: flex;
183
  justify-content: space-between;
184
- font-size: 13px;
185
- color: var(--text-main);
186
- margin-bottom: 6px;
187
  }
188
 
189
- .parameter-value {
 
 
 
190
  font-weight: 600;
191
- color: var(--accent);
 
 
192
  }
193
 
194
  input[type="range"] {
195
  width: 100%;
196
- height: 6px;
197
- background: rgba(255, 255, 255, 0.1);
198
- border-radius: 3px;
199
  outline: none;
200
  -webkit-appearance: none;
 
201
  }
202
-
203
  input[type="range"]::-webkit-slider-thumb {
204
  -webkit-appearance: none;
205
  width: 14px;
206
  height: 14px;
207
  border-radius: 50%;
208
- background: var(--accent);
209
- cursor: pointer;
210
- box-shadow: 0 0 10px var(--accent-glow);
211
- transition: transform 0.1s ease;
212
  }
 
213
 
214
- input[type="range"]::-webkit-slider-thumb:hover {
215
- transform: scale(1.2);
216
- }
217
-
218
- .textarea-config {
219
  width: 100%;
220
- background: rgba(0, 0, 0, 0.25);
221
- border: 1px solid var(--border-color);
222
- border-radius: 8px;
223
- color: var(--text-main);
224
- padding: 10px;
225
  font-family: inherit;
226
- font-size: 13px;
 
 
227
  resize: vertical;
228
- min-height: 80px;
229
  outline: none;
230
- transition: border-color 0.2s;
231
  }
 
232
 
233
- .textarea-config:focus {
234
- border-color: var(--primary);
235
- }
236
-
237
- /* Example Presets */
238
- .presets-container {
239
  display: flex;
240
- flex-direction: column;
241
  gap: 8px;
242
- }
243
-
244
- .preset-btn {
245
  width: 100%;
246
- background: rgba(255, 255, 255, 0.03);
247
- border: 1px solid var(--border-color);
248
- color: var(--text-main);
249
- padding: 10px 12px;
250
- border-radius: 8px;
 
 
251
  cursor: pointer;
252
  text-align: left;
253
- font-size: 13px;
254
- display: flex;
255
- align-items: center;
256
- gap: 10px;
257
- transition: all 0.2s ease;
258
- }
259
-
260
- .preset-btn i {
261
- color: var(--primary);
262
- font-size: 14px;
263
- }
264
-
265
- .preset-btn:hover {
266
- background: rgba(139, 92, 246, 0.1);
267
- border-color: var(--primary);
268
- transform: translateY(-1px);
269
  }
270
 
271
  .sidebar-footer {
272
- border-top: 1px solid var(--border-color);
273
- padding-top: 16px;
274
- font-size: 12px;
275
- color: var(--text-muted);
276
  display: flex;
277
  flex-direction: column;
278
- gap: 8px;
279
  }
280
 
281
- .sidebar-link {
282
- color: var(--accent);
283
- text-decoration: none;
284
  display: flex;
285
  align-items: center;
286
- gap: 6px;
287
- transition: color 0.2s;
288
- }
289
-
290
- .sidebar-link:hover {
291
- color: #22d3ee;
 
292
  }
 
 
293
 
294
- /* Main Chat Panel */
295
- .main-panel {
296
  flex: 1;
297
  display: flex;
298
  flex-direction: column;
299
- height: 100%;
300
- background: rgba(7, 10, 19, 0.3);
301
- backdrop-filter: blur(10px);
302
- position: relative;
303
  }
304
 
305
- .chat-header {
306
- height: 70px;
307
- border-bottom: 1px solid var(--border-color);
308
- padding: 0 32px;
 
309
  display: flex;
310
  align-items: center;
311
  justify-content: space-between;
312
- background: rgba(15, 21, 36, 0.4);
 
313
  }
314
 
315
- .header-info {
316
- display: flex;
317
- flex-direction: column;
318
- }
319
 
320
- .header-title {
321
- font-size: 18px;
322
- font-weight: 600;
323
- display: flex;
324
- align-items: center;
325
- gap: 8px;
 
 
 
 
 
 
 
 
 
326
  }
327
 
328
- .status-container {
329
  display: flex;
330
  align-items: center;
331
- gap: 6px;
 
 
 
 
332
  font-size: 13px;
333
- color: var(--text-muted);
 
 
334
  }
335
 
336
- .status-dot {
337
- width: 8px;
338
- height: 8px;
339
  border-radius: 50%;
340
- background-color: #22c55e;
341
- box-shadow: 0 0 8px #22c55e;
 
 
 
 
 
 
 
 
 
342
  }
343
 
344
- .status-dot.busy {
345
- background-color: #eab308;
346
- box-shadow: 0 0 8px #eab308;
347
- animation: pulse 1.5s infinite;
 
 
 
 
 
 
 
 
 
348
  }
 
349
 
350
- /* Chat Messages */
351
- .chat-history {
352
  flex: 1;
353
  overflow-y: auto;
354
- padding: 32px;
355
- display: flex;
356
- flex-direction: column;
357
- gap: 24px;
358
  scroll-behavior: smooth;
359
  }
 
 
360
 
361
- .welcome-card {
362
- max-width: 720px;
363
- margin: 40px auto;
364
- background: var(--card-bg);
365
- border: 1px solid var(--border-color);
366
- border-radius: 16px;
367
- padding: 32px;
 
 
 
 
 
368
  text-align: center;
369
- box-shadow: 0 20px 40px rgba(0,0,0,0.3);
370
  }
371
 
372
- .welcome-icon {
373
- font-size: 48px;
374
- margin-bottom: 16px;
375
- display: inline-block;
 
 
376
  }
377
 
378
  .welcome-title {
379
- font-size: 24px;
380
  font-weight: 700;
381
- margin-bottom: 12px;
 
382
  }
383
 
384
- .welcome-description {
385
- color: var(--text-muted);
386
- font-size: 15px;
387
- line-height: 1.6;
388
- margin-bottom: 24px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  }
 
 
 
 
 
 
390
 
391
- .welcome-grid {
392
  display: grid;
393
  grid-template-columns: 1fr 1fr;
394
- gap: 16px;
395
- text-align: left;
 
396
  }
397
 
398
- .welcome-feature {
399
  padding: 16px;
400
- background: rgba(255, 255, 255, 0.02);
401
- border: 1px solid var(--border-color);
402
- border-radius: 12px;
 
 
403
  }
 
404
 
405
- .welcome-feature-title {
406
- font-weight: 600;
407
- font-size: 14px;
408
- margin-bottom: 6px;
409
- color: var(--accent);
410
- display: flex;
411
- align-items: center;
412
- gap: 8px;
413
  }
 
 
414
 
415
- .welcome-feature-desc {
416
- font-size: 12px;
417
- color: var(--text-muted);
418
- line-height: 1.4;
419
  }
420
 
421
- /* Message Bubbles */
422
- .message-row {
423
  display: flex;
424
- width: 100%;
425
- margin-bottom: 8px;
426
- }
427
-
428
- .message-row.user {
429
  justify-content: flex-end;
 
430
  }
431
 
432
- .message-row.assistant {
433
- justify-content: flex-start;
434
- }
435
-
436
- .message-bubble {
437
- max-width: 80%;
438
- border-radius: 16px;
439
- padding: 16px 20px;
440
- position: relative;
441
- box-shadow: 0 4px 15px rgba(0,0,0,0.15);
442
  line-height: 1.6;
 
 
443
  }
444
 
445
- .message-row.user .message-bubble {
446
- background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
447
- color: #fff;
448
- border-bottom-right-radius: 4px;
 
449
  }
450
 
451
- .message-row.assistant .message-bubble {
452
- background: var(--card-bg);
453
- border: 1px solid var(--border-color);
454
- color: var(--text-main);
455
- border-top-left-radius: 4px;
 
 
 
456
  }
457
 
458
- .message-meta {
459
- font-size: 11px;
460
- color: var(--text-muted);
461
- margin-top: 6px;
462
- display: flex;
463
- align-items: center;
464
- gap: 8px;
465
  }
466
 
467
- /* Collapsible Thought Box styling */
468
- .thought-container {
469
- margin-bottom: 14px;
470
- border: 1px solid rgba(139, 92, 246, 0.2);
471
- background: rgba(139, 92, 246, 0.03);
472
- border-radius: 10px;
473
  overflow: hidden;
474
  }
475
 
476
- .thought-header {
477
- padding: 8px 12px;
478
- background: rgba(139, 92, 246, 0.08);
479
- font-size: 12.5px;
480
- font-weight: 600;
481
- color: #c084fc;
482
- cursor: pointer;
483
  display: flex;
484
- justify-content: space-between;
485
  align-items: center;
 
 
 
486
  user-select: none;
487
- transition: background 0.2s;
488
- }
489
-
490
- .thought-header:hover {
491
- background: rgba(139, 92, 246, 0.12);
492
  }
 
493
 
494
- .thought-title-left {
495
  display: flex;
496
  align-items: center;
497
- gap: 6px;
 
 
 
498
  }
499
 
500
- .thought-timer {
 
 
 
 
 
 
 
 
 
501
  font-size: 11px;
502
  color: var(--text-muted);
503
- font-weight: normal;
504
  }
505
 
506
- .thought-content {
507
- padding: 12px;
508
- font-size: 13px;
509
- font-family: 'Fira Code', monospace;
510
- color: #cbd5e1;
511
- border-top: 1px solid rgba(139, 92, 246, 0.1);
 
512
  white-space: pre-wrap;
513
- max-height: 250px;
514
  overflow-y: auto;
515
- background: rgba(0, 0, 0, 0.15);
516
- line-height: 1.5;
517
  }
518
 
519
- .thought-status-spinner {
520
- font-size: 12px;
521
- animation: spin 1s linear infinite;
522
- color: var(--primary);
 
523
  }
524
 
525
- .thought-status-done {
526
- color: #22c55e;
527
- }
528
 
529
- /* Code block inside chat message styling */
530
- pre {
531
- margin: 12px 0;
532
- border-radius: 8px;
533
- overflow: hidden;
534
- border: 1px solid var(--border-color);
535
- position: relative;
536
  }
 
 
 
537
 
538
- code {
539
- font-family: 'Fira Code', monospace;
540
- font-size: 14px;
541
  }
 
542
 
543
- p code, li code {
544
- background: rgba(255, 255, 255, 0.1);
545
- padding: 2px 6px;
546
- border-radius: 4px;
547
- font-size: 13.5px;
548
- }
549
 
550
- /* Markdown styling updates */
551
- .message-body h1, .message-body h2, .message-body h3 {
552
- margin-top: 16px;
553
- margin-bottom: 8px;
554
- font-weight: 600;
555
- color: #fff;
 
556
  }
557
- .message-body h1 { font-size: 18px; border-bottom: 1px solid var(--border-color); padding-bottom: 4px; }
558
- .message-body h2 { font-size: 16px; }
559
- .message-body h3 { font-size: 14.5px; }
560
 
561
- .message-body p {
562
- margin-bottom: 10px;
 
 
 
 
563
  }
564
 
565
- .message-body ul, .message-body ol {
566
- margin-left: 20px;
567
- margin-bottom: 10px;
 
 
 
568
  }
569
 
570
- .message-body table {
571
  width: 100%;
572
  border-collapse: collapse;
573
- margin: 14px 0;
574
- font-size: 13px;
575
  }
576
-
577
- .message-body th, .message-body td {
578
- border: 1px solid var(--border-color);
579
- padding: 8px 12px;
580
  text-align: left;
581
  }
582
-
583
- .message-body th {
584
- background: rgba(255, 255, 255, 0.05);
585
  font-weight: 600;
586
  }
587
 
588
- /* Copy Button overlay in Code Blocks */
 
 
589
  .code-header {
590
- background: rgba(0, 0, 0, 0.4);
591
- padding: 6px 12px;
592
- font-size: 11px;
593
- color: var(--text-muted);
594
  display: flex;
595
  justify-content: space-between;
596
  align-items: center;
597
- border-bottom: 1px solid var(--border-color);
 
 
 
 
 
598
  }
599
 
600
- .copy-btn {
601
- background: transparent;
602
  border: none;
603
  color: var(--text-muted);
604
  cursor: pointer;
605
- font-size: 12px;
606
- transition: color 0.2s;
 
 
 
607
  }
 
608
 
609
- .copy-btn:hover {
610
- color: #fff;
 
 
 
 
 
 
 
 
 
 
 
611
  }
612
 
613
- /* Input Panel */
614
- .input-panel {
615
- padding: 24px 32px 32px;
616
- border-top: 1px solid var(--border-color);
617
- background: rgba(15, 21, 36, 0.6);
618
- backdrop-filter: blur(10px);
619
  }
620
 
621
- .input-wrapper {
622
- max-width: 900px;
623
  margin: 0 auto;
624
- background: rgba(0, 0, 0, 0.3);
625
- border: 1px solid var(--border-color);
 
 
626
  border-radius: 14px;
627
- padding: 8px 12px;
628
  display: flex;
629
  flex-direction: column;
630
- gap: 8px;
631
- position: relative;
632
- transition: border-color 0.2s, box-shadow 0.2s;
633
  }
634
-
635
- .input-wrapper:focus-within {
636
- border-color: var(--primary);
637
- box-shadow: 0 0 15px rgba(139, 92, 246, 0.25);
638
  }
639
 
640
- .chat-textarea {
641
  width: 100%;
642
  background: transparent;
643
  border: none;
644
  outline: none;
645
- color: var(--text-main);
646
  font-family: inherit;
647
- font-size: 15px;
 
 
 
648
  resize: none;
649
- min-height: 48px;
650
- max-height: 200px;
651
- padding: 8px 4px;
652
- line-height: 1.5;
653
  }
 
654
 
655
- .input-actions {
656
  display: flex;
657
  justify-content: space-between;
658
  align-items: center;
659
- border-top: 1px solid rgba(255, 255, 255, 0.04);
660
- padding-top: 8px;
661
  }
662
 
663
- .input-left-buttons {
664
- display: flex;
665
- gap: 8px;
666
- }
667
 
668
- .action-icon-btn {
669
- background: transparent;
670
  border: none;
671
- width: 32px;
672
- height: 32px;
673
- border-radius: 6px;
674
  color: var(--text-muted);
675
  cursor: pointer;
676
- display: flex;
677
- align-items: center;
678
- justify-content: center;
679
- transition: background 0.2s, color 0.2s;
680
- }
681
-
682
- .action-icon-btn:hover {
683
- background: rgba(255, 255, 255, 0.05);
684
- color: var(--text-main);
685
  }
 
686
 
687
  .send-btn {
688
- background: var(--primary);
689
- border: none;
690
- padding: 8px 16px;
691
- border-radius: 8px;
692
- color: #fff;
693
- font-weight: 600;
694
- font-size: 13px;
695
- cursor: pointer;
696
  display: flex;
697
  align-items: center;
698
  gap: 6px;
699
- transition: background 0.2s, transform 0.1s;
700
- }
701
-
702
- .send-btn:hover {
703
- background: var(--primary-hover);
704
- }
705
-
706
- .send-btn:active {
707
- transform: scale(0.97);
708
- }
709
-
710
- .send-btn.stop {
711
- background: #ef4444;
712
- }
713
-
714
- .send-btn.stop:hover {
715
- background: #dc2626;
716
  }
 
 
 
 
717
 
718
- .info-footer {
719
  text-align: center;
720
- font-size: 11px;
721
  color: var(--text-muted);
722
- margin-top: 8px;
723
  }
724
 
725
- /* Loading placeholder */
726
- .typing-indicator {
727
  display: flex;
728
- gap: 4px;
729
- padding: 6px 12px;
730
- }
731
-
732
- .dot {
733
- width: 6px;
734
- height: 6px;
735
- background-color: var(--text-muted);
736
- border-radius: 50%;
737
- animation: bounce 1.4s infinite ease-in-out both;
738
- }
739
-
740
- .dot:nth-child(1) { animation-delay: -0.32s; }
741
- .dot:nth-child(2) { animation-delay: -0.16s; }
742
-
743
- /* Animations */
744
- @keyframes float {
745
- 0%, 100% { transform: translateY(0); }
746
- 50% { transform: translateY(-6px); }
747
- }
748
-
749
- @keyframes spin {
750
- 100% { transform: rotate(360deg); }
751
- }
752
-
753
- @keyframes pulse {
754
- 0%, 100% { opacity: 1; transform: scale(1); }
755
- 50% { opacity: 0.5; transform: scale(0.92); }
756
- }
757
-
758
- @keyframes bounce {
759
- 0%, 80%, 100% { transform: scale(0); }
760
- 40% { transform: scale(1.0); }
761
  }
762
 
763
- /* Mobile Responsive */
764
- .sidebar-toggle {
765
- display: none;
766
- background: transparent;
767
- border: none;
768
- color: var(--text-main);
769
- font-size: 20px;
770
- cursor: pointer;
771
  }
772
 
773
- @media (max-width: 768px) {
774
- .sidebar {
775
- position: absolute;
776
- left: 0;
777
- top: 0;
778
- height: 100%;
779
- z-index: 10;
780
- transform: translateX(-100%);
781
- }
782
- .sidebar.open {
783
- transform: translateX(0);
784
- }
785
- .sidebar-toggle {
786
- display: block;
787
- }
788
- .welcome-grid {
789
- grid-template-columns: 1fr;
790
- }
791
- }
792
  </style>
793
  </head>
794
  <body>
795
 
796
- <!-- Background Ambience -->
797
- <div class="glow-blob"></div>
798
- <div class="glow-blob-2"></div>
 
799
 
800
- <!-- App Wrapper -->
801
- <div class="app-container">
802
-
803
- <!-- Sidebar Panel -->
804
- <aside class="sidebar" id="sidebar">
805
  <div>
806
- <div class="brand-section">
807
- <div class="brand-logo">😻</div>
808
- <div>
809
- <h1 class="brand-title">VibeThinker</h1>
810
- <span class="brand-badge">3B Scale</span>
811
- </div>
812
- </div>
813
 
814
- <!-- Configuration Settings -->
815
- <div class="sidebar-section">
816
- <h2 class="section-title"><i class="fa-solid fa-sliders"></i> Parameters</h2>
817
-
818
- <div class="parameter-group">
819
- <div class="parameter-label">
820
- <span>Temperature</span>
821
- <span class="parameter-value" id="val-temp">1.0</span>
822
- </div>
823
- <input type="range" id="param-temp" min="0.0" max="1.5" step="0.05" value="1.0">
824
- </div>
825
 
826
- <div class="parameter-group">
827
- <div class="parameter-label">
828
- <span>Top-P</span>
829
- <span class="parameter-value" id="val-topp">0.95</span>
830
- </div>
831
- <input type="range" id="param-topp" min="0.0" max="1.0" step="0.01" value="0.95">
832
- </div>
833
 
834
- <div class="parameter-group">
835
- <div class="parameter-label">
836
- <span>Max Tokens</span>
837
- <span class="parameter-value" id="val-tokens">4096</span>
838
- </div>
839
- <input type="range" id="param-tokens" min="256" max="32768" step="256" value="4096">
840
- </div>
841
 
842
- <div class="parameter-group">
843
- <div class="parameter-label">
844
- <span>System Prompt</span>
845
- </div>
846
- <textarea class="textarea-config" id="param-system">You are VibeThinker, a helpful and harmless AI assistant specialized in reasoning. You solve complex problems step-by-step to verify correctness.</textarea>
847
- </div>
848
  </div>
 
 
849
 
850
- <!-- Preset Prompts -->
851
- <div class="sidebar-section">
852
- <h2 class="section-title"><i class="fa-solid fa-lightbulb"></i> Presets</h2>
853
- <div class="presets-container">
854
- <button class="preset-btn" onclick="loadPreset('card')">
855
- <i class="fa-solid fa-calculator"></i> Spade & Face Cards
856
- </button>
857
- <button class="preset-btn" onclick="loadPreset('palindrome')">
858
- <i class="fa-solid fa-code"></i> Longest Palindrome
859
- </button>
860
- <button class="preset-btn" onclick="loadPreset('logic')">
861
- <i class="fa-solid fa-puzzle-piece"></i> Labeled Fruit Boxes
862
- </button>
863
- </div>
864
  </div>
 
865
  </div>
866
 
867
- <!-- Sidebar Footer -->
868
- <div class="sidebar-footer">
869
- <a href="https://huggingface.co/WeiboAI/VibeThinker-3B" target="_blank" class="sidebar-link">
870
- <i class="fa-brands fa-hugging-face"></i> Model Card πŸ€—
871
- </a>
872
- <a href="https://arxiv.org/abs/2606.16140" target="_blank" class="sidebar-link">
873
- <i class="fa-regular fa-file-pdf"></i> Research Paper
874
- </a>
875
- <div>MIT License β€’ WeiboAI 2026</div>
876
  </div>
877
- </aside>
878
-
879
- <!-- Main Panel -->
880
- <main class="main-panel">
881
-
882
- <!-- Chat Header -->
883
- <header class="chat-header">
884
- <button class="sidebar-toggle" id="sidebar-toggle" onclick="toggleSidebar()">
885
- <i class="fa-solid fa-bars"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  </button>
887
- <div class="header-info">
888
- <div class="header-title">VibeThinker-3B Reasoning Sandbox</div>
889
- <div class="status-container">
890
- <div class="status-dot" id="status-indicator"></div>
891
- <span id="status-text">Server Ready</span>
892
- </div>
893
  </div>
894
- <div>
895
- <!-- Clean logo design linking HF -->
896
- <a href="https://huggingface.co" target="_blank" style="color: inherit; text-decoration: none; font-size: 24px; filter: drop-shadow(0 0 8px rgba(255,255,255,0.15));">πŸ€—</a>
897
- </div>
898
- </header>
899
-
900
- <!-- Chat History -->
901
- <section class="chat-history" id="chat-history">
902
-
903
- <!-- Welcome Card -->
904
- <div class="welcome-card" id="welcome-card">
905
- <span class="welcome-icon">πŸ€—</span>
906
- <h2 class="welcome-title">VibeThinker-3B Engine</h2>
907
- <p class="welcome-description">
908
- VibeThinker-3B is a 3-billion parameter Small Language Model (SLM) trained using curriculum post-training SFT and multi-domain Reinforcement Learning. It achieves near-frontier results on math, coding, and STEM by using a dedicated chain-of-thought strategy.
 
 
 
 
 
 
 
909
  </p>
910
- <div class="welcome-grid">
911
- <div class="welcome-feature">
912
- <div class="welcome-feature-title"><i class="fa-solid fa-brain"></i> Chain-of-Thought</div>
913
- <div class="welcome-feature-desc">Analyzes problems thoroughly inside <code>&lt;think&gt;</code> tags before giving the final answer.</div>
 
 
 
 
 
 
 
 
914
  </div>
915
- <div class="welcome-feature">
916
- <div class="welcome-feature-title"><i class="fa-solid fa-code"></i> Coding Logic</div>
917
- <div class="welcome-feature-desc">Excellent at LeetCode, algorithms, complexity analysis, and programming constructs.</div>
 
918
  </div>
919
- <div class="welcome-feature">
920
- <div class="welcome-feature-title"><i class="fa-solid fa-calculator"></i> STEM Focus</div>
921
- <div class="welcome-feature-desc">Verifiably correct solutions to mathematical and reasoning challenges.</div>
 
922
  </div>
923
- <div class="welcome-feature">
924
- <div class="welcome-feature-title"><i class="fa-solid fa-cloud"></i> Gradio Server</div>
925
- <div class="welcome-feature-desc">Custom web front-end backed by Gradio's advanced queuing & ZeroGPU engine.</div>
 
926
  </div>
927
  </div>
928
  </div>
929
 
930
- </section>
931
-
932
- <!-- Input Panel -->
933
- <footer class="input-panel">
934
- <div class="input-wrapper">
935
- <textarea class="chat-textarea" id="chat-input" placeholder="Ask a math, logic, or programming question..." rows="1" oninput="autoResizeTextarea(this)"></textarea>
936
-
937
- <div class="input-actions">
938
- <div class="input-left-buttons">
939
- <button class="action-icon-btn" title="Clear Chat" onclick="clearChat()">
 
 
 
 
940
  <i class="fa-regular fa-trash-can"></i>
941
  </button>
942
- <button class="action-icon-btn" title="Preset Library" onclick="toggleSidebar()">
943
  <i class="fa-solid fa-sliders"></i>
944
  </button>
945
  </div>
946
-
947
- <button class="send-btn" id="submit-btn" onclick="handleSubmit()">
948
- <i class="fa-solid fa-paper-plane" id="submit-icon"></i> <span id="submit-text">Send</span>
949
  </button>
950
  </div>
951
  </div>
952
- <div class="info-footer">
953
- VibeThinker-3B can produce incorrect reasoning paths. Verify output before critical use.
954
- </div>
955
- </footer>
956
-
957
- </main>
958
-
959
- </div>
960
-
961
- <!-- Gradio Client & Execution Logic -->
962
- <script type="module">
963
- import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
964
-
965
- let client = null;
966
- let activeJob = null;
967
- let isGenerating = false;
968
- let currentMessageId = null;
969
- let timerInterval = null;
970
-
971
- // Connect to Gradio.Server backend
972
- async function initClient() {
973
- try {
974
- updateStatus("connecting", "Initializing connection...");
975
- client = await Client.connect(window.location.origin);
976
- updateStatus("ready", "Server Ready");
977
- } catch (err) {
978
- console.error("Failed to connect to server:", err);
979
- updateStatus("error", "Error Connecting to Server");
980
- }
981
- }
982
-
983
- // Connect on load
984
- window.addEventListener("DOMContentLoaded", () => {
985
- initClient();
986
- setupSliders();
987
- });
988
-
989
- // Setup configuration sliders value update
990
- function setupSliders() {
991
- const sliders = [
992
- { id: "param-temp", valId: "val-temp" },
993
- { id: "param-topp", valId: "val-topp" },
994
- { id: "param-tokens", valId: "val-tokens" }
995
- ];
996
-
997
- sliders.forEach(slider => {
998
- const input = document.getElementById(slider.id);
999
- const valueSpan = document.getElementById(slider.valId);
1000
-
1001
- input.addEventListener("input", () => {
1002
- valueSpan.textContent = input.value;
1003
- });
1004
  });
1005
- }
1006
-
1007
- // Toggle sidebar on mobile
1008
- window.toggleSidebar = function() {
1009
- const sidebar = document.getElementById("sidebar");
1010
- sidebar.classList.toggle("open");
 
 
 
 
 
 
 
 
 
 
1011
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1012
 
1013
- // Load example presets
1014
- window.loadPreset = function(type) {
1015
- const chatInput = document.getElementById("chat-input");
1016
- if (type === 'palindrome') {
1017
- chatInput.value = "Write a Python function to solve the Longest Palindromic Substring problem using dynamic programming, with full complexity analysis.";
1018
- } else if (type === 'card') {
1019
- chatInput.value = "If a card is drawn from a standard deck, what is the probability that it is a spade or a face card?";
1020
- } else if (type === 'logic') {
1021
- chatInput.value = "Three boxes are labeled 'Apples', 'Oranges', and 'Mixed'. All labels are incorrect. You can pick one fruit from one box. How can you label all boxes correctly?";
1022
- }
1023
- autoResizeTextarea(chatInput);
1024
-
1025
- // Close sidebar on mobile after choosing preset
1026
- if (window.innerWidth <= 768) {
1027
- document.getElementById("sidebar").classList.remove("open");
1028
- }
1029
- chatInput.focus();
1030
- };
1031
-
1032
- // Auto-resize textarea as user types
1033
- window.autoResizeTextarea = function(element) {
1034
- element.style.height = "auto";
1035
- element.style.height = (element.scrollHeight) + "px";
1036
- };
1037
-
1038
- // Update server status UI
1039
- function updateStatus(status, text) {
1040
- const indicator = document.getElementById("status-indicator");
1041
- const textSpan = document.getElementById("status-text");
1042
-
1043
- textSpan.textContent = text;
1044
-
1045
- indicator.className = "status-dot";
1046
- if (status === "connecting") {
1047
- indicator.classList.add("busy");
1048
- } else if (status === "generating") {
1049
- indicator.classList.add("busy");
1050
- } else if (status === "ready") {
1051
- // defaults to green
1052
- } else if (status === "error") {
1053
- indicator.style.backgroundColor = "#ef4444";
1054
- indicator.style.boxShadow = "0 0 8px #ef4444";
1055
- }
1056
- }
1057
-
1058
- // Main Submit handling
1059
- window.handleSubmit = async function() {
1060
- if (isGenerating) {
1061
- // Stop action
1062
- if (activeJob) {
1063
- activeJob.cancel();
1064
- isGenerating = false;
1065
- updateStatus("ready", "Generation Stopped");
1066
- resetSubmitButton();
1067
- }
1068
- return;
1069
- }
1070
-
1071
- const inputField = document.getElementById("chat-input");
1072
- const prompt = inputField.value.trim();
1073
-
1074
- if (!prompt) return;
1075
- if (!client) {
1076
- alert("Server connection is not established. Trying to reconnect...");
1077
- initClient();
1078
- return;
1079
- }
1080
-
1081
- // Hide welcome card if visible
1082
- const welcomeCard = document.getElementById("welcome-card");
1083
- if (welcomeCard) welcomeCard.remove();
1084
-
1085
- // Clear input and adjust size
1086
- inputField.value = "";
1087
- autoResizeTextarea(inputField);
1088
-
1089
- // Append user bubble
1090
- appendMessage("user", prompt);
1091
-
1092
- // Create empty assistant bubble for streaming
1093
- currentMessageId = appendMessage("assistant", "", true);
1094
-
1095
- // Change button state to "Stop"
1096
- setSubmitButtonToStop();
1097
- isGenerating = true;
1098
- updateStatus("generating", "Thinking...");
1099
-
1100
- // Fetch parameters
1101
- const temp = parseFloat(document.getElementById("param-temp").value);
1102
- const topp = parseFloat(document.getElementById("param-topp").value);
1103
- const tokens = parseInt(document.getElementById("param-tokens").value);
1104
- const system = document.getElementById("param-system").value;
1105
-
1106
- // Start elapsed timer
1107
- let elapsedSeconds = 0;
1108
- const timerSpan = document.getElementById(`timer-${currentMessageId}`);
1109
- if (timerSpan) {
1110
- timerInterval = setInterval(() => {
1111
- elapsedSeconds += 0.1;
1112
- timerSpan.textContent = `Thinking for ${elapsedSeconds.toFixed(1)}s`;
1113
- }, 100);
1114
- }
1115
-
1116
- try {
1117
- // Submit stream request using submit() instead of predict()
1118
- activeJob = client.submit("/predict", {
1119
- message: prompt,
1120
- system_prompt: system,
1121
- temperature: temp,
1122
- top_p: topp,
1123
- max_tokens: tokens
1124
- });
1125
-
1126
- // Consume stream using async iterator
1127
- (async () => {
1128
- try {
1129
- for await (const msg of activeJob) {
1130
- if (msg && msg.data) {
1131
- const streamedText = msg.data[0];
1132
- updateAssistantStream(currentMessageId, streamedText);
1133
- }
1134
  }
1135
- finalizeGeneration(currentMessageId);
1136
- } catch (err) {
1137
- console.error("Stream reading error:", err);
1138
- clearInterval(timerInterval);
1139
- updateMessageError(currentMessageId, "An error occurred during text generation.");
1140
- isGenerating = false;
1141
- resetSubmitButton();
1142
- updateStatus("ready", "Error");
1143
  }
1144
- })();
1145
-
1146
- } catch (err) {
1147
- console.error("Submission failed:", err);
1148
- clearInterval(timerInterval);
1149
- updateMessageError(currentMessageId, "Failed to submit request.");
1150
- isGenerating = false;
1151
- resetSubmitButton();
1152
- updateStatus("ready", "Failed");
1153
- }
1154
- };
1155
-
1156
- function setSubmitButtonToStop() {
1157
- const btn = document.getElementById("submit-btn");
1158
- const icon = document.getElementById("submit-icon");
1159
- const text = document.getElementById("submit-text");
1160
-
1161
- btn.className = "send-btn stop";
1162
- icon.className = "fa-solid fa-circle-stop";
1163
- text.textContent = "Stop";
1164
- }
1165
-
1166
- function resetSubmitButton() {
1167
- const btn = document.getElementById("submit-btn");
1168
- const icon = document.getElementById("submit-icon");
1169
- const text = document.getElementById("submit-text");
1170
-
1171
- btn.className = "send-btn";
1172
- icon.className = "fa-solid fa-paper-plane";
1173
- text.textContent = "Send";
1174
- }
1175
-
1176
- // Helper to format/parse text stream for collapsible thoughts
1177
- function parseStreamedText(text) {
1178
- let thinkingText = "";
1179
- let responseText = "";
1180
- let isThinking = false;
1181
- let hasThoughtCompleted = false;
1182
-
1183
- if (!text || typeof text !== "string") {
1184
- return { thinkingText, responseText, isThinking, hasThoughtCompleted };
1185
- }
1186
-
1187
- if (text.includes("<think>")) {
1188
- const startIndex = text.indexOf("<think>") + 7;
1189
- if (text.includes("</think>")) {
1190
- const endIndex = text.indexOf("</think>");
1191
- thinkingText = text.substring(startIndex, endIndex).trim();
1192
- responseText = text.substring(endIndex + 8).trim();
1193
- hasThoughtCompleted = true;
1194
- } else {
1195
- thinkingText = text.substring(startIndex);
1196
- isThinking = true;
1197
  }
1198
- } else {
1199
- responseText = text;
1200
- }
1201
-
1202
- return {
1203
- thinkingText,
1204
- responseText,
1205
- isThinking,
1206
- hasThoughtCompleted
1207
- };
1208
- }
1209
 
1210
- // Append a message bubble to the chat
1211
- function appendMessage(sender, text, isLoading = false) {
1212
- const chatHistory = document.getElementById("chat-history");
1213
- const messageId = "msg-" + Date.now();
1214
-
1215
- const row = document.createElement("div");
1216
- row.className = `message-row ${sender}`;
1217
- row.id = messageId;
1218
-
1219
- const bubble = document.createElement("div");
1220
- bubble.className = "message-bubble";
1221
-
1222
- if (sender === "user") {
1223
- const textNode = document.createElement("div");
1224
- textNode.textContent = text;
1225
- bubble.appendChild(textNode);
1226
- } else {
1227
- // Assistant template layout with collapsible thought block
1228
- bubble.innerHTML = `
1229
- <div class="thought-container" id="thought-box-${messageId}" style="display: none;">
1230
- <div class="thought-header" onclick="toggleThought('${messageId}')">
1231
- <div class="thought-title-left">
1232
- <i class="fa-solid fa-brain"></i>
1233
- <span id="thought-label-${messageId}">Thinking Process</span>
1234
- <span class="thought-timer" id="timer-${messageId}">Thinking...</span>
1235
- </div>
1236
- <div id="thought-status-${messageId}">
1237
- <i class="fa-solid fa-spinner thought-status-spinner"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1238
  </div>
 
1239
  </div>
1240
- <div class="thought-content" id="thought-content-${messageId}"></div>
1241
  </div>
1242
- <div class="message-body" id="body-${messageId}">
1243
- ${isLoading ? '<div class="typing-indicator"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>' : ''}
 
 
 
 
 
1244
  </div>
1245
- `;
1246
- }
1247
-
1248
- // Meta timestamp
1249
- const meta = document.createElement("div");
1250
- meta.className = "message-meta";
1251
- const timeStr = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1252
- meta.innerHTML = `<i class="fa-regular fa-clock"></i> ${timeStr}`;
1253
- bubble.appendChild(meta);
1254
-
1255
- row.appendChild(bubble);
1256
- chatHistory.appendChild(row);
1257
- chatHistory.scrollTop = chatHistory.scrollHeight;
1258
-
1259
- return messageId;
1260
- }
1261
-
1262
- // Toggle Collapsible thought accordion
1263
- window.toggleThought = function(messageId) {
1264
- const content = document.getElementById(`thought-content-${messageId}`);
1265
- if (content.style.display === "none") {
1266
- content.style.display = "block";
1267
- } else {
1268
- content.style.display = "none";
1269
- }
1270
- };
1271
 
1272
- // Update assistant response bubble on stream
1273
- function updateAssistantStream(messageId, fullText) {
1274
- const parsed = parseStreamedText(fullText);
1275
- const thoughtBox = document.getElementById(`thought-box-${messageId}`);
1276
- const thoughtContent = document.getElementById(`thought-content-${messageId}`);
1277
- const thoughtLabel = document.getElementById(`thought-label-${messageId}`);
1278
- const thoughtStatus = document.getElementById(`thought-status-${messageId}`);
1279
- const bodyDiv = document.getElementById(`body-${messageId}`);
1280
-
1281
- // Update Thought process box
1282
- if (parsed.thinkingText) {
1283
- thoughtBox.style.display = "block";
1284
- thoughtContent.textContent = parsed.thinkingText;
1285
-
1286
- if (parsed.hasThoughtCompleted) {
1287
- // Stop timer and change spinner to check
1288
- clearInterval(timerInterval);
1289
- const timerSpan = document.getElementById(`timer-${messageId}`);
1290
- const durationStr = timerSpan.textContent.replace("Thinking for ", "");
1291
- timerSpan.textContent = `Thought for ${durationStr}`;
1292
-
1293
- thoughtLabel.textContent = "Reasoning Process Complete";
1294
- thoughtStatus.innerHTML = '<i class="fa-solid fa-circle-check thought-status-done"></i>';
1295
-
1296
- // Collapse thought content once reasoning finishes and final response starts
1297
- if (parsed.responseText && thoughtContent.style.display !== "none" && !thoughtContent.dataset.collapsedOnce) {
1298
- thoughtContent.style.display = "none";
1299
- thoughtContent.dataset.collapsedOnce = "true";
1300
- }
1301
- }
1302
- }
1303
-
1304
- // Update response body with parsed Markdown
1305
- if (parsed.responseText) {
1306
- bodyDiv.innerHTML = marked.parse(parsed.responseText);
1307
-
1308
- // Highlight code syntax
1309
- Prism.highlightAllUnder(bodyDiv);
1310
-
1311
- // Setup code block copy buttons
1312
- setupCodeBlockHeaders(bodyDiv);
1313
- } else if (parsed.hasThoughtCompleted && !parsed.responseText) {
1314
- bodyDiv.innerHTML = '<div class="typing-indicator"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>';
1315
- }
1316
-
1317
- // Typeset Math equations once completed
1318
- if (parsed.hasThoughtCompleted && (fullText.endsWith(".") || fullText.endsWith("```") || !isGenerating)) {
1319
- debouncedMathTypeset();
1320
- }
1321
-
1322
- const chatHistory = document.getElementById("chat-history");
1323
- chatHistory.scrollTop = chatHistory.scrollHeight;
1324
 
1325
- // Handle stream completion check
1326
- if (parsed.hasThoughtCompleted && parsed.responseText && activeJob && activeJob.is_complete) {
1327
- finalizeGeneration(messageId);
 
 
 
 
 
 
 
 
 
 
1328
  }
1329
  }
1330
 
1331
- // Debounce MathJax typeset to avoid layout thrashing during fast streams
1332
- let mathTimeout = null;
1333
- function debouncedMathTypeset() {
1334
- clearTimeout(mathTimeout);
1335
- mathTimeout = setTimeout(() => {
1336
- if (window.MathJax && window.MathJax.typesetPromise) {
1337
- window.MathJax.typesetPromise().catch((err) => console.log("MathJax typesetting error: ", err));
1338
- }
1339
- }, 500);
1340
- }
1341
-
1342
- // Setup headers and copy buttons for code segments
1343
- function setupCodeBlockHeaders(container) {
1344
- const codeBlocks = container.querySelectorAll("pre");
1345
- codeBlocks.forEach((pre) => {
1346
- if (pre.querySelector(".code-header")) return; // Header already exists
1347
-
1348
- const code = pre.querySelector("code");
1349
- let lang = "code";
1350
- // Extract language class e.g., language-python
1351
- const classNames = code.className.split(" ");
1352
- classNames.forEach(cls => {
1353
- if (cls.startsWith("language-")) {
1354
- lang = cls.replace("language-", "");
1355
- }
1356
- });
1357
-
1358
- const header = document.createElement("div");
1359
- header.className = "code-header";
1360
- header.innerHTML = `
1361
- <span>${lang.toUpperCase()}</span>
1362
- <button class="copy-btn" onclick="copyCode(this)"><i class="fa-regular fa-copy"></i> Copy</button>
1363
- `;
1364
- pre.insertBefore(header, code);
1365
- });
1366
  }
1367
 
1368
- // Code copy implementation
1369
- window.copyCode = function(button) {
1370
- const pre = button.closest("pre");
1371
- const code = pre.querySelector("code").textContent;
1372
- navigator.clipboard.writeText(code).then(() => {
1373
- button.innerHTML = '<i class="fa-solid fa-check" style="color: #22c55e"></i> Copied!';
1374
- setTimeout(() => {
1375
- button.innerHTML = '<i class="fa-regular fa-copy"></i> Copy';
1376
- }, 2000);
1377
- });
1378
- };
1379
 
1380
- function updateMessageError(messageId, text) {
1381
- const bodyDiv = document.getElementById(`body-${messageId}`);
1382
- if (bodyDiv) {
1383
- bodyDiv.innerHTML = `<span style="color: #ef4444; font-weight: 500;"><i class="fa-solid fa-triangle-exclamation"></i> ${text}</span>`;
1384
- }
1385
- }
 
1386
 
1387
- // Finalize generation
1388
- function finalizeGeneration(messageId) {
1389
- clearInterval(timerInterval);
1390
- isGenerating = false;
1391
- resetSubmitButton();
1392
- updateStatus("ready", "Server Ready");
1393
-
1394
- // Final full typeset pass
1395
- if (window.MathJax && window.MathJax.typesetPromise) {
1396
- window.MathJax.typesetPromise();
1397
- }
1398
  }
 
1399
 
1400
- // Override activeJob data completion hook
1401
- // Since activeJob event on completed is standard, we finalize when streaming finishes
1402
- document.getElementById("chat-input").addEventListener("keydown", (e) => {
1403
- if (e.key === "Enter" && !e.shiftKey) {
1404
- e.preventDefault();
1405
- handleSubmit();
1406
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1407
  });
 
1408
 
1409
- // Clear entire conversation
1410
- window.clearChat = function() {
1411
- if (isGenerating) {
1412
- if (activeJob) activeJob.cancel();
1413
- isGenerating = false;
1414
- resetSubmitButton();
1415
- }
1416
-
1417
- const chatHistory = document.getElementById("chat-history");
1418
- chatHistory.innerHTML = `
1419
- <div class="welcome-card" id="welcome-card">
1420
- <span class="welcome-icon">πŸ€—</span>
1421
- <h2 class="welcome-title">VibeThinker-3B Engine</h2>
1422
- <p class="welcome-description">
1423
- VibeThinker-3B is a 3-billion parameter Small Language Model (SLM) trained using curriculum post-training SFT and multi-domain Reinforcement Learning. It achieves near-frontier results on math, coding, and STEM by using a dedicated chain-of-thought strategy.
1424
- </p>
1425
- <div class="welcome-grid">
1426
- <div class="welcome-feature">
1427
- <div class="welcome-feature-title"><i class="fa-solid fa-brain"></i> Chain-of-Thought</div>
1428
- <div class="welcome-feature-desc">Analyzes problems thoroughly inside <code>&lt;think&gt;</code> tags before giving the final answer.</div>
1429
- </div>
1430
- <div class="welcome-feature">
1431
- <div class="welcome-feature-title"><i class="fa-solid fa-code"></i> Coding Logic</div>
1432
- <div class="welcome-feature-desc">Excellent at LeetCode, algorithms, complexity analysis, and programming constructs.</div>
1433
- </div>
1434
- <div class="welcome-feature">
1435
- <div class="welcome-feature-title"><i class="fa-solid fa-calculator"></i> STEM Focus</div>
1436
- <div class="welcome-feature-desc">Verifiably correct solutions to mathematical and reasoning challenges.</div>
1437
- </div>
1438
- <div class="welcome-feature">
1439
- <div class="welcome-feature-title"><i class="fa-solid fa-cloud"></i> Gradio Server</div>
1440
- <div class="welcome-feature-desc">Custom web front-end backed by Gradio's advanced queuing & ZeroGPU engine.</div>
1441
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1442
  </div>
1443
  </div>
1444
- `;
1445
- updateStatus("ready", "Server Ready");
1446
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1447
 
1448
- // Stream state is now fully managed by the async iterator loop
1449
- </script>
1450
  </body>
1451
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>VibeThinker-3B β€” WeiboAI Reasoning</title>
7
+ <meta name="description" content="VibeThinker-3B: A small language model achieving frontier-level verifiable reasoning in math, coding, and STEM by WeiboAI.">
8
+
9
  <!-- Fonts -->
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
13
+
14
+ <!-- FontAwesome -->
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
 
 
16
 
17
+ <!-- Prism.js (light theme for Claude-style) -->
18
+ <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" rel="stylesheet">
19
+
20
+ <!-- marked.js -->
21
  <script src="https://cdn.jsdelivr.net/npm/marked@9.1.6/marked.min.js"></script>
22
 
23
+ <!-- Prism.js core + languages -->
24
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
25
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"></script>
26
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script>
27
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
28
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-cpp.min.js"></script>
29
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-java.min.js"></script>
30
+
31
+ <!-- MathJax -->
32
  <script>
33
  window.MathJax = {
34
  tex: {
 
36
  displayMath: [['$$', '$$'], ['\\[', '\\]']],
37
  processEscapes: true
38
  },
39
+ options: { skipHtmlTags: ['script','noscript','style','textarea','pre','code'] }
 
 
40
  };
41
  </script>
42
  <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
43
 
44
  <style>
45
+ /* ─── Design Tokens ─────────────────────────────────────────────── */
46
  :root {
47
+ /* WeiboAI brand colors extracted from hummingbird logo */
48
+ --brand-deep: #C0310A; /* deep crimson-red */
49
+ --brand-mid: #E04015; /* primary orange-red */
50
+ --brand-bright: #F26027; /* vivid orange */
51
+ --brand-light: #F5874F; /* soft orange */
52
+ --brand-pale: #FEF0EB; /* blush tint */
53
+
54
+ /* Claude-style neutrals */
55
+ --bg: #FFFFFF;
56
+ --bg-sidebar: #F7F6F3;
57
+ --bg-hover: #EFEDE8;
58
+ --bg-input: #F9F8F6;
59
+ --border: #E8E6E1;
60
+ --border-light: #F0EEE9;
61
+
62
+ --text-primary: #1A1A18;
63
+ --text-secondary:#6B6560;
64
+ --text-muted: #9B9590;
65
+ --text-on-brand: #FFFFFF;
66
+
67
+ /* Shadows */
68
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
69
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.08);
70
+ --shadow-lg: 0 8px 24px rgba(0,0,0,0.10);
71
+
72
+ --sidebar-w: 260px;
73
+ --radius: 12px;
74
+ --radius-sm: 8px;
75
+ --radius-xs: 6px;
76
+ }
77
+
78
+ /* ─── Reset ─────────────────────────────────────────────────────── */
79
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
80
+
81
+ html, body {
82
+ height: 100%;
83
+ font-family: 'Inter', system-ui, sans-serif;
84
+ font-size: 15px;
85
+ color: var(--text-primary);
86
+ background: var(--bg);
87
+ -webkit-font-smoothing: antialiased;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
 
90
+ /* ─── Layout ─────────────────────────────────────────────────────── */
91
+ .app {
92
  display: flex;
 
93
  height: 100vh;
94
+ overflow: hidden;
 
95
  }
96
 
97
+ /* ─── Sidebar ────────────────────────────────────────────────────── */
98
  .sidebar {
99
+ width: var(--sidebar-w);
100
+ background: var(--bg-sidebar);
101
+ border-right: 1px solid var(--border);
 
 
102
  display: flex;
103
  flex-direction: column;
104
+ flex-shrink: 0;
105
+ overflow: hidden;
106
+ transition: width 0.22s ease;
107
  }
108
 
109
+ .sidebar.collapsed { width: 0; border-right: none; }
110
+
111
+ /* Brand / Logo area */
112
+ .sidebar-header {
113
+ padding: 20px 16px 14px;
114
  display: flex;
115
  align-items: center;
116
+ gap: 10px;
117
+ border-bottom: 1px solid var(--border);
118
  }
119
 
120
  .brand-logo {
121
+ width: 34px;
122
+ height: 34px;
123
+ border-radius: 8px;
124
+ object-fit: cover;
125
+ flex-shrink: 0;
126
  }
127
 
128
+ .brand-name {
129
+ font-size: 15px;
130
  font-weight: 700;
131
+ color: var(--text-primary);
132
+ letter-spacing: -0.3px;
 
 
 
133
  }
134
 
135
+ .brand-tag {
136
+ display: inline-block;
137
+ font-size: 10px;
 
 
 
 
138
  font-weight: 600;
139
+ background: var(--brand-pale);
140
+ color: var(--brand-mid);
141
+ border: 1px solid rgba(224,64,21,0.2);
142
+ padding: 1px 6px;
143
+ border-radius: 20px;
144
+ letter-spacing: 0.2px;
145
  }
146
 
147
+ /* Sidebar body / sections */
148
+ .sidebar-body {
149
+ flex: 1;
150
+ overflow-y: auto;
151
+ padding: 12px 10px;
152
  }
153
 
154
+ .sidebar-body::-webkit-scrollbar { width: 4px; }
155
+ .sidebar-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
156
+
157
+ .sidebar-section-label {
158
+ font-size: 11px;
159
  font-weight: 600;
160
  text-transform: uppercase;
161
+ letter-spacing: 0.8px;
162
  color: var(--text-muted);
163
+ padding: 8px 6px 6px;
 
 
 
164
  }
165
 
166
+ /* Parameter controls */
167
+ .param-group { margin-bottom: 14px; padding: 0 4px; }
 
 
168
 
169
+ .param-row {
170
  display: flex;
171
  justify-content: space-between;
172
+ align-items: center;
173
+ margin-bottom: 5px;
 
174
  }
175
 
176
+ .param-label { font-size: 12.5px; color: var(--text-secondary); font-weight: 500; }
177
+
178
+ .param-val {
179
+ font-size: 12px;
180
  font-weight: 600;
181
+ color: var(--brand-mid);
182
+ min-width: 36px;
183
+ text-align: right;
184
  }
185
 
186
  input[type="range"] {
187
  width: 100%;
188
+ height: 4px;
189
+ background: var(--border);
190
+ border-radius: 2px;
191
  outline: none;
192
  -webkit-appearance: none;
193
+ cursor: pointer;
194
  }
 
195
  input[type="range"]::-webkit-slider-thumb {
196
  -webkit-appearance: none;
197
  width: 14px;
198
  height: 14px;
199
  border-radius: 50%;
200
+ background: var(--brand-mid);
201
+ box-shadow: 0 0 0 3px rgba(224,64,21,0.15);
202
+ transition: transform 0.1s;
 
203
  }
204
+ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.15); }
205
 
206
+ .system-prompt-textarea {
 
 
 
 
207
  width: 100%;
208
+ background: var(--bg);
209
+ border: 1px solid var(--border);
210
+ border-radius: var(--radius-xs);
211
+ color: var(--text-primary);
 
212
  font-family: inherit;
213
+ font-size: 12px;
214
+ line-height: 1.5;
215
+ padding: 8px 10px;
216
  resize: vertical;
217
+ min-height: 74px;
218
  outline: none;
219
+ transition: border-color 0.15s;
220
  }
221
+ .system-prompt-textarea:focus { border-color: var(--brand-light); }
222
 
223
+ /* Preset buttons */
224
+ .preset-btn {
 
 
 
 
225
  display: flex;
226
+ align-items: center;
227
  gap: 8px;
 
 
 
228
  width: 100%;
229
+ padding: 8px 10px;
230
+ border-radius: var(--radius-xs);
231
+ border: none;
232
+ background: transparent;
233
+ color: var(--text-secondary);
234
+ font-size: 12.5px;
235
+ font-family: inherit;
236
  cursor: pointer;
237
  text-align: left;
238
+ transition: background 0.12s, color 0.12s;
239
+ }
240
+ .preset-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
241
+ .preset-btn .preset-icon {
242
+ width: 22px; height: 22px;
243
+ background: var(--brand-pale);
244
+ border-radius: 5px;
245
+ display: flex; align-items: center; justify-content: center;
246
+ font-size: 11px;
247
+ color: var(--brand-mid);
248
+ flex-shrink: 0;
 
 
 
 
 
249
  }
250
 
251
  .sidebar-footer {
252
+ padding: 12px 14px;
253
+ border-top: 1px solid var(--border);
 
 
254
  display: flex;
255
  flex-direction: column;
256
+ gap: 6px;
257
  }
258
 
259
+ .footer-link {
 
 
260
  display: flex;
261
  align-items: center;
262
+ gap: 7px;
263
+ font-size: 12px;
264
+ color: var(--text-muted);
265
+ text-decoration: none;
266
+ padding: 4px 2px;
267
+ border-radius: 4px;
268
+ transition: color 0.12s;
269
  }
270
+ .footer-link:hover { color: var(--brand-mid); }
271
+ .footer-link i { width: 14px; text-align: center; }
272
 
273
+ /* ─── Main Panel ─────────────────────────────────────────────────── */
274
+ .main {
275
  flex: 1;
276
  display: flex;
277
  flex-direction: column;
278
+ min-width: 0;
279
+ background: var(--bg);
 
 
280
  }
281
 
282
+ /* Top bar */
283
+ .topbar {
284
+ height: 56px;
285
+ border-bottom: 1px solid var(--border);
286
+ padding: 0 20px;
287
  display: flex;
288
  align-items: center;
289
  justify-content: space-between;
290
+ flex-shrink: 0;
291
+ background: var(--bg);
292
  }
293
 
294
+ .topbar-left { display: flex; align-items: center; gap: 12px; }
 
 
 
295
 
296
+ .toggle-sidebar-btn {
297
+ width: 32px; height: 32px;
298
+ border: 1px solid var(--border);
299
+ border-radius: var(--radius-xs);
300
+ background: transparent;
301
+ color: var(--text-secondary);
302
+ display: flex; align-items: center; justify-content: center;
303
+ cursor: pointer;
304
+ font-size: 14px;
305
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
306
+ }
307
+ .toggle-sidebar-btn:hover {
308
+ background: var(--bg-hover);
309
+ color: var(--text-primary);
310
+ border-color: var(--border);
311
  }
312
 
313
+ .model-pill {
314
  display: flex;
315
  align-items: center;
316
+ gap: 7px;
317
+ padding: 5px 11px;
318
+ border: 1px solid var(--border);
319
+ border-radius: 20px;
320
+ background: var(--bg);
321
  font-size: 13px;
322
+ font-weight: 500;
323
+ color: var(--text-primary);
324
+ cursor: default;
325
  }
326
 
327
+ .model-dot {
328
+ width: 7px; height: 7px;
 
329
  border-radius: 50%;
330
+ background: var(--brand-mid);
331
+ flex-shrink: 0;
332
+ }
333
+
334
+ .model-dot.busy {
335
+ animation: pulse-dot 1.2s ease-in-out infinite;
336
+ }
337
+
338
+ @keyframes pulse-dot {
339
+ 0%, 100% { opacity: 1; transform: scale(1); }
340
+ 50% { opacity: 0.4; transform: scale(0.7); }
341
  }
342
 
343
+ .topbar-right { display: flex; align-items: center; gap: 8px; }
344
+
345
+ .topbar-action-btn {
346
+ width: 32px; height: 32px;
347
+ border: 1px solid var(--border);
348
+ border-radius: var(--radius-xs);
349
+ background: transparent;
350
+ color: var(--text-secondary);
351
+ display: flex; align-items: center; justify-content: center;
352
+ cursor: pointer;
353
+ font-size: 13px;
354
+ transition: background 0.12s, color 0.12s;
355
+ text-decoration: none;
356
  }
357
+ .topbar-action-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
358
 
359
+ /* ─── Chat area ──────────────────────────────────────────────────── */
360
+ .chat-scroll {
361
  flex: 1;
362
  overflow-y: auto;
 
 
 
 
363
  scroll-behavior: smooth;
364
  }
365
+ .chat-scroll::-webkit-scrollbar { width: 5px; }
366
+ .chat-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
367
 
368
+ .chat-inner {
369
+ max-width: 740px;
370
+ margin: 0 auto;
371
+ padding: 32px 24px 16px;
372
+ }
373
+
374
+ /* ─── Welcome screen ─────────────────────────────────────────────── */
375
+ .welcome {
376
+ padding: 48px 0 32px;
377
+ display: flex;
378
+ flex-direction: column;
379
+ align-items: center;
380
  text-align: center;
381
+ gap: 20px;
382
  }
383
 
384
+ .welcome-logo {
385
+ width: 64px;
386
+ height: 64px;
387
+ border-radius: 16px;
388
+ object-fit: cover;
389
+ box-shadow: var(--shadow-md);
390
  }
391
 
392
  .welcome-title {
393
+ font-size: 26px;
394
  font-weight: 700;
395
+ letter-spacing: -0.5px;
396
+ color: var(--text-primary);
397
  }
398
 
399
+ .welcome-sub {
400
+ font-size: 14.5px;
401
+ color: var(--text-secondary);
402
+ max-width: 520px;
403
+ line-height: 1.65;
404
+ }
405
+
406
+ .welcome-chips {
407
+ display: flex;
408
+ flex-wrap: wrap;
409
+ gap: 8px;
410
+ justify-content: center;
411
+ margin-top: 4px;
412
+ }
413
+
414
+ .chip {
415
+ display: flex;
416
+ align-items: center;
417
+ gap: 6px;
418
+ padding: 7px 13px;
419
+ border: 1px solid var(--border);
420
+ border-radius: 20px;
421
+ font-size: 13px;
422
+ color: var(--text-secondary);
423
+ background: var(--bg);
424
+ cursor: pointer;
425
+ transition: all 0.14s;
426
  }
427
+ .chip:hover {
428
+ border-color: var(--brand-light);
429
+ color: var(--brand-mid);
430
+ background: var(--brand-pale);
431
+ }
432
+ .chip i { font-size: 12px; }
433
 
434
+ .welcome-cards {
435
  display: grid;
436
  grid-template-columns: 1fr 1fr;
437
+ gap: 12px;
438
+ width: 100%;
439
+ margin-top: 8px;
440
  }
441
 
442
+ .welcome-card {
443
  padding: 16px;
444
+ border: 1px solid var(--border);
445
+ border-radius: var(--radius);
446
+ background: var(--bg);
447
+ text-align: left;
448
+ transition: border-color 0.14s, box-shadow 0.14s;
449
  }
450
+ .welcome-card:hover { border-color: var(--brand-light); box-shadow: var(--shadow-sm); }
451
 
452
+ .wc-icon {
453
+ width: 28px; height: 28px;
454
+ background: var(--brand-pale);
455
+ border-radius: 7px;
456
+ display: flex; align-items: center; justify-content: center;
457
+ font-size: 13px;
458
+ color: var(--brand-mid);
459
+ margin-bottom: 10px;
460
  }
461
+ .wc-title { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
462
+ .wc-desc { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
463
 
464
+ /* ─── Messages ───────────────────────────────────────────────────── */
465
+ .message-group {
466
+ margin-bottom: 24px;
 
467
  }
468
 
469
+ /* User message */
470
+ .user-row {
471
  display: flex;
 
 
 
 
 
472
  justify-content: flex-end;
473
+ margin-bottom: 4px;
474
  }
475
 
476
+ .user-bubble {
477
+ max-width: 72%;
478
+ background: var(--brand-pale);
479
+ border: 1px solid rgba(224,64,21,0.12);
480
+ border-radius: 18px 18px 4px 18px;
481
+ padding: 11px 16px;
482
+ font-size: 14.5px;
 
 
 
483
  line-height: 1.6;
484
+ color: var(--text-primary);
485
+ word-wrap: break-word;
486
  }
487
 
488
+ /* Assistant message */
489
+ .assistant-row {
490
+ display: flex;
491
+ align-items: flex-start;
492
+ gap: 12px;
493
  }
494
 
495
+ .assistant-avatar {
496
+ width: 28px;
497
+ height: 28px;
498
+ border-radius: 7px;
499
+ object-fit: cover;
500
+ flex-shrink: 0;
501
+ margin-top: 2px;
502
+ box-shadow: var(--shadow-sm);
503
  }
504
 
505
+ .assistant-content { flex: 1; min-width: 0; }
506
+
507
+ .assistant-name {
508
+ font-size: 13px;
509
+ font-weight: 600;
510
+ color: var(--text-secondary);
511
+ margin-bottom: 6px;
512
  }
513
 
514
+ /* Thinking accordion */
515
+ .think-box {
516
+ border: 1px solid #F0C4B4;
517
+ border-radius: var(--radius-sm);
518
+ background: #FFF7F4;
519
+ margin-bottom: 12px;
520
  overflow: hidden;
521
  }
522
 
523
+ .think-header {
 
 
 
 
 
 
524
  display: flex;
 
525
  align-items: center;
526
+ justify-content: space-between;
527
+ padding: 9px 13px;
528
+ cursor: pointer;
529
  user-select: none;
530
+ gap: 8px;
 
 
 
 
531
  }
532
+ .think-header:hover { background: rgba(224,64,21,0.04); }
533
 
534
+ .think-header-left {
535
  display: flex;
536
  align-items: center;
537
+ gap: 7px;
538
+ font-size: 12.5px;
539
+ font-weight: 600;
540
+ color: var(--brand-mid);
541
  }
542
 
543
+ .think-spinner {
544
+ font-size: 11px;
545
+ color: var(--brand-mid);
546
+ animation: spin 1s linear infinite;
547
+ }
548
+ @keyframes spin { to { transform: rotate(360deg); } }
549
+
550
+ .think-check { color: #22A757; font-size: 13px; }
551
+
552
+ .think-timer {
553
  font-size: 11px;
554
  color: var(--text-muted);
555
+ font-weight: 400;
556
  }
557
 
558
+ .think-body {
559
+ padding: 10px 13px 12px;
560
+ border-top: 1px solid #F0C4B4;
561
+ font-family: 'JetBrains Mono', monospace;
562
+ font-size: 12px;
563
+ line-height: 1.65;
564
+ color: #7A5040;
565
  white-space: pre-wrap;
566
+ max-height: 220px;
567
  overflow-y: auto;
568
+ background: #FFFAF8;
 
569
  }
570
 
571
+ /* Response body markdown */
572
+ .response-body {
573
+ font-size: 14.5px;
574
+ line-height: 1.7;
575
+ color: var(--text-primary);
576
  }
577
 
578
+ .response-body p { margin-bottom: 10px; }
579
+ .response-body p:last-child { margin-bottom: 0; }
 
580
 
581
+ .response-body h1, .response-body h2, .response-body h3 {
582
+ font-weight: 600;
583
+ margin-top: 18px;
584
+ margin-bottom: 6px;
585
+ color: var(--text-primary);
 
 
586
  }
587
+ .response-body h1 { font-size: 18px; }
588
+ .response-body h2 { font-size: 16px; }
589
+ .response-body h3 { font-size: 14.5px; }
590
 
591
+ .response-body ul, .response-body ol {
592
+ padding-left: 20px;
593
+ margin-bottom: 10px;
594
  }
595
+ .response-body li { margin-bottom: 3px; }
596
 
597
+ .response-body strong { font-weight: 600; }
598
+ .response-body em { font-style: italic; }
 
 
 
 
599
 
600
+ .response-body code {
601
+ font-family: 'JetBrains Mono', monospace;
602
+ font-size: 12.5px;
603
+ background: #F4F3F0;
604
+ border: 1px solid var(--border);
605
+ border-radius: 4px;
606
+ padding: 1px 5px;
607
  }
 
 
 
608
 
609
+ .response-body pre {
610
+ margin: 12px 0;
611
+ border-radius: var(--radius-sm);
612
+ overflow: hidden;
613
+ border: 1px solid var(--border);
614
+ box-shadow: var(--shadow-sm);
615
  }
616
 
617
+ .response-body pre code {
618
+ background: none;
619
+ border: none;
620
+ padding: 0;
621
+ font-size: 13px;
622
+ border-radius: 0;
623
  }
624
 
625
+ .response-body table {
626
  width: 100%;
627
  border-collapse: collapse;
628
+ margin: 12px 0;
629
+ font-size: 13.5px;
630
  }
631
+ .response-body th, .response-body td {
632
+ border: 1px solid var(--border);
633
+ padding: 7px 11px;
 
634
  text-align: left;
635
  }
636
+ .response-body th {
637
+ background: var(--bg-sidebar);
 
638
  font-weight: 600;
639
  }
640
 
641
+ /* Code block header */
642
+ .code-block-wrap { position: relative; }
643
+
644
  .code-header {
 
 
 
 
645
  display: flex;
646
  justify-content: space-between;
647
  align-items: center;
648
+ padding: 6px 12px;
649
+ background: #F4F3F0;
650
+ border-bottom: 1px solid var(--border);
651
+ font-size: 11.5px;
652
+ color: var(--text-muted);
653
+ font-family: 'JetBrains Mono', monospace;
654
  }
655
 
656
+ .code-copy-btn {
657
+ background: none;
658
  border: none;
659
  color: var(--text-muted);
660
  cursor: pointer;
661
+ font-size: 11.5px;
662
+ display: flex; align-items: center; gap: 4px;
663
+ padding: 2px 6px;
664
+ border-radius: 4px;
665
+ transition: background 0.12s, color 0.12s;
666
  }
667
+ .code-copy-btn:hover { background: var(--border); color: var(--text-primary); }
668
 
669
+ /* Typing dots */
670
+ .typing { display: flex; gap: 4px; padding: 4px 0; }
671
+ .typing-dot {
672
+ width: 7px; height: 7px;
673
+ background: var(--brand-light);
674
+ border-radius: 50%;
675
+ animation: bounce-dot 1.3s ease-in-out infinite both;
676
+ }
677
+ .typing-dot:nth-child(1) { animation-delay: -0.3s; }
678
+ .typing-dot:nth-child(2) { animation-delay: -0.15s; }
679
+ @keyframes bounce-dot {
680
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
681
+ 40% { transform: scale(1); opacity: 1; }
682
  }
683
 
684
+ /* ─── Input area ─────────────────────────────────────────────────── */
685
+ .input-area {
686
+ padding: 16px 24px 24px;
687
+ flex-shrink: 0;
688
+ background: var(--bg);
 
689
  }
690
 
691
+ .input-inner {
692
+ max-width: 740px;
693
  margin: 0 auto;
694
+ }
695
+
696
+ .input-box {
697
+ border: 1.5px solid var(--border);
698
  border-radius: 14px;
699
+ background: var(--bg-input);
700
  display: flex;
701
  flex-direction: column;
702
+ gap: 0;
703
+ transition: border-color 0.15s, box-shadow 0.15s;
704
+ box-shadow: var(--shadow-sm);
705
  }
706
+ .input-box:focus-within {
707
+ border-color: var(--brand-light);
708
+ box-shadow: 0 0 0 3px rgba(224,64,21,0.08), var(--shadow-sm);
 
709
  }
710
 
711
+ .input-textarea {
712
  width: 100%;
713
  background: transparent;
714
  border: none;
715
  outline: none;
 
716
  font-family: inherit;
717
+ font-size: 14.5px;
718
+ color: var(--text-primary);
719
+ line-height: 1.55;
720
+ padding: 13px 16px 4px;
721
  resize: none;
722
+ min-height: 50px;
723
+ max-height: 220px;
 
 
724
  }
725
+ .input-textarea::placeholder { color: var(--text-muted); }
726
 
727
+ .input-toolbar {
728
  display: flex;
729
  justify-content: space-between;
730
  align-items: center;
731
+ padding: 6px 10px 8px;
 
732
  }
733
 
734
+ .input-left-actions { display: flex; gap: 4px; align-items: center; }
 
 
 
735
 
736
+ .tool-btn {
737
+ width: 30px; height: 30px;
738
  border: none;
739
+ border-radius: 7px;
740
+ background: transparent;
 
741
  color: var(--text-muted);
742
  cursor: pointer;
743
+ display: flex; align-items: center; justify-content: center;
744
+ font-size: 13px;
745
+ transition: background 0.12s, color 0.12s;
 
 
 
 
 
 
746
  }
747
+ .tool-btn:hover { background: var(--bg-hover); color: var(--text-secondary); }
748
 
749
  .send-btn {
 
 
 
 
 
 
 
 
750
  display: flex;
751
  align-items: center;
752
  gap: 6px;
753
+ padding: 7px 15px;
754
+ background: var(--brand-mid);
755
+ color: #fff;
756
+ border: none;
757
+ border-radius: 9px;
758
+ font-family: inherit;
759
+ font-size: 13.5px;
760
+ font-weight: 600;
761
+ cursor: pointer;
762
+ transition: background 0.14s, transform 0.08s;
 
 
 
 
 
 
 
763
  }
764
+ .send-btn:hover { background: var(--brand-deep); }
765
+ .send-btn:active { transform: scale(0.97); }
766
+ .send-btn.stop { background: #DC2626; }
767
+ .send-btn.stop:hover { background: #B91C1C; }
768
 
769
+ .input-hint {
770
  text-align: center;
771
+ font-size: 11.5px;
772
  color: var(--text-muted);
773
+ margin-top: 9px;
774
  }
775
 
776
+ /* ─── Error state ─────────────────────────────────���───────────────── */
777
+ .error-msg {
778
  display: flex;
779
+ align-items: center;
780
+ gap: 7px;
781
+ font-size: 13.5px;
782
+ color: #C0310A;
783
+ background: #FEF2EE;
784
+ border: 1px solid #FCCDB8;
785
+ border-radius: 8px;
786
+ padding: 9px 13px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  }
788
 
789
+ /* ─── Responsive ─────────────────────────────────────────────────── */
790
+ @media (max-width: 640px) {
791
+ .sidebar { position: absolute; z-index: 20; height: 100%; box-shadow: var(--shadow-lg); }
792
+ .sidebar.collapsed { width: 0; }
793
+ .welcome-cards { grid-template-columns: 1fr; }
 
 
 
794
  }
795
 
796
+ /* Scrollbar for prism code blocks */
797
+ .response-body pre::-webkit-scrollbar { height: 4px; }
798
+ .response-body pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
  </style>
800
  </head>
801
  <body>
802
 
803
+ <div class="app">
804
+
805
+ <!-- ═══ SIDEBAR ═══════════════════════════════════════════════════════ -->
806
+ <aside class="sidebar" id="sidebar">
807
 
808
+ <div class="sidebar-header">
809
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
810
+ alt="VibeThinker" class="brand-logo">
 
 
811
  <div>
812
+ <div class="brand-name">VibeThinker</div>
813
+ <span class="brand-tag">3B Β· WeiboAI</span>
814
+ </div>
815
+ </div>
 
 
 
816
 
817
+ <div class="sidebar-body">
 
 
 
 
 
 
 
 
 
 
818
 
819
+ <!-- Parameters -->
820
+ <div class="sidebar-section-label">Parameters</div>
 
 
 
 
 
821
 
822
+ <div class="param-group">
823
+ <div class="param-row">
824
+ <span class="param-label">Temperature</span>
825
+ <span class="param-val" id="val-temp">1.0</span>
826
+ </div>
827
+ <input type="range" id="param-temp" min="0" max="1.5" step="0.05" value="1.0">
828
+ </div>
829
 
830
+ <div class="param-group">
831
+ <div class="param-row">
832
+ <span class="param-label">Top-P</span>
833
+ <span class="param-val" id="val-topp">0.95</span>
 
 
834
  </div>
835
+ <input type="range" id="param-topp" min="0" max="1" step="0.01" value="0.95">
836
+ </div>
837
 
838
+ <div class="param-group">
839
+ <div class="param-row">
840
+ <span class="param-label">Max Tokens</span>
841
+ <span class="param-val" id="val-tokens">4096</span>
 
 
 
 
 
 
 
 
 
 
842
  </div>
843
+ <input type="range" id="param-tokens" min="256" max="32768" step="256" value="4096">
844
  </div>
845
 
846
+ <div class="param-group">
847
+ <div class="param-label" style="margin-bottom:6px">System Prompt</div>
848
+ <textarea class="system-prompt-textarea" id="param-system">You are VibeThinker, a helpful and harmless AI assistant specialized in reasoning. You solve complex problems step-by-step to verify correctness.</textarea>
 
 
 
 
 
 
849
  </div>
850
+
851
+ <!-- Presets -->
852
+ <div class="sidebar-section-label" style="margin-top:4px">Try a prompt</div>
853
+
854
+ <button class="preset-btn" onclick="loadPreset('card')">
855
+ <span class="preset-icon"><i class="fa-solid fa-dice"></i></span>
856
+ Probability β€” card deck
857
+ </button>
858
+ <button class="preset-btn" onclick="loadPreset('palindrome')">
859
+ <span class="preset-icon"><i class="fa-solid fa-code"></i></span>
860
+ Algorithm β€” longest palindrome
861
+ </button>
862
+ <button class="preset-btn" onclick="loadPreset('logic')">
863
+ <span class="preset-icon"><i class="fa-solid fa-box"></i></span>
864
+ Logic β€” labeled fruit boxes
865
+ </button>
866
+
867
+ </div><!-- /sidebar-body -->
868
+
869
+ <div class="sidebar-footer">
870
+ <a class="footer-link" href="https://huggingface.co/WeiboAI/VibeThinker-3B" target="_blank">
871
+ <i class="fa-solid fa-cube"></i> Model Card
872
+ </a>
873
+ <a class="footer-link" href="https://arxiv.org/abs/2606.16140" target="_blank">
874
+ <i class="fa-regular fa-file-lines"></i> Research Paper
875
+ </a>
876
+ <span class="footer-link" style="cursor:default">
877
+ <i class="fa-solid fa-scale-balanced"></i> MIT License Β· WeiboAI 2026
878
+ </span>
879
+ </div>
880
+
881
+ </aside><!-- /sidebar -->
882
+
883
+ <!-- ═══ MAIN ══════════════════════════════════════════════════════════ -->
884
+ <div class="main">
885
+
886
+ <!-- Top bar -->
887
+ <div class="topbar">
888
+ <div class="topbar-left">
889
+ <button class="toggle-sidebar-btn" id="toggle-sidebar-btn" onclick="toggleSidebar()" title="Toggle sidebar">
890
+ <i class="fa-solid fa-sidebar"></i>
891
  </button>
892
+ <div class="model-pill">
893
+ <div class="model-dot" id="status-dot"></div>
894
+ <span id="status-text">VibeThinker-3B</span>
 
 
 
895
  </div>
896
+ </div>
897
+ <div class="topbar-right">
898
+ <button class="topbar-action-btn" title="Clear chat" onclick="clearChat()">
899
+ <i class="fa-regular fa-trash-can"></i>
900
+ </button>
901
+ <a class="topbar-action-btn" href="https://huggingface.co/WeiboAI/VibeThinker-3B" target="_blank" title="Model on HuggingFace">
902
+ πŸ€—
903
+ </a>
904
+ </div>
905
+ </div>
906
+
907
+ <!-- Chat scroll -->
908
+ <div class="chat-scroll" id="chat-scroll">
909
+ <div class="chat-inner" id="chat-inner">
910
+
911
+ <!-- Welcome -->
912
+ <div class="welcome" id="welcome-screen">
913
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
914
+ alt="VibeThinker" class="welcome-logo">
915
+ <h1 class="welcome-title">VibeThinker-3B</h1>
916
+ <p class="welcome-sub">
917
+ A 3B-parameter reasoning model by WeiboAI, achieving frontier-level performance on mathematics, coding, and STEM β€” with transparent chain-of-thought.
918
  </p>
919
+
920
+ <div class="welcome-chips">
921
+ <div class="chip" onclick="loadPreset('palindrome')"><i class="fa-solid fa-code"></i> Coding</div>
922
+ <div class="chip" onclick="loadPreset('card')"><i class="fa-solid fa-calculator"></i> Math</div>
923
+ <div class="chip" onclick="loadPreset('logic')"><i class="fa-solid fa-puzzle-piece"></i> Logic</div>
924
+ </div>
925
+
926
+ <div class="welcome-cards">
927
+ <div class="welcome-card">
928
+ <div class="wc-icon"><i class="fa-solid fa-brain"></i></div>
929
+ <div class="wc-title">Chain-of-Thought</div>
930
+ <div class="wc-desc">Reasoning steps rendered live inside <code>&lt;think&gt;</code> blocks before the final answer.</div>
931
  </div>
932
+ <div class="welcome-card">
933
+ <div class="wc-icon"><i class="fa-solid fa-trophy"></i></div>
934
+ <div class="wc-title">Frontier Accuracy</div>
935
+ <div class="wc-desc">76.4% on IMO-AnswerBench β€” matching models 200Γ— larger.</div>
936
  </div>
937
+ <div class="welcome-card">
938
+ <div class="wc-icon"><i class="fa-solid fa-bolt"></i></div>
939
+ <div class="wc-title">Token Streaming</div>
940
+ <div class="wc-desc">Gradio Server + ZeroGPU stream every token in real time.</div>
941
  </div>
942
+ <div class="welcome-card">
943
+ <div class="wc-icon"><i class="fa-brands fa-python"></i></div>
944
+ <div class="wc-title">96.1% LeetCode</div>
945
+ <div class="wc-desc">123/128 first-attempt submissions on recent weekly contests.</div>
946
  </div>
947
  </div>
948
  </div>
949
 
950
+ </div><!-- /chat-inner -->
951
+ </div><!-- /chat-scroll -->
952
+
953
+ <!-- Input -->
954
+ <div class="input-area">
955
+ <div class="input-inner">
956
+ <div class="input-box">
957
+ <textarea class="input-textarea" id="chat-input"
958
+ placeholder="Ask a math, logic, or coding question…"
959
+ rows="1"
960
+ oninput="autoResize(this)"></textarea>
961
+ <div class="input-toolbar">
962
+ <div class="input-left-actions">
963
+ <button class="tool-btn" title="Clear chat" onclick="clearChat()">
964
  <i class="fa-regular fa-trash-can"></i>
965
  </button>
966
+ <button class="tool-btn" title="Parameters" onclick="toggleSidebar()">
967
  <i class="fa-solid fa-sliders"></i>
968
  </button>
969
  </div>
970
+ <button class="send-btn" id="send-btn" onclick="handleSubmit()">
971
+ <i class="fa-solid fa-paper-plane" id="send-icon"></i>
972
+ <span id="send-text">Send</span>
973
  </button>
974
  </div>
975
  </div>
976
+ <div class="input-hint">VibeThinker can make mistakes. Verify important results.</div>
977
+ </div>
978
+ </div>
979
+
980
+ </div><!-- /main -->
981
+ </div><!-- /app -->
982
+
983
+ <!-- ═══ SCRIPT ════════════════════════════════════════════════════════ -->
984
+ <script type="module">
985
+ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
986
+
987
+ let client = null;
988
+ let activeJob = null;
989
+ let isGenerating = false;
990
+ let currentMsgId = null;
991
+ let timerInterval = null;
992
+ let sidebarOpen = true;
993
+
994
+ // ── Init ──────────────────────────────────────────────────────────────
995
+ window.addEventListener("DOMContentLoaded", () => {
996
+ setupSliders();
997
+ initClient();
998
+ });
999
+
1000
+ async function initClient() {
1001
+ try {
1002
+ setStatus("connecting", "Connecting…");
1003
+ client = await Client.connect(window.location.origin);
1004
+ setStatus("ready", "VibeThinker-3B");
1005
+ } catch (e) {
1006
+ console.error("Client connect failed:", e);
1007
+ setStatus("error", "Connection error");
1008
+ }
1009
+ }
1010
+
1011
+ // ── Sliders ───────────────────────────────────────────────────────────
1012
+ function setupSliders() {
1013
+ [["param-temp","val-temp"],["param-topp","val-topp"],["param-tokens","val-tokens"]]
1014
+ .forEach(([inputId, valId]) => {
1015
+ const el = document.getElementById(inputId);
1016
+ const vl = document.getElementById(valId);
1017
+ el.addEventListener("input", () => { vl.textContent = el.value; });
 
 
 
 
 
 
 
 
 
 
1018
  });
1019
+ }
1020
+
1021
+ // ── Sidebar toggle ────────────────────────────────────────────────────
1022
+ window.toggleSidebar = function() {
1023
+ const sidebar = document.getElementById("sidebar");
1024
+ sidebarOpen = !sidebarOpen;
1025
+ sidebar.classList.toggle("collapsed", !sidebarOpen);
1026
+ };
1027
+
1028
+ // ── Presets ───────────────────────────────────────────────────────────
1029
+ window.loadPreset = function(type) {
1030
+ const ta = document.getElementById("chat-input");
1031
+ const map = {
1032
+ palindrome: "Write a Python function to solve the Longest Palindromic Substring problem, with full complexity analysis.",
1033
+ card: "If a card is drawn from a standard deck, what is the probability it is a spade or a face card?",
1034
+ logic: "Three boxes are labeled 'Apples', 'Oranges', and 'Mixed'. All labels are wrong. Draw one fruit from one box β€” how do you correctly label all three boxes?"
1035
  };
1036
+ ta.value = map[type] || "";
1037
+ autoResize(ta);
1038
+ ta.focus();
1039
+ if (window.innerWidth <= 640) document.getElementById("sidebar").classList.add("collapsed");
1040
+ };
1041
+
1042
+ // ── Auto-resize textarea ──────────────────────────────────────────────
1043
+ window.autoResize = function(el) {
1044
+ el.style.height = "auto";
1045
+ el.style.height = Math.min(el.scrollHeight, 220) + "px";
1046
+ };
1047
+
1048
+ // ── Status indicator ──────────────────────────────────────────────────
1049
+ function setStatus(state, label) {
1050
+ const dot = document.getElementById("status-dot");
1051
+ const txt = document.getElementById("status-text");
1052
+ txt.textContent = label;
1053
+ dot.className = "model-dot";
1054
+ if (state === "connecting" || state === "generating") dot.classList.add("busy");
1055
+ if (state === "error") dot.style.background = "#DC2626";
1056
+ else dot.style.background = "";
1057
+ }
1058
+
1059
+ // ── Submit ────────────────────────────────────────────────────────────
1060
+ window.handleSubmit = async function() {
1061
+ if (isGenerating) {
1062
+ if (activeJob) { try { activeJob.cancel(); } catch(_) {} }
1063
+ isGenerating = false;
1064
+ clearInterval(timerInterval);
1065
+ resetBtn();
1066
+ setStatus("ready", "VibeThinker-3B");
1067
+ return;
1068
+ }
1069
+
1070
+ const ta = document.getElementById("chat-input");
1071
+ const prompt = ta.value.trim();
1072
+ if (!prompt) return;
1073
+ if (!client) { alert("Server not connected. Please wait…"); initClient(); return; }
1074
+
1075
+ // Hide welcome
1076
+ const ws = document.getElementById("welcome-screen");
1077
+ if (ws) ws.remove();
1078
+
1079
+ ta.value = "";
1080
+ autoResize(ta);
1081
+
1082
+ appendUserMsg(prompt);
1083
+ currentMsgId = appendAssistantMsg();
1084
+
1085
+ setToStop();
1086
+ isGenerating = true;
1087
+ setStatus("generating", "Thinking…");
1088
+
1089
+ const temp = parseFloat(document.getElementById("param-temp").value);
1090
+ const topp = parseFloat(document.getElementById("param-topp").value);
1091
+ const tokens = parseInt(document.getElementById("param-tokens").value);
1092
+ const system = document.getElementById("param-system").value;
1093
+
1094
+ // Start elapsed timer
1095
+ let elapsed = 0;
1096
+ const timerEl = document.getElementById("think-timer-" + currentMsgId);
1097
+ if (timerEl) {
1098
+ timerInterval = setInterval(() => {
1099
+ elapsed += 0.1;
1100
+ timerEl.textContent = `${elapsed.toFixed(1)}s`;
1101
+ }, 100);
1102
+ }
1103
+
1104
+ try {
1105
+ activeJob = client.submit("/predict", {
1106
+ message: prompt,
1107
+ system_prompt: system,
1108
+ temperature: temp,
1109
+ top_p: topp,
1110
+ max_tokens: tokens
1111
+ });
1112
 
1113
+ (async () => {
1114
+ try {
1115
+ for await (const msg of activeJob) {
1116
+ if (msg && msg.data) {
1117
+ const text = msg.data[0];
1118
+ if (typeof text === "string") updateStream(currentMsgId, text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1119
  }
 
 
 
 
 
 
 
 
1120
  }
1121
+ finalizeStream(currentMsgId);
1122
+ } catch (err) {
1123
+ console.error("Stream error:", err);
1124
+ clearInterval(timerInterval);
1125
+ showError(currentMsgId, "An error occurred during generation. Please try again.");
1126
+ isGenerating = false;
1127
+ resetBtn();
1128
+ setStatus("ready", "VibeThinker-3B");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
  }
1130
+ })();
 
 
 
 
 
 
 
 
 
 
1131
 
1132
+ } catch (err) {
1133
+ console.error("Submit failed:", err);
1134
+ clearInterval(timerInterval);
1135
+ showError(currentMsgId, "Failed to submit request.");
1136
+ isGenerating = false;
1137
+ resetBtn();
1138
+ setStatus("ready", "VibeThinker-3B");
1139
+ }
1140
+ };
1141
+
1142
+ // ── Button states ─────────────────────────────────────────────────────
1143
+ function setToStop() {
1144
+ document.getElementById("send-btn").className = "send-btn stop";
1145
+ document.getElementById("send-icon").className = "fa-solid fa-stop";
1146
+ document.getElementById("send-text").textContent = "Stop";
1147
+ }
1148
+ function resetBtn() {
1149
+ document.getElementById("send-btn").className = "send-btn";
1150
+ document.getElementById("send-icon").className = "fa-solid fa-paper-plane";
1151
+ document.getElementById("send-text").textContent = "Send";
1152
+ }
1153
+
1154
+ // ── Append messages ───────────────────────────────────────────────────
1155
+ function appendUserMsg(text) {
1156
+ const inner = document.getElementById("chat-inner");
1157
+ const g = document.createElement("div");
1158
+ g.className = "message-group";
1159
+ g.innerHTML = `
1160
+ <div class="user-row">
1161
+ <div class="user-bubble">${escHtml(text)}</div>
1162
+ </div>`;
1163
+ inner.appendChild(g);
1164
+ scrollBottom();
1165
+ }
1166
+
1167
+ function appendAssistantMsg() {
1168
+ const inner = document.getElementById("chat-inner");
1169
+ const id = "msg-" + Date.now();
1170
+ const g = document.createElement("div");
1171
+ g.className = "message-group";
1172
+ g.id = id;
1173
+ g.innerHTML = `
1174
+ <div class="assistant-row">
1175
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
1176
+ alt="VibeThinker" class="assistant-avatar">
1177
+ <div class="assistant-content">
1178
+ <div class="assistant-name">VibeThinker-3B</div>
1179
+ <!-- think box (hidden until we see <think>) -->
1180
+ <div class="think-box" id="think-box-${id}" style="display:none">
1181
+ <div class="think-header" onclick="toggleThink('${id}')">
1182
+ <div class="think-header-left">
1183
+ <i class="fa-solid fa-spinner think-spinner" id="think-spin-${id}"></i>
1184
+ <span id="think-label-${id}">Thinking…</span>
1185
  </div>
1186
+ <span class="think-timer" id="think-timer-${id}">0.0s</span>
1187
  </div>
1188
+ <div class="think-body" id="think-body-${id}" style="display:none"></div>
1189
  </div>
1190
+ <!-- response body -->
1191
+ <div class="response-body" id="resp-body-${id}">
1192
+ <div class="typing">
1193
+ <div class="typing-dot"></div>
1194
+ <div class="typing-dot"></div>
1195
+ <div class="typing-dot"></div>
1196
+ </div>
1197
  </div>
1198
+ </div>
1199
+ </div>`;
1200
+ inner.appendChild(g);
1201
+ scrollBottom();
1202
+ return id;
1203
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1204
 
1205
+ // ── Stream update ─────────────────────────────────────────────────────
1206
+ function updateStream(id, fullText) {
1207
+ const { thinking, response, thinkDone } = parseStream(fullText);
1208
+
1209
+ const thinkBox = document.getElementById("think-box-" + id);
1210
+ const thinkBody = document.getElementById("think-body-" + id);
1211
+ const thinkSpin = document.getElementById("think-spin-" + id);
1212
+ const thinkLbl = document.getElementById("think-label-" + id);
1213
+ const timerEl = document.getElementById("think-timer-" + id);
1214
+ const respBody = document.getElementById("resp-body-" + id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1215
 
1216
+ if (thinking) {
1217
+ thinkBox.style.display = "block";
1218
+ thinkBody.textContent = thinking;
1219
+ }
1220
+
1221
+ if (thinkDone) {
1222
+ clearInterval(timerInterval);
1223
+ thinkSpin.className = "fa-solid fa-check think-check";
1224
+ thinkLbl.textContent = "Reasoning complete";
1225
+ // Auto-collapse body once done
1226
+ if (!thinkBody.dataset.collapsed) {
1227
+ thinkBody.style.display = "none";
1228
+ thinkBody.dataset.collapsed = "true";
1229
  }
1230
  }
1231
 
1232
+ if (response) {
1233
+ respBody.innerHTML = marked.parse(response);
1234
+ setupCodeHeaders(respBody);
1235
+ Prism.highlightAllUnder(respBody);
1236
+ } else if (!thinkDone) {
1237
+ // Still thinking β€” keep typing dots if no response yet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1238
  }
1239
 
1240
+ scrollBottom();
1241
+ debounceMath();
1242
+ }
 
 
 
 
 
 
 
 
1243
 
1244
+ function finalizeStream(id) {
1245
+ clearInterval(timerInterval);
1246
+ isGenerating = false;
1247
+ resetBtn();
1248
+ setStatus("ready", "VibeThinker-3B");
1249
+ if (window.MathJax?.typesetPromise) window.MathJax.typesetPromise();
1250
+ }
1251
 
1252
+ function showError(id, msg) {
1253
+ const respBody = document.getElementById("resp-body-" + id);
1254
+ if (respBody) {
1255
+ respBody.innerHTML = `<div class="error-msg"><i class="fa-solid fa-triangle-exclamation"></i>${escHtml(msg)}</div>`;
 
 
 
 
 
 
 
1256
  }
1257
+ }
1258
 
1259
+ // ── Parse <think>…</think> ────────────────────────────────────────────
1260
+ function parseStream(text) {
1261
+ if (!text || typeof text !== "string") return { thinking: "", response: "", thinkDone: false };
1262
+
1263
+ if (!text.includes("<think>")) return { thinking: "", response: text, thinkDone: false };
1264
+
1265
+ const start = text.indexOf("<think>") + 7;
1266
+ if (text.includes("</think>")) {
1267
+ const end = text.indexOf("</think>");
1268
+ return {
1269
+ thinking: text.substring(start, end).trim(),
1270
+ response: text.substring(end + 8).trim(),
1271
+ thinkDone: true
1272
+ };
1273
+ }
1274
+ return { thinking: text.substring(start), response: "", thinkDone: false };
1275
+ }
1276
+
1277
+ // ── Think accordion toggle ─────────────────────────────────────────────
1278
+ window.toggleThink = function(id) {
1279
+ const body = document.getElementById("think-body-" + id);
1280
+ body.style.display = body.style.display === "none" ? "block" : "none";
1281
+ };
1282
+
1283
+ // ── Code block copy buttons ───────────────────────────────────────────
1284
+ function setupCodeHeaders(container) {
1285
+ container.querySelectorAll("pre").forEach(pre => {
1286
+ if (pre.querySelector(".code-header")) return;
1287
+ const code = pre.querySelector("code");
1288
+ let lang = "";
1289
+ (code?.className || "").split(" ").forEach(c => {
1290
+ if (c.startsWith("language-")) lang = c.replace("language-", "");
1291
+ });
1292
+ const header = document.createElement("div");
1293
+ header.className = "code-header";
1294
+ header.innerHTML = `
1295
+ <span>${lang.toUpperCase() || "CODE"}</span>
1296
+ <button class="code-copy-btn" onclick="copyCode(this)">
1297
+ <i class="fa-regular fa-copy"></i> Copy
1298
+ </button>`;
1299
+ pre.insertBefore(header, code);
1300
  });
1301
+ }
1302
 
1303
+ window.copyCode = function(btn) {
1304
+ const code = btn.closest("pre").querySelector("code")?.textContent || "";
1305
+ navigator.clipboard.writeText(code).then(() => {
1306
+ btn.innerHTML = '<i class="fa-solid fa-check" style="color:#22A757"></i> Copied';
1307
+ setTimeout(() => { btn.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 2000);
1308
+ });
1309
+ };
1310
+
1311
+ // ── Math debounce ─────────────────────────────────────────────────────
1312
+ let mathTimer = null;
1313
+ function debounceMath() {
1314
+ clearTimeout(mathTimer);
1315
+ mathTimer = setTimeout(() => {
1316
+ if (window.MathJax?.typesetPromise) window.MathJax.typesetPromise();
1317
+ }, 600);
1318
+ }
1319
+
1320
+ // ── Clear chat ────────────────────────────────────────────────────────
1321
+ window.clearChat = function() {
1322
+ if (isGenerating) {
1323
+ try { activeJob?.cancel(); } catch(_) {}
1324
+ isGenerating = false;
1325
+ clearInterval(timerInterval);
1326
+ resetBtn();
1327
+ setStatus("ready", "VibeThinker-3B");
1328
+ }
1329
+ const inner = document.getElementById("chat-inner");
1330
+ inner.innerHTML = `
1331
+ <div class="welcome" id="welcome-screen">
1332
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
1333
+ alt="VibeThinker" class="welcome-logo">
1334
+ <h1 class="welcome-title">VibeThinker-3B</h1>
1335
+ <p class="welcome-sub">
1336
+ A 3B-parameter reasoning model by WeiboAI, achieving frontier-level performance on mathematics, coding, and STEM β€” with transparent chain-of-thought.
1337
+ </p>
1338
+ <div class="welcome-chips">
1339
+ <div class="chip" onclick="loadPreset('palindrome')"><i class="fa-solid fa-code"></i> Coding</div>
1340
+ <div class="chip" onclick="loadPreset('card')"><i class="fa-solid fa-calculator"></i> Math</div>
1341
+ <div class="chip" onclick="loadPreset('logic')"><i class="fa-solid fa-puzzle-piece"></i> Logic</div>
1342
+ </div>
1343
+ <div class="welcome-cards">
1344
+ <div class="welcome-card">
1345
+ <div class="wc-icon"><i class="fa-solid fa-brain"></i></div>
1346
+ <div class="wc-title">Chain-of-Thought</div>
1347
+ <div class="wc-desc">Reasoning rendered live in <code>&lt;think&gt;</code> blocks.</div>
1348
+ </div>
1349
+ <div class="welcome-card">
1350
+ <div class="wc-icon"><i class="fa-solid fa-trophy"></i></div>
1351
+ <div class="wc-title">Frontier Accuracy</div>
1352
+ <div class="wc-desc">76.4% on IMO-AnswerBench β€” matching models 200Γ— larger.</div>
1353
+ </div>
1354
+ <div class="welcome-card">
1355
+ <div class="wc-icon"><i class="fa-solid fa-bolt"></i></div>
1356
+ <div class="wc-title">Token Streaming</div>
1357
+ <div class="wc-desc">Gradio Server + ZeroGPU stream every token live.</div>
1358
+ </div>
1359
+ <div class="welcome-card">
1360
+ <div class="wc-icon"><i class="fa-brands fa-python"></i></div>
1361
+ <div class="wc-title">96.1% LeetCode</div>
1362
+ <div class="wc-desc">123/128 first-attempt contest submissions.</div>
1363
  </div>
1364
  </div>
1365
+ </div>`;
1366
+ };
1367
+
1368
+ // ── Helpers ───────────────────────────────────────────────────────────
1369
+ function scrollBottom() {
1370
+ const sc = document.getElementById("chat-scroll");
1371
+ sc.scrollTop = sc.scrollHeight;
1372
+ }
1373
+
1374
+ function escHtml(s) {
1375
+ return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")
1376
+ .replace(/"/g,"&quot;").replace(/'/g,"&#39;");
1377
+ }
1378
+
1379
+ // Enter to submit, Shift+Enter for newline
1380
+ document.getElementById("chat-input").addEventListener("keydown", e => {
1381
+ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); }
1382
+ });
1383
+ </script>
1384
 
 
 
1385
  </body>
1386
  </html>