Ashok75 commited on
Commit
461da3a
·
verified ·
1 Parent(s): 9dee6e2

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +796 -0
index.html ADDED
@@ -0,0 +1,796 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Nanbeige4.1-3B Chat</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --bg-primary: #0f0f0f;
16
+ --bg-secondary: #1a1a1a;
17
+ --bg-tertiary: #252525;
18
+ --accent: #00d4aa;
19
+ --accent-hover: #00b894;
20
+ --text-primary: #ffffff;
21
+ --text-secondary: #a0a0a0;
22
+ --border: #333333;
23
+ --user-msg: #1a3d35;
24
+ --assistant-msg: #252525;
25
+ --error: #ff4757;
26
+ --shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
27
+ }
28
+
29
+ body {
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
31
+ background: var(--bg-primary);
32
+ color: var(--text-primary);
33
+ height: 100vh;
34
+ display: flex;
35
+ flex-direction: column;
36
+ overflow: hidden;
37
+ }
38
+
39
+ /* Header */
40
+ .header {
41
+ background: var(--bg-secondary);
42
+ border-bottom: 1px solid var(--border);
43
+ padding: 1rem 1.5rem;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ box-shadow: var(--shadow);
48
+ z-index: 10;
49
+ }
50
+
51
+ .logo {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 0.75rem;
55
+ }
56
+
57
+ .logo-icon {
58
+ width: 36px;
59
+ height: 36px;
60
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
61
+ border-radius: 10px;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ font-weight: bold;
66
+ font-size: 1.2rem;
67
+ }
68
+
69
+ .logo-text {
70
+ font-size: 1.25rem;
71
+ font-weight: 600;
72
+ letter-spacing: -0.5px;
73
+ }
74
+
75
+ .logo-text span {
76
+ color: var(--accent);
77
+ }
78
+
79
+ .status {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 0.5rem;
83
+ font-size: 0.875rem;
84
+ color: var(--text-secondary);
85
+ }
86
+
87
+ .status-dot {
88
+ width: 8px;
89
+ height: 8px;
90
+ border-radius: 50%;
91
+ background: var(--text-secondary);
92
+ transition: background 0.3s ease;
93
+ }
94
+
95
+ .status-dot.online {
96
+ background: var(--accent);
97
+ box-shadow: 0 0 10px var(--accent);
98
+ }
99
+
100
+ .status-dot.error {
101
+ background: var(--error);
102
+ box-shadow: 0 0 10px var(--error);
103
+ }
104
+
105
+ /* Main Chat Area */
106
+ .chat-container {
107
+ flex: 1;
108
+ overflow-y: auto;
109
+ padding: 2rem;
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 1.5rem;
113
+ scroll-behavior: smooth;
114
+ }
115
+
116
+ .chat-container::-webkit-scrollbar {
117
+ width: 8px;
118
+ }
119
+
120
+ .chat-container::-webkit-scrollbar-track {
121
+ background: transparent;
122
+ }
123
+
124
+ .chat-container::-webkit-scrollbar-thumb {
125
+ background: var(--border);
126
+ border-radius: 4px;
127
+ }
128
+
129
+ .chat-container::-webkit-scrollbar-thumb:hover {
130
+ background: var(--text-secondary);
131
+ }
132
+
133
+ .welcome-message {
134
+ text-align: center;
135
+ padding: 3rem 1rem;
136
+ color: var(--text-secondary);
137
+ }
138
+
139
+ .welcome-message h2 {
140
+ color: var(--text-primary);
141
+ margin-bottom: 0.5rem;
142
+ font-size: 1.5rem;
143
+ }
144
+
145
+ .message {
146
+ display: flex;
147
+ gap: 1rem;
148
+ max-width: 900px;
149
+ margin: 0 auto;
150
+ width: 100%;
151
+ animation: fadeIn 0.3s ease;
152
+ }
153
+
154
+ @keyframes fadeIn {
155
+ from { opacity: 0; transform: translateY(10px); }
156
+ to { opacity: 1; transform: translateY(0); }
157
+ }
158
+
159
+ .message-avatar {
160
+ width: 36px;
161
+ height: 36px;
162
+ border-radius: 50%;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ font-size: 1rem;
167
+ flex-shrink: 0;
168
+ font-weight: 600;
169
+ }
170
+
171
+ .user-message .message-avatar {
172
+ background: var(--accent);
173
+ color: var(--bg-primary);
174
+ }
175
+
176
+ .assistant-message .message-avatar {
177
+ background: var(--bg-tertiary);
178
+ border: 1px solid var(--border);
179
+ }
180
+
181
+ .message-content {
182
+ flex: 1;
183
+ padding: 1rem 1.25rem;
184
+ border-radius: 12px;
185
+ line-height: 1.6;
186
+ font-size: 0.95rem;
187
+ white-space: pre-wrap;
188
+ word-wrap: break-word;
189
+ }
190
+
191
+ .user-message .message-content {
192
+ background: var(--user-msg);
193
+ border: 1px solid rgba(0, 212, 170, 0.2);
194
+ }
195
+
196
+ .assistant-message .message-content {
197
+ background: var(--assistant-msg);
198
+ border: 1px solid var(--border);
199
+ }
200
+
201
+ .message-content.loading {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 0.5rem;
205
+ }
206
+
207
+ .typing-indicator {
208
+ display: flex;
209
+ gap: 4px;
210
+ }
211
+
212
+ .typing-indicator span {
213
+ width: 6px;
214
+ height: 6px;
215
+ background: var(--text-secondary);
216
+ border-radius: 50%;
217
+ animation: bounce 1.4s infinite ease-in-out both;
218
+ }
219
+
220
+ .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
221
+ .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
222
+
223
+ @keyframes bounce {
224
+ 0%, 80%, 100% { transform: scale(0); }
225
+ 40% { transform: scale(1); }
226
+ }
227
+
228
+ /* Input Area */
229
+ .input-container {
230
+ background: var(--bg-secondary);
231
+ border-top: 1px solid var(--border);
232
+ padding: 1.5rem;
233
+ display: flex;
234
+ justify-content: center;
235
+ }
236
+
237
+ .input-wrapper {
238
+ max-width: 900px;
239
+ width: 100%;
240
+ position: relative;
241
+ display: flex;
242
+ gap: 0.75rem;
243
+ align-items: flex-end;
244
+ background: var(--bg-tertiary);
245
+ border: 1px solid var(--border);
246
+ border-radius: 16px;
247
+ padding: 0.75rem;
248
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
249
+ }
250
+
251
+ .input-wrapper:focus-within {
252
+ border-color: var(--accent);
253
+ box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.1);
254
+ }
255
+
256
+ textarea {
257
+ flex: 1;
258
+ background: transparent;
259
+ border: none;
260
+ color: var(--text-primary);
261
+ font-size: 0.95rem;
262
+ resize: none;
263
+ max-height: 200px;
264
+ min-height: 24px;
265
+ font-family: inherit;
266
+ line-height: 1.5;
267
+ padding: 0.25rem 0.5rem;
268
+ }
269
+
270
+ textarea:focus {
271
+ outline: none;
272
+ }
273
+
274
+ textarea::placeholder {
275
+ color: var(--text-secondary);
276
+ }
277
+
278
+ .send-btn {
279
+ background: var(--accent);
280
+ color: var(--bg-primary);
281
+ border: none;
282
+ width: 36px;
283
+ height: 36px;
284
+ border-radius: 10px;
285
+ cursor: pointer;
286
+ display: flex;
287
+ align-items: center;
288
+ justify-content: center;
289
+ transition: all 0.2s ease;
290
+ flex-shrink: 0;
291
+ }
292
+
293
+ .send-btn:hover:not(:disabled) {
294
+ background: var(--accent-hover);
295
+ transform: scale(1.05);
296
+ }
297
+
298
+ .send-btn:disabled {
299
+ opacity: 0.5;
300
+ cursor: not-allowed;
301
+ }
302
+
303
+ .send-btn svg {
304
+ width: 18px;
305
+ height: 18px;
306
+ }
307
+
308
+ /* Settings Panel */
309
+ .settings-btn {
310
+ background: transparent;
311
+ border: 1px solid var(--border);
312
+ color: var(--text-secondary);
313
+ padding: 0.5rem 1rem;
314
+ border-radius: 8px;
315
+ cursor: pointer;
316
+ font-size: 0.875rem;
317
+ transition: all 0.2s ease;
318
+ }
319
+
320
+ .settings-btn:hover {
321
+ border-color: var(--accent);
322
+ color: var(--accent);
323
+ }
324
+
325
+ .settings-panel {
326
+ position: fixed;
327
+ top: 50%;
328
+ left: 50%;
329
+ transform: translate(-50%, -50%) scale(0.95);
330
+ background: var(--bg-secondary);
331
+ border: 1px solid var(--border);
332
+ border-radius: 16px;
333
+ padding: 1.5rem;
334
+ width: 90%;
335
+ max-width: 400px;
336
+ box-shadow: var(--shadow);
337
+ opacity: 0;
338
+ visibility: hidden;
339
+ transition: all 0.2s ease;
340
+ z-index: 100;
341
+ }
342
+
343
+ .settings-panel.active {
344
+ opacity: 1;
345
+ visibility: visible;
346
+ transform: translate(-50%, -50%) scale(1);
347
+ }
348
+
349
+ .settings-overlay {
350
+ position: fixed;
351
+ top: 0;
352
+ left: 0;
353
+ right: 0;
354
+ bottom: 0;
355
+ background: rgba(0, 0, 0, 0.7);
356
+ opacity: 0;
357
+ visibility: hidden;
358
+ transition: all 0.2s ease;
359
+ z-index: 99;
360
+ }
361
+
362
+ .settings-overlay.active {
363
+ opacity: 1;
364
+ visibility: visible;
365
+ }
366
+
367
+ .settings-header {
368
+ display: flex;
369
+ justify-content: space-between;
370
+ align-items: center;
371
+ margin-bottom: 1.5rem;
372
+ }
373
+
374
+ .settings-title {
375
+ font-size: 1.25rem;
376
+ font-weight: 600;
377
+ }
378
+
379
+ .close-settings {
380
+ background: none;
381
+ border: none;
382
+ color: var(--text-secondary);
383
+ cursor: pointer;
384
+ font-size: 1.5rem;
385
+ line-height: 1;
386
+ }
387
+
388
+ .setting-item {
389
+ margin-bottom: 1.25rem;
390
+ }
391
+
392
+ .setting-label {
393
+ display: block;
394
+ font-size: 0.875rem;
395
+ color: var(--text-secondary);
396
+ margin-bottom: 0.5rem;
397
+ }
398
+
399
+ .setting-input {
400
+ width: 100%;
401
+ background: var(--bg-tertiary);
402
+ border: 1px solid var(--border);
403
+ color: var(--text-primary);
404
+ padding: 0.75rem;
405
+ border-radius: 8px;
406
+ font-size: 0.9rem;
407
+ }
408
+
409
+ .setting-input:focus {
410
+ outline: none;
411
+ border-color: var(--accent);
412
+ }
413
+
414
+ /* Code blocks */
415
+ pre {
416
+ background: var(--bg-primary);
417
+ border: 1px solid var(--border);
418
+ border-radius: 8px;
419
+ padding: 1rem;
420
+ overflow-x: auto;
421
+ margin: 0.75rem 0;
422
+ }
423
+
424
+ code {
425
+ font-family: 'Courier New', monospace;
426
+ font-size: 0.9em;
427
+ }
428
+
429
+ pre code {
430
+ color: #e0e0e0;
431
+ }
432
+
433
+ /* Responsive */
434
+ @media (max-width: 768px) {
435
+ .chat-container {
436
+ padding: 1rem;
437
+ }
438
+
439
+ .header {
440
+ padding: 1rem;
441
+ }
442
+
443
+ .logo-text {
444
+ font-size: 1rem;
445
+ }
446
+ }
447
+
448
+ /* Empty state suggestions */
449
+ .suggestions {
450
+ display: grid;
451
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
452
+ gap: 1rem;
453
+ max-width: 800px;
454
+ margin: 2rem auto;
455
+ padding: 0 1rem;
456
+ }
457
+
458
+ .suggestion-card {
459
+ background: var(--bg-secondary);
460
+ border: 1px solid var(--border);
461
+ border-radius: 12px;
462
+ padding: 1rem;
463
+ cursor: pointer;
464
+ transition: all 0.2s ease;
465
+ text-align: left;
466
+ }
467
+
468
+ .suggestion-card:hover {
469
+ border-color: var(--accent);
470
+ transform: translateY(-2px);
471
+ box-shadow: 0 4px 12px rgba(0, 212, 170, 0.1);
472
+ }
473
+
474
+ .suggestion-card h4 {
475
+ color: var(--accent);
476
+ margin-bottom: 0.5rem;
477
+ font-size: 0.9rem;
478
+ }
479
+
480
+ .suggestion-card p {
481
+ color: var(--text-secondary);
482
+ font-size: 0.85rem;
483
+ line-height: 1.4;
484
+ }
485
+ </style>
486
+ </head>
487
+ <body>
488
+ <header class="header">
489
+ <div class="logo">
490
+ <div class="logo-icon">N</div>
491
+ <div class="logo-text">Nanbeige<span>4.1-3B</span></div>
492
+ </div>
493
+ <div style="display: flex; gap: 1rem; align-items: center;">
494
+ <div class="status">
495
+ <div class="status-dot" id="statusDot"></div>
496
+ <span id="statusText">Checking...</span>
497
+ </div>
498
+ <button class="settings-btn" onclick="toggleSettings()">⚙️ Settings</button>
499
+ <button class="settings-btn" onclick="clearChat()">🗑️ Clear</button>
500
+ </div>
501
+ </header>
502
+
503
+ <div class="settings-overlay" id="settingsOverlay" onclick="toggleSettings()"></div>
504
+ <div class="settings-panel" id="settingsPanel">
505
+ <div class="settings-header">
506
+ <h3 class="settings-title">Settings</h3>
507
+ <button class="close-settings" onclick="toggleSettings()">×</button>
508
+ </div>
509
+ <div class="setting-item">
510
+ <label class="setting-label">Temperature (0.0 - 2.0)</label>
511
+ <input type="number" class="setting-input" id="temperature" min="0" max="2" step="0.1" value="0.6">
512
+ </div>
513
+ <div class="setting-item">
514
+ <label class="setting-label">Max Tokens (1 - 4096)</label>
515
+ <input type="number" class="setting-input" id="maxTokens" min="1" max="4096" step="1" value="2048">
516
+ </div>
517
+ <div class="setting-item">
518
+ <label class="setting-label">System Prompt</label>
519
+ <textarea class="setting-input" id="systemPrompt" rows="3" placeholder="Optional system instructions..."></textarea>
520
+ </div>
521
+ </div>
522
+
523
+ <main class="chat-container" id="chatContainer">
524
+ <div class="welcome-message" id="welcomeMessage">
525
+ <h2>Welcome to Nanbeige4.1-3B</h2>
526
+ <p>A powerful language model for conversation and assistance</p>
527
+
528
+ <div class="suggestions">
529
+ <div class="suggestion-card" onclick="sendSuggestion('Explain quantum computing in simple terms')">
530
+ <h4>💡 Explain a concept</h4>
531
+ <p>"Explain quantum computing in simple terms"</p>
532
+ </div>
533
+ <div class="suggestion-card" onclick="sendSuggestion('Write a Python function to calculate fibonacci numbers')">
534
+ <h4>💻 Code assistance</h4>
535
+ <p>"Write a Python function to calculate fibonacci numbers"</p>
536
+ </div>
537
+ <div class="suggestion-card" onclick="sendSuggestion('Help me brainstorm ideas for a sci-fi short story')">
538
+ <h4>✨ Creative writing</h4>
539
+ <p>"Help me brainstorm ideas for a sci-fi short story"</p>
540
+ </div>
541
+ <div class="suggestion-card" onclick="sendSuggestion('What are the latest developments in AI?')">
542
+ <h4>🤖 General knowledge</h4>
543
+ <p>"What are the latest developments in AI?"</p>
544
+ </div>
545
+ </div>
546
+ </div>
547
+ </main>
548
+
549
+ <div class="input-container">
550
+ <div class="input-wrapper">
551
+ <textarea
552
+ id="messageInput"
553
+ placeholder="Type your message..."
554
+ rows="1"
555
+ onkeydown="handleKeyDown(event)"
556
+ oninput="autoResize(this)"
557
+ ></textarea>
558
+ <button class="send-btn" id="sendBtn" onclick="sendMessage()">
559
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
560
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
561
+ </svg>
562
+ </button>
563
+ </div>
564
+ </div>
565
+
566
+ <script>
567
+ let messages = [];
568
+ let isStreaming = false;
569
+ const API_BASE = window.location.origin;
570
+
571
+ // Check health on load
572
+ checkHealth();
573
+ setInterval(checkHealth, 30000);
574
+
575
+ async function checkHealth() {
576
+ try {
577
+ const response = await fetch(`${API_BASE}/health`);
578
+ const data = await response.json();
579
+ const dot = document.getElementById('statusDot');
580
+ const text = document.getElementById('statusText');
581
+
582
+ if (data.model_loaded) {
583
+ dot.className = 'status-dot online';
584
+ text.textContent = 'Online';
585
+ } else {
586
+ dot.className = 'status-dot';
587
+ text.textContent = 'Loading...';
588
+ }
589
+ } catch (error) {
590
+ document.getElementById('statusDot').className = 'status-dot error';
591
+ document.getElementById('statusText').textContent = 'Offline';
592
+ }
593
+ }
594
+
595
+ function toggleSettings() {
596
+ document.getElementById('settingsPanel').classList.toggle('active');
597
+ document.getElementById('settingsOverlay').classList.toggle('active');
598
+ }
599
+
600
+ function clearChat() {
601
+ messages = [];
602
+ document.getElementById('chatContainer').innerHTML = `
603
+ <div class="welcome-message" id="welcomeMessage">
604
+ <h2>Welcome to Nanbeige4.1-3B</h2>
605
+ <p>Start a new conversation</p>
606
+ </div>
607
+ `;
608
+ }
609
+
610
+ function autoResize(textarea) {
611
+ textarea.style.height = 'auto';
612
+ textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
613
+ }
614
+
615
+ function handleKeyDown(e) {
616
+ if (e.key === 'Enter' && !e.shiftKey) {
617
+ e.preventDefault();
618
+ sendMessage();
619
+ }
620
+ }
621
+
622
+ function sendSuggestion(text) {
623
+ document.getElementById('messageInput').value = text;
624
+ sendMessage();
625
+ }
626
+
627
+ async function sendMessage() {
628
+ const input = document.getElementById('messageInput');
629
+ const text = input.value.trim();
630
+
631
+ if (!text || isStreaming) return;
632
+
633
+ // Hide welcome message
634
+ const welcome = document.getElementById('welcomeMessage');
635
+ if (welcome) welcome.style.display = 'none';
636
+
637
+ // Add user message
638
+ addMessage('user', text);
639
+ messages.push({ role: 'user', content: text });
640
+
641
+ // Clear input
642
+ input.value = '';
643
+ input.style.height = 'auto';
644
+
645
+ // Show loading
646
+ isStreaming = true;
647
+ document.getElementById('sendBtn').disabled = true;
648
+ const loadingId = addLoadingMessage();
649
+
650
+ try {
651
+ const temperature = parseFloat(document.getElementById('temperature').value) || 0.6;
652
+ const maxTokens = parseInt(document.getElementById('maxTokens').value) || 2048;
653
+ const systemPrompt = document.getElementById('systemPrompt').value.trim();
654
+
655
+ let chatMessages = [...messages];
656
+ if (systemPrompt && messages.length === 1) {
657
+ chatMessages.unshift({ role: 'system', content: systemPrompt });
658
+ }
659
+
660
+ const response = await fetch(`${API_BASE}/chat`, {
661
+ method: 'POST',
662
+ headers: { 'Content-Type': 'application/json' },
663
+ body: JSON.stringify({
664
+ messages: chatMessages,
665
+ stream: true,
666
+ temperature: temperature,
667
+ max_tokens: maxTokens
668
+ })
669
+ });
670
+
671
+ if (!response.ok) throw new Error('Failed to get response');
672
+
673
+ // Remove loading, prepare for streaming
674
+ removeLoadingMessage(loadingId);
675
+ const assistantId = addMessage('assistant', '');
676
+
677
+ const reader = response.body.getReader();
678
+ const decoder = new TextDecoder();
679
+ let buffer = '';
680
+ let fullContent = '';
681
+
682
+ while (true) {
683
+ const { done, value } = await reader.read();
684
+ if (done) break;
685
+
686
+ buffer += decoder.decode(value, { stream: true });
687
+ const lines = buffer.split('\n');
688
+ buffer = lines.pop();
689
+
690
+ for (const line of lines) {
691
+ if (line.startsWith('data: ')) {
692
+ try {
693
+ const data = JSON.parse(line.slice(6));
694
+ if (data.type === 'token') {
695
+ fullContent += data.content;
696
+ updateMessage(assistantId, fullContent);
697
+ } else if (data.type === 'done') {
698
+ messages.push({ role: 'assistant', content: fullContent });
699
+ }
700
+ } catch (e) {
701
+ console.error('Parse error:', e);
702
+ }
703
+ }
704
+ }
705
+ }
706
+
707
+ } catch (error) {
708
+ removeLoadingMessage(loadingId);
709
+ addMessage('assistant', `Error: ${error.message}. Please check if the API is running.`);
710
+ } finally {
711
+ isStreaming = false;
712
+ document.getElementById('sendBtn').disabled = false;
713
+ }
714
+ }
715
+
716
+ function addMessage(role, content) {
717
+ const container = document.getElementById('chatContainer');
718
+ const id = 'msg-' + Date.now();
719
+
720
+ const div = document.createElement('div');
721
+ div.className = `message ${role}-message`;
722
+ div.id = id;
723
+ div.innerHTML = `
724
+ <div class="message-avatar">${role === 'user' ? 'U' : '🤖'}</div>
725
+ <div class="message-content">${formatContent(content)}</div>
726
+ `;
727
+
728
+ container.appendChild(div);
729
+ scrollToBottom();
730
+ return id;
731
+ }
732
+
733
+ function updateMessage(id, content) {
734
+ const msg = document.getElementById(id);
735
+ if (msg) {
736
+ msg.querySelector('.message-content').innerHTML = formatContent(content);
737
+ scrollToBottom();
738
+ }
739
+ }
740
+
741
+ function addLoadingMessage() {
742
+ const id = 'loading-' + Date.now();
743
+ const container = document.getElementById('chatContainer');
744
+
745
+ const div = document.createElement('div');
746
+ div.className = 'message assistant-message';
747
+ div.id = id;
748
+ div.innerHTML = `
749
+ <div class="message-avatar">🤖</div>
750
+ <div class="message-content loading">
751
+ <div class="typing-indicator">
752
+ <span></span>
753
+ <span></span>
754
+ <span></span>
755
+ </div>
756
+ </div>
757
+ `;
758
+
759
+ container.appendChild(div);
760
+ scrollToBottom();
761
+ return id;
762
+ }
763
+
764
+ function removeLoadingMessage(id) {
765
+ const el = document.getElementById(id);
766
+ if (el) el.remove();
767
+ }
768
+
769
+ function formatContent(text) {
770
+ // Escape HTML
771
+ text = text.replace(/&/g, '&amp;')
772
+ .replace(/</g, '&lt;')
773
+ .replace(/>/g, '&gt;');
774
+
775
+ // Format code blocks
776
+ text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
777
+ text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
778
+
779
+ // Format bold and italic
780
+ text = text.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
781
+ text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
782
+ text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
783
+
784
+ // Convert newlines to <br> outside of pre blocks
785
+ text = text.replace(/\n/g, '<br>');
786
+
787
+ return text;
788
+ }
789
+
790
+ function scrollToBottom() {
791
+ const container = document.getElementById('chatContainer');
792
+ container.scrollTop = container.scrollHeight;
793
+ }
794
+ </script>
795
+ </body>
796
+ </html>