Harmony18090 commited on
Commit
68993d0
·
verified ·
1 Parent(s): 6d6367a

Upload dashboard/index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. dashboard/index.html +569 -0
dashboard/index.html ADDED
@@ -0,0 +1,569 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>MedFound - Medical AI Assistant</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #0f1117;
12
+ --surface: #1a1d27;
13
+ --surface2: #242736;
14
+ --border: #2e3144;
15
+ --text: #e4e4e7;
16
+ --text-dim: #9ca3af;
17
+ --accent: #3b82f6;
18
+ --accent-hover: #2563eb;
19
+ --user-bg: #1e3a5f;
20
+ --bot-bg: #1f2937;
21
+ --danger: #ef4444;
22
+ --success: #22c55e;
23
+ --radius: 12px;
24
+ }
25
+
26
+ html, body { height: 100%; }
27
+
28
+ body {
29
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
30
+ background: var(--bg);
31
+ color: var(--text);
32
+ display: flex;
33
+ flex-direction: column;
34
+ }
35
+
36
+ header {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ padding: 16px 24px;
41
+ background: var(--surface);
42
+ border-bottom: 1px solid var(--border);
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .logo {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 12px;
50
+ }
51
+
52
+ .logo-icon {
53
+ width: 40px;
54
+ height: 40px;
55
+ background: linear-gradient(135deg, var(--accent), #8b5cf6);
56
+ border-radius: 10px;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ font-size: 20px;
61
+ }
62
+
63
+ .logo h1 {
64
+ font-size: 20px;
65
+ font-weight: 700;
66
+ background: linear-gradient(135deg, #60a5fa, #a78bfa);
67
+ -webkit-background-clip: text;
68
+ -webkit-text-fill-color: transparent;
69
+ }
70
+
71
+ .logo span {
72
+ font-size: 12px;
73
+ color: var(--text-dim);
74
+ }
75
+
76
+ .status-badge {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 6px;
80
+ font-size: 13px;
81
+ color: var(--text-dim);
82
+ padding: 6px 14px;
83
+ background: var(--surface2);
84
+ border-radius: 20px;
85
+ }
86
+
87
+ .status-dot {
88
+ width: 8px;
89
+ height: 8px;
90
+ border-radius: 50%;
91
+ background: var(--danger);
92
+ }
93
+
94
+ .status-dot.online { background: var(--success); }
95
+
96
+ .chat-area {
97
+ flex: 1;
98
+ overflow-y: auto;
99
+ padding: 24px;
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 16px;
103
+ }
104
+
105
+ .welcome {
106
+ text-align: center;
107
+ padding: 60px 20px;
108
+ max-width: 600px;
109
+ margin: auto;
110
+ }
111
+
112
+ .welcome-icon {
113
+ font-size: 64px;
114
+ margin-bottom: 16px;
115
+ }
116
+
117
+ .welcome h2 {
118
+ font-size: 24px;
119
+ margin-bottom: 8px;
120
+ color: var(--text);
121
+ }
122
+
123
+ .welcome p {
124
+ color: var(--text-dim);
125
+ line-height: 1.6;
126
+ margin-bottom: 24px;
127
+ }
128
+
129
+ .suggestions {
130
+ display: grid;
131
+ grid-template-columns: 1fr 1fr;
132
+ gap: 10px;
133
+ }
134
+
135
+ .suggestion {
136
+ padding: 14px 16px;
137
+ background: var(--surface2);
138
+ border: 1px solid var(--border);
139
+ border-radius: var(--radius);
140
+ cursor: pointer;
141
+ text-align: left;
142
+ color: var(--text-dim);
143
+ font-size: 13px;
144
+ transition: all 0.15s;
145
+ }
146
+
147
+ .suggestion:hover {
148
+ background: var(--surface);
149
+ border-color: var(--accent);
150
+ color: var(--text);
151
+ }
152
+
153
+ .message {
154
+ display: flex;
155
+ gap: 12px;
156
+ max-width: 800px;
157
+ width: 100%;
158
+ margin: 0 auto;
159
+ animation: fadeIn 0.3s ease;
160
+ }
161
+
162
+ @keyframes fadeIn {
163
+ from { opacity: 0; transform: translateY(8px); }
164
+ to { opacity: 1; transform: translateY(0); }
165
+ }
166
+
167
+ .message.user { flex-direction: row-reverse; }
168
+
169
+ .avatar {
170
+ width: 36px;
171
+ height: 36px;
172
+ border-radius: 10px;
173
+ display: flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ font-size: 16px;
177
+ flex-shrink: 0;
178
+ }
179
+
180
+ .message.bot .avatar { background: linear-gradient(135deg, var(--accent), #8b5cf6); }
181
+ .message.user .avatar { background: var(--user-bg); }
182
+
183
+ .bubble {
184
+ padding: 14px 18px;
185
+ border-radius: var(--radius);
186
+ line-height: 1.7;
187
+ font-size: 14px;
188
+ max-width: 70%;
189
+ word-break: break-word;
190
+ }
191
+
192
+ .bubble ol, .bubble ul {
193
+ margin: 8px 0;
194
+ padding-left: 24px;
195
+ }
196
+
197
+ .bubble ol li, .bubble ul li {
198
+ margin-bottom: 6px;
199
+ }
200
+
201
+ .bubble p {
202
+ margin: 6px 0;
203
+ }
204
+
205
+ .bubble p:first-child { margin-top: 0; }
206
+ .bubble p:last-child { margin-bottom: 0; }
207
+
208
+ .bubble strong { color: #93c5fd; font-weight: 600; }
209
+
210
+ .bubble code {
211
+ background: rgba(255,255,255,0.08);
212
+ padding: 2px 6px;
213
+ border-radius: 4px;
214
+ font-size: 13px;
215
+ }
216
+
217
+ .bubble pre {
218
+ background: rgba(0,0,0,0.3);
219
+ padding: 12px;
220
+ border-radius: 8px;
221
+ overflow-x: auto;
222
+ margin: 8px 0;
223
+ }
224
+
225
+ .bubble pre code {
226
+ background: none;
227
+ padding: 0;
228
+ }
229
+
230
+ .bubble h3, .bubble h4 {
231
+ margin: 12px 0 6px;
232
+ color: #93c5fd;
233
+ }
234
+
235
+ .message.user .bubble { white-space: pre-wrap; }
236
+
237
+ .message.bot .bubble { background: var(--bot-bg); border: 1px solid var(--border); }
238
+ .message.user .bubble { background: var(--user-bg); }
239
+
240
+ .bubble .typing-dots span {
241
+ display: inline-block;
242
+ width: 7px;
243
+ height: 7px;
244
+ margin: 0 2px;
245
+ border-radius: 50%;
246
+ background: var(--text-dim);
247
+ animation: bounce 1.4s infinite ease-in-out both;
248
+ }
249
+
250
+ .bubble .typing-dots span:nth-child(1) { animation-delay: -0.32s; }
251
+ .bubble .typing-dots span:nth-child(2) { animation-delay: -0.16s; }
252
+
253
+ @keyframes bounce {
254
+ 0%, 80%, 100% { transform: scale(0); }
255
+ 40% { transform: scale(1); }
256
+ }
257
+
258
+ .input-area {
259
+ padding: 16px 24px 24px;
260
+ background: var(--surface);
261
+ border-top: 1px solid var(--border);
262
+ flex-shrink: 0;
263
+ }
264
+
265
+ .input-wrap {
266
+ display: flex;
267
+ gap: 10px;
268
+ max-width: 800px;
269
+ margin: 0 auto;
270
+ }
271
+
272
+ .input-wrap textarea {
273
+ flex: 1;
274
+ resize: none;
275
+ border: 1px solid var(--border);
276
+ background: var(--surface2);
277
+ color: var(--text);
278
+ border-radius: var(--radius);
279
+ padding: 14px 18px;
280
+ font-size: 14px;
281
+ font-family: inherit;
282
+ line-height: 1.5;
283
+ outline: none;
284
+ transition: border-color 0.15s;
285
+ min-height: 52px;
286
+ max-height: 160px;
287
+ }
288
+
289
+ .input-wrap textarea:focus { border-color: var(--accent); }
290
+ .input-wrap textarea::placeholder { color: var(--text-dim); }
291
+
292
+ .input-wrap button {
293
+ padding: 14px 20px;
294
+ background: var(--accent);
295
+ color: #fff;
296
+ border: none;
297
+ border-radius: var(--radius);
298
+ font-size: 14px;
299
+ font-weight: 600;
300
+ cursor: pointer;
301
+ transition: background 0.15s;
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 6px;
305
+ white-space: nowrap;
306
+ }
307
+
308
+ .input-wrap button:hover { background: var(--accent-hover); }
309
+ .input-wrap button:disabled { opacity: 0.5; cursor: not-allowed; }
310
+
311
+ .controls {
312
+ display: flex;
313
+ justify-content: space-between;
314
+ align-items: center;
315
+ max-width: 800px;
316
+ margin: 8px auto 0;
317
+ }
318
+
319
+ .disclaimer {
320
+ font-size: 11px;
321
+ color: var(--text-dim);
322
+ }
323
+
324
+ .clear-btn {
325
+ background: none;
326
+ border: 1px solid var(--border);
327
+ color: var(--text-dim);
328
+ padding: 5px 12px;
329
+ border-radius: 8px;
330
+ font-size: 12px;
331
+ cursor: pointer;
332
+ transition: all 0.15s;
333
+ }
334
+
335
+ .clear-btn:hover { border-color: var(--danger); color: var(--danger); }
336
+
337
+ @media (max-width: 640px) {
338
+ .suggestions { grid-template-columns: 1fr; }
339
+ .bubble { max-width: 85%; }
340
+ header { padding: 12px 16px; }
341
+ .chat-area { padding: 16px; }
342
+ .input-area { padding: 12px 16px 16px; }
343
+ }
344
+ </style>
345
+ </head>
346
+ <body>
347
+ <header>
348
+ <div class="logo">
349
+ <div class="logo-icon">&#x2695;</div>
350
+ <div>
351
+ <h1>MedFound AI</h1>
352
+ <span>Medical Assistant &middot; Llama3-8B</span>
353
+ </div>
354
+ </div>
355
+ <div class="status-badge">
356
+ <div class="status-dot" id="statusDot"></div>
357
+ <span id="statusText">Connecting...</span>
358
+ </div>
359
+ </header>
360
+
361
+ <div class="chat-area" id="chatArea">
362
+ <div class="welcome" id="welcome">
363
+ <div class="welcome-icon">&#x1F3E5;</div>
364
+ <h2>Medical AI Assistant</h2>
365
+ <p>Ask me about symptoms, conditions, medications, or general health information.
366
+ Responses are for informational purposes only&mdash;always consult a healthcare professional.</p>
367
+ <div class="suggestions">
368
+ <div class="suggestion" onclick="useSuggestion(this)">What are common symptoms of type 2 diabetes?</div>
369
+ <div class="suggestion" onclick="useSuggestion(this)">Explain the difference between viral and bacterial infections</div>
370
+ <div class="suggestion" onclick="useSuggestion(this)">What are the risk factors for cardiovascular disease?</div>
371
+ <div class="suggestion" onclick="useSuggestion(this)">How does hypertension affect the body over time?</div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+
376
+ <div class="input-area">
377
+ <div class="input-wrap">
378
+ <textarea id="msgInput" rows="1" placeholder="Describe your symptoms or ask a medical question..."
379
+ onkeydown="handleKey(event)" oninput="autoGrow(this)"></textarea>
380
+ <button id="sendBtn" onclick="sendMessage()">Send &#x27A4;</button>
381
+ </div>
382
+ <div class="controls">
383
+ <span class="disclaimer">&#x26A0; Not a substitute for professional medical advice.</span>
384
+ <button class="clear-btn" onclick="clearChat()">Clear chat</button>
385
+ </div>
386
+ </div>
387
+
388
+ <script>
389
+ const chatArea = document.getElementById('chatArea');
390
+ const msgInput = document.getElementById('msgInput');
391
+ const sendBtn = document.getElementById('sendBtn');
392
+ const statusDot = document.getElementById('statusDot');
393
+ const statusText= document.getElementById('statusText');
394
+ const welcome = document.getElementById('welcome');
395
+
396
+ let history = [];
397
+ let busy = false;
398
+
399
+ async function checkHealth() {
400
+ try {
401
+ const r = await fetch('/health');
402
+ const d = await r.json();
403
+ statusDot.classList.toggle('online', d.status === 'ok');
404
+ statusText.textContent = d.status === 'ok'
405
+ ? `Online \u2022 GPU ${d.gpu_memory_used_mb} MB`
406
+ : 'Error';
407
+ } catch {
408
+ statusDot.classList.remove('online');
409
+ statusText.textContent = 'Offline';
410
+ }
411
+ }
412
+ checkHealth();
413
+ setInterval(checkHealth, 15000);
414
+
415
+ function useSuggestion(el) {
416
+ msgInput.value = el.textContent;
417
+ msgInput.focus();
418
+ autoGrow(msgInput);
419
+ }
420
+
421
+ function handleKey(e) {
422
+ if (e.key === 'Enter' && !e.shiftKey) {
423
+ e.preventDefault();
424
+ sendMessage();
425
+ }
426
+ }
427
+
428
+ function autoGrow(el) {
429
+ el.style.height = 'auto';
430
+ el.style.height = Math.min(el.scrollHeight, 160) + 'px';
431
+ }
432
+
433
+ function appendMessage(role, content) {
434
+ if (welcome) welcome.style.display = 'none';
435
+ const div = document.createElement('div');
436
+ div.className = `message ${role}`;
437
+ const avatarChar = role === 'user' ? '\u{1F464}' : '\u2695';
438
+ const rendered = role === 'bot' ? renderMarkdown(content) : escapeHtml(content);
439
+ div.innerHTML = `
440
+ <div class="avatar">${avatarChar}</div>
441
+ <div class="bubble">${rendered}</div>`;
442
+ chatArea.appendChild(div);
443
+ chatArea.scrollTop = chatArea.scrollHeight;
444
+ return div;
445
+ }
446
+
447
+ function showTyping() {
448
+ if (welcome) welcome.style.display = 'none';
449
+ const div = document.createElement('div');
450
+ div.className = 'message bot';
451
+ div.id = 'typing';
452
+ div.innerHTML = `
453
+ <div class="avatar">\u2695</div>
454
+ <div class="bubble"><div class="typing-dots"><span></span><span></span><span></span></div></div>`;
455
+ chatArea.appendChild(div);
456
+ chatArea.scrollTop = chatArea.scrollHeight;
457
+ }
458
+
459
+ function removeTyping() {
460
+ const t = document.getElementById('typing');
461
+ if (t) t.remove();
462
+ }
463
+
464
+ function escapeHtml(s) {
465
+ const d = document.createElement('div');
466
+ d.textContent = s;
467
+ return d.innerHTML;
468
+ }
469
+
470
+ function renderMarkdown(text) {
471
+ let html = escapeHtml(text);
472
+
473
+ // Code blocks: ```...```
474
+ html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
475
+ // Inline code
476
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
477
+ // Bold
478
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
479
+ // Italic
480
+ html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
481
+ // Headings (### and ####)
482
+ html = html.replace(/^####\s+(.+)$/gm, '<h4>$1</h4>');
483
+ html = html.replace(/^###\s+(.+)$/gm, '<h3>$1</h3>');
484
+
485
+ // Numbered lists: lines starting with 1. 2. etc.
486
+ html = html.replace(/((?:^\d+[\.\)]\s+.+$\n?)+)/gm, function(block) {
487
+ const items = block.trim().split('\n').map(line =>
488
+ '<li>' + line.replace(/^\d+[\.\)]\s+/, '') + '</li>'
489
+ ).join('');
490
+ return '<ol>' + items + '</ol>';
491
+ });
492
+
493
+ // Bullet lists: lines starting with - or *
494
+ html = html.replace(/((?:^[\-\*]\s+.+$\n?)+)/gm, function(block) {
495
+ const items = block.trim().split('\n').map(line =>
496
+ '<li>' + line.replace(/^[\-\*]\s+/, '') + '</li>'
497
+ ).join('');
498
+ return '<ul>' + items + '</ul>';
499
+ });
500
+
501
+ // Paragraphs: split on double newlines
502
+ html = html.replace(/\n{2,}/g, '</p><p>');
503
+ // Single newlines (not inside lists/pre) become <br>
504
+ html = html.replace(/\n/g, '<br>');
505
+ // Clean up: remove <br> right after block elements
506
+ html = html.replace(/(<\/?(ol|ul|li|h[34]|pre|p)>)\s*<br>/g, '$1');
507
+ html = html.replace(/<br>\s*(<(ol|ul|li|h[34]|pre|p)[ >])/g, '$1');
508
+
509
+ // Wrap in paragraph if not starting with a block element
510
+ if (!/^\s*<(ol|ul|h[34]|pre|p)/.test(html)) {
511
+ html = '<p>' + html + '</p>';
512
+ }
513
+
514
+ return html;
515
+ }
516
+
517
+ async function sendMessage() {
518
+ const text = msgInput.value.trim();
519
+ if (!text || busy) return;
520
+
521
+ busy = true;
522
+ sendBtn.disabled = true;
523
+ msgInput.value = '';
524
+ msgInput.style.height = 'auto';
525
+
526
+ appendMessage('user', text);
527
+ history.push({ role: 'user', content: text });
528
+
529
+ showTyping();
530
+
531
+ try {
532
+ const resp = await fetch('/v1/chat', {
533
+ method: 'POST',
534
+ headers: { 'Content-Type': 'application/json' },
535
+ body: JSON.stringify({
536
+ messages: history,
537
+ max_new_tokens: 512,
538
+ temperature: 0.7,
539
+ stream: false
540
+ })
541
+ });
542
+
543
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
544
+ const data = await resp.json();
545
+
546
+ removeTyping();
547
+ appendMessage('bot', data.content);
548
+ history.push({ role: 'assistant', content: data.content });
549
+ } catch (err) {
550
+ removeTyping();
551
+ appendMessage('bot', `Error: ${err.message}. Please try again.`);
552
+ }
553
+
554
+ busy = false;
555
+ sendBtn.disabled = false;
556
+ msgInput.focus();
557
+ }
558
+
559
+ function clearChat() {
560
+ history = [];
561
+ chatArea.innerHTML = '';
562
+ if (welcome) {
563
+ welcome.style.display = '';
564
+ chatArea.appendChild(welcome);
565
+ }
566
+ }
567
+ </script>
568
+ </body>
569
+ </html>