Srinu007 commited on
Commit
b2c4c5d
·
verified ·
1 Parent(s): 0fe4f5f

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +747 -0
index.html ADDED
@@ -0,0 +1,747 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>GroqChat</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
9
+ <style>
10
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11
+
12
+ :root {
13
+ --bg: #0d0d0f;
14
+ --surface: #18181b;
15
+ --surface-2: #1f1f23;
16
+ --border: #2a2a30;
17
+ --accent: #7c6af7;
18
+ --accent-glow: rgba(124,106,247,0.18);
19
+ --accent-2: #a78bfa;
20
+ --text: #e8e8ed;
21
+ --text-muted: #8b8b99;
22
+ --text-dim: #55555f;
23
+ --user-bubble: #1e1b38;
24
+ --user-border: #3d3580;
25
+ --success: #4ade80;
26
+ --radius: 14px;
27
+ --sidebar: 260px;
28
+ }
29
+
30
+ html, body { height: 100%; overflow: hidden; }
31
+
32
+ body {
33
+ font-family: 'Inter', sans-serif;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ display: flex;
37
+ font-size: 14px;
38
+ line-height: 1.6;
39
+ }
40
+
41
+ /* ── Sidebar ── */
42
+ .sidebar {
43
+ width: var(--sidebar);
44
+ background: var(--surface);
45
+ border-right: 1px solid var(--border);
46
+ display: flex;
47
+ flex-direction: column;
48
+ flex-shrink: 0;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .sidebar-header {
53
+ padding: 20px 16px 12px;
54
+ border-bottom: 1px solid var(--border);
55
+ }
56
+
57
+ .logo {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 10px;
61
+ margin-bottom: 16px;
62
+ }
63
+
64
+ .logo-icon {
65
+ width: 32px; height: 32px;
66
+ background: linear-gradient(135deg, var(--accent), var(--accent-2));
67
+ border-radius: 8px;
68
+ display: grid;
69
+ place-items: center;
70
+ font-size: 16px;
71
+ flex-shrink: 0;
72
+ }
73
+
74
+ .logo-text {
75
+ font-size: 16px;
76
+ font-weight: 600;
77
+ letter-spacing: -0.3px;
78
+ color: var(--text);
79
+ }
80
+ .logo-text span { color: var(--accent-2); }
81
+
82
+ .new-chat-btn {
83
+ width: 100%;
84
+ padding: 9px 12px;
85
+ background: var(--accent-glow);
86
+ border: 1px solid var(--accent);
87
+ border-radius: 8px;
88
+ color: var(--accent-2);
89
+ font-size: 13px;
90
+ font-weight: 500;
91
+ cursor: pointer;
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 8px;
95
+ transition: background 0.15s, opacity 0.15s;
96
+ font-family: inherit;
97
+ }
98
+ .new-chat-btn:hover { background: rgba(124,106,247,0.28); }
99
+
100
+ /* Model selector */
101
+ .model-section { padding: 12px 16px; border-bottom: 1px solid var(--border); }
102
+ .model-label { font-size: 11px; font-weight: 500; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 8px; }
103
+
104
+ .model-option {
105
+ padding: 9px 11px;
106
+ border-radius: 8px;
107
+ cursor: pointer;
108
+ border: 1px solid transparent;
109
+ margin-bottom: 4px;
110
+ transition: all 0.15s;
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 10px;
114
+ }
115
+ .model-option:hover { background: var(--surface-2); }
116
+ .model-option.active {
117
+ background: var(--accent-glow);
118
+ border-color: var(--accent);
119
+ }
120
+ .model-icon { font-size: 16px; flex-shrink: 0; }
121
+ .model-info { min-width: 0; }
122
+ .model-name { font-size: 12px; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
123
+ .model-desc { font-size: 11px; color: var(--text-muted); }
124
+
125
+ /* Conversation history */
126
+ .history-section { flex: 1; overflow-y: auto; padding: 12px 8px; }
127
+ .history-label { font-size: 11px; font-weight: 500; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.8px; padding: 0 8px; margin-bottom: 6px; }
128
+
129
+ .history-item {
130
+ padding: 8px 10px;
131
+ border-radius: 8px;
132
+ cursor: pointer;
133
+ color: var(--text-muted);
134
+ font-size: 12px;
135
+ white-space: nowrap;
136
+ overflow: hidden;
137
+ text-overflow: ellipsis;
138
+ transition: all 0.15s;
139
+ border: 1px solid transparent;
140
+ }
141
+ .history-item:hover { background: var(--surface-2); color: var(--text); }
142
+ .history-item.active { background: var(--surface-2); border-color: var(--border); color: var(--text); }
143
+
144
+ .history-empty { padding: 0 10px; color: var(--text-dim); font-size: 12px; font-style: italic; }
145
+
146
+ /* Sidebar footer */
147
+ .sidebar-footer {
148
+ padding: 12px 16px;
149
+ border-top: 1px solid var(--border);
150
+ font-size: 11px;
151
+ color: var(--text-dim);
152
+ text-align: center;
153
+ }
154
+ .sidebar-footer a { color: var(--accent); text-decoration: none; }
155
+
156
+ /* ── Main ── */
157
+ .main {
158
+ flex: 1;
159
+ display: flex;
160
+ flex-direction: column;
161
+ overflow: hidden;
162
+ background: var(--bg);
163
+ }
164
+
165
+ /* Top bar */
166
+ .topbar {
167
+ padding: 14px 24px;
168
+ border-bottom: 1px solid var(--border);
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ background: var(--surface);
173
+ flex-shrink: 0;
174
+ }
175
+ .topbar-model {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: 8px;
179
+ font-size: 13px;
180
+ color: var(--text-muted);
181
+ }
182
+ .topbar-model .dot {
183
+ width: 7px; height: 7px;
184
+ border-radius: 50%;
185
+ background: var(--success);
186
+ animation: pulse 2s infinite;
187
+ }
188
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
189
+ .topbar-actions { display: flex; gap: 8px; }
190
+ .icon-btn {
191
+ background: none;
192
+ border: 1px solid var(--border);
193
+ color: var(--text-muted);
194
+ border-radius: 7px;
195
+ padding: 6px 10px;
196
+ cursor: pointer;
197
+ font-size: 12px;
198
+ font-family: inherit;
199
+ transition: all 0.15s;
200
+ }
201
+ .icon-btn:hover { background: var(--surface-2); color: var(--text); }
202
+
203
+ /* Chat window */
204
+ .chat-window {
205
+ flex: 1;
206
+ overflow-y: auto;
207
+ padding: 32px 0;
208
+ scroll-behavior: smooth;
209
+ }
210
+
211
+ .chat-inner { max-width: 740px; margin: 0 auto; padding: 0 24px; }
212
+
213
+ /* Welcome */
214
+ .welcome {
215
+ text-align: center;
216
+ padding: 60px 20px;
217
+ display: flex;
218
+ flex-direction: column;
219
+ align-items: center;
220
+ gap: 16px;
221
+ }
222
+ .welcome-icon {
223
+ width: 64px; height: 64px;
224
+ background: linear-gradient(135deg, var(--accent), var(--accent-2));
225
+ border-radius: 18px;
226
+ display: grid;
227
+ place-items: center;
228
+ font-size: 32px;
229
+ box-shadow: 0 0 40px var(--accent-glow);
230
+ }
231
+ .welcome h1 { font-size: 26px; font-weight: 600; letter-spacing: -0.5px; }
232
+ .welcome h1 span { color: var(--accent-2); }
233
+ .welcome p { color: var(--text-muted); font-size: 14px; max-width: 380px; line-height: 1.7; }
234
+
235
+ .starter-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; width: 100%; max-width: 500px; }
236
+ .starter-card {
237
+ background: var(--surface);
238
+ border: 1px solid var(--border);
239
+ border-radius: 10px;
240
+ padding: 14px;
241
+ cursor: pointer;
242
+ text-align: left;
243
+ transition: all 0.15s;
244
+ font-family: inherit;
245
+ color: var(--text);
246
+ }
247
+ .starter-card:hover { border-color: var(--accent); background: var(--accent-glow); }
248
+ .starter-card .starter-icon { font-size: 18px; margin-bottom: 6px; display: block; }
249
+ .starter-card .starter-text { font-size: 12px; color: var(--text-muted); }
250
+
251
+ /* Messages */
252
+ .message { margin-bottom: 28px; display: flex; flex-direction: column; }
253
+ .message.user { align-items: flex-end; }
254
+ .message.assistant { align-items: flex-start; }
255
+
256
+ .msg-role {
257
+ font-size: 11px;
258
+ font-weight: 600;
259
+ text-transform: uppercase;
260
+ letter-spacing: 0.7px;
261
+ margin-bottom: 6px;
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 6px;
265
+ }
266
+ .message.user .msg-role { color: var(--accent-2); }
267
+ .message.assistant .msg-role { color: var(--text-muted); }
268
+
269
+ .msg-avatar {
270
+ width: 20px; height: 20px;
271
+ border-radius: 5px;
272
+ display: grid;
273
+ place-items: center;
274
+ font-size: 10px;
275
+ }
276
+ .message.assistant .msg-avatar { background: linear-gradient(135deg, var(--accent), var(--accent-2)); }
277
+ .message.user .msg-avatar { background: var(--user-border); }
278
+
279
+ .msg-bubble {
280
+ max-width: 85%;
281
+ padding: 14px 18px;
282
+ border-radius: var(--radius);
283
+ line-height: 1.7;
284
+ font-size: 14px;
285
+ }
286
+ .message.user .msg-bubble {
287
+ background: var(--user-bubble);
288
+ border: 1px solid var(--user-border);
289
+ border-radius: var(--radius) var(--radius) 4px var(--radius);
290
+ }
291
+ .message.assistant .msg-bubble {
292
+ background: var(--surface);
293
+ border: 1px solid var(--border);
294
+ border-radius: var(--radius) var(--radius) var(--radius) 4px;
295
+ width: 100%;
296
+ max-width: 100%;
297
+ }
298
+
299
+ .msg-bubble code {
300
+ font-family: 'JetBrains Mono', monospace;
301
+ background: rgba(124,106,247,0.1);
302
+ border: 1px solid rgba(124,106,247,0.2);
303
+ padding: 2px 6px;
304
+ border-radius: 4px;
305
+ font-size: 12px;
306
+ color: var(--accent-2);
307
+ }
308
+ .msg-bubble pre {
309
+ background: #111117;
310
+ border: 1px solid var(--border);
311
+ border-radius: 8px;
312
+ padding: 14px;
313
+ overflow-x: auto;
314
+ margin: 10px 0;
315
+ }
316
+ .msg-bubble pre code {
317
+ background: none;
318
+ border: none;
319
+ padding: 0;
320
+ font-size: 13px;
321
+ color: #c9d1d9;
322
+ }
323
+
324
+ .msg-bubble p { margin-bottom: 10px; }
325
+ .msg-bubble p:last-child { margin-bottom: 0; }
326
+ .msg-bubble ul, .msg-bubble ol { padding-left: 20px; margin: 8px 0; }
327
+ .msg-bubble li { margin-bottom: 4px; }
328
+ .msg-bubble strong { color: var(--text); font-weight: 600; }
329
+ .msg-bubble h1,.msg-bubble h2,.msg-bubble h3 { font-weight: 600; margin: 14px 0 6px; color: var(--text); }
330
+
331
+ /* Typing cursor */
332
+ .cursor {
333
+ display: inline-block;
334
+ width: 2px; height: 16px;
335
+ background: var(--accent-2);
336
+ vertical-align: middle;
337
+ margin-left: 2px;
338
+ animation: blink 1s infinite;
339
+ border-radius: 1px;
340
+ }
341
+ @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
342
+
343
+ /* Input area */
344
+ .input-area {
345
+ padding: 16px 24px 20px;
346
+ background: var(--bg);
347
+ border-top: 1px solid var(--border);
348
+ flex-shrink: 0;
349
+ }
350
+
351
+ .input-inner { max-width: 740px; margin: 0 auto; }
352
+
353
+ .input-box {
354
+ background: var(--surface);
355
+ border: 1px solid var(--border);
356
+ border-radius: 14px;
357
+ padding: 14px 16px;
358
+ display: flex;
359
+ align-items: flex-end;
360
+ gap: 10px;
361
+ transition: border-color 0.15s;
362
+ }
363
+ .input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
364
+
365
+ #user-input {
366
+ flex: 1;
367
+ background: none;
368
+ border: none;
369
+ color: var(--text);
370
+ font-family: 'Inter', sans-serif;
371
+ font-size: 14px;
372
+ line-height: 1.6;
373
+ resize: none;
374
+ outline: none;
375
+ max-height: 200px;
376
+ min-height: 24px;
377
+ }
378
+ #user-input::placeholder { color: var(--text-dim); }
379
+
380
+ .send-btn {
381
+ width: 34px; height: 34px;
382
+ background: var(--accent);
383
+ border: none;
384
+ border-radius: 9px;
385
+ color: white;
386
+ cursor: pointer;
387
+ display: grid;
388
+ place-items: center;
389
+ flex-shrink: 0;
390
+ transition: all 0.15s;
391
+ font-size: 14px;
392
+ }
393
+ .send-btn:hover { background: var(--accent-2); transform: scale(1.05); }
394
+ .send-btn:disabled { background: var(--border); cursor: not-allowed; transform: none; }
395
+
396
+ .input-footer {
397
+ display: flex;
398
+ justify-content: center;
399
+ margin-top: 8px;
400
+ font-size: 11px;
401
+ color: var(--text-dim);
402
+ }
403
+
404
+ /* Scrollbar */
405
+ ::-webkit-scrollbar { width: 5px; }
406
+ ::-webkit-scrollbar-track { background: transparent; }
407
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
408
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
409
+
410
+ /* Mobile */
411
+ @media (max-width: 640px) {
412
+ .sidebar { display: none; }
413
+ .starter-grid { grid-template-columns: 1fr; }
414
+ }
415
+ </style>
416
+ </head>
417
+ <body>
418
+
419
+ <!-- Sidebar -->
420
+ <aside class="sidebar">
421
+ <div class="sidebar-header">
422
+ <div class="logo">
423
+ <div class="logo-icon">⚡</div>
424
+ <div class="logo-text">Groq<span>Chat</span></div>
425
+ </div>
426
+ <button class="new-chat-btn" onclick="newChat()">
427
+ <span>+</span> New conversation
428
+ </button>
429
+ </div>
430
+
431
+ <div class="model-section">
432
+ <div class="model-label">Model</div>
433
+ {% for model_id, model in models.items() %}
434
+ <div class="model-option {% if loop.first %}active{% endif %}" data-model="{{ model_id }}" onclick="selectModel('{{ model_id }}', this)">
435
+ <span class="model-icon">{{ model.icon }}</span>
436
+ <div class="model-info">
437
+ <div class="model-name">{{ model.name }}</div>
438
+ <div class="model-desc">{{ model.description }}</div>
439
+ </div>
440
+ </div>
441
+ {% endfor %}
442
+ </div>
443
+
444
+ <div class="history-section">
445
+ <div class="history-label">History</div>
446
+ <div id="history-list">
447
+ <div class="history-empty">No conversations yet</div>
448
+ </div>
449
+ </div>
450
+
451
+ <div class="sidebar-footer">
452
+ Powered by <a href="https://groq.com" target="_blank">Groq</a> · Meta Llama
453
+ </div>
454
+ </aside>
455
+
456
+ <!-- Main -->
457
+ <main class="main">
458
+ <header class="topbar">
459
+ <div class="topbar-model">
460
+ <div class="dot"></div>
461
+ <span id="current-model-label">Llama 3.1 8B Instant</span>
462
+ </div>
463
+ <div class="topbar-actions">
464
+ <button class="icon-btn" onclick="newChat()">⊕ New chat</button>
465
+ <button class="icon-btn" onclick="copyLastResponse()">⎘ Copy</button>
466
+ </div>
467
+ </header>
468
+
469
+ <div class="chat-window" id="chat-window">
470
+ <div class="chat-inner" id="chat-inner">
471
+ <div class="welcome" id="welcome-screen">
472
+ <div class="welcome-icon">⚡</div>
473
+ <h1>Welcome to <span>GroqChat</span></h1>
474
+ <p>Ultra-fast AI conversations powered by Groq's LPU inference. Choose a model and start chatting.</p>
475
+ <div class="starter-grid">
476
+ <button class="starter-card" onclick="sendStarter(this)">
477
+ <span class="starter-icon">💡</span>
478
+ <div><strong>Explain a concept</strong></div>
479
+ <div class="starter-text">How does transformer attention work?</div>
480
+ </button>
481
+ <button class="starter-card" onclick="sendStarter(this)">
482
+ <span class="starter-icon">✍️</span>
483
+ <div><strong>Write for me</strong></div>
484
+ <div class="starter-text">Draft a professional email declining a meeting</div>
485
+ </button>
486
+ <button class="starter-card" onclick="sendStarter(this)">
487
+ <span class="starter-icon">🐛</span>
488
+ <div><strong>Debug code</strong></div>
489
+ <div class="starter-text">Help me find the bug in my Python code</div>
490
+ </button>
491
+ <button class="starter-card" onclick="sendStarter(this)">
492
+ <span class="starter-icon">🌐</span>
493
+ <div><strong>Translate text</strong></div>
494
+ <div class="starter-text">Translate this paragraph to French</div>
495
+ </button>
496
+ </div>
497
+ </div>
498
+ </div>
499
+ </div>
500
+
501
+ <div class="input-area">
502
+ <div class="input-inner">
503
+ <div class="input-box">
504
+ <textarea id="user-input" placeholder="Message GroqChat…" rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea>
505
+ <button class="send-btn" id="send-btn" onclick="sendMessage()">
506
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
507
+ <line x1="22" y1="2" x2="11" y2="13"></line>
508
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
509
+ </svg>
510
+ </button>
511
+ </div>
512
+ <div class="input-footer">GroqChat can make mistakes. Verify important information.</div>
513
+ </div>
514
+ </div>
515
+ </main>
516
+
517
+ <script>
518
+ let messages = [];
519
+ let currentModel = "llama-3.1-8b-instant";
520
+ let modelLabels = {};
521
+ let conversations = [];
522
+ let currentConvId = null;
523
+ let isStreaming = false;
524
+
525
+ // Gather model labels from DOM
526
+ document.querySelectorAll('.model-option').forEach(el => {
527
+ const id = el.dataset.model;
528
+ const name = el.querySelector('.model-name').textContent.trim();
529
+ modelLabels[id] = name;
530
+ });
531
+
532
+ function selectModel(id, el) {
533
+ currentModel = id;
534
+ document.querySelectorAll('.model-option').forEach(o => o.classList.remove('active'));
535
+ el.classList.add('active');
536
+ document.getElementById('current-model-label').textContent = modelLabels[id] || id;
537
+ }
538
+
539
+ function autoResize(el) {
540
+ el.style.height = 'auto';
541
+ el.style.height = Math.min(el.scrollHeight, 200) + 'px';
542
+ }
543
+
544
+ function handleKey(e) {
545
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
546
+ }
547
+
548
+ function sendStarter(btn) {
549
+ const text = btn.querySelector('.starter-text').textContent.trim();
550
+ document.getElementById('user-input').value = text;
551
+ sendMessage();
552
+ }
553
+
554
+ function renderMarkdown(text) {
555
+ return text
556
+ .replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>
557
+ `<pre><code class="language-${lang}">${escHtml(code.trim())}</code></pre>`)
558
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
559
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
560
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
561
+ .replace(/^### (.+)$/gm, '<h3>$1</h3>')
562
+ .replace(/^## (.+)$/gm, '<h2>$1</h2>')
563
+ .replace(/^# (.+)$/gm, '<h1>$1</h1>')
564
+ .replace(/^\- (.+)$/gm, '<li>$1</li>')
565
+ .replace(/(<li>.*<\/li>\n?)+/g, s => `<ul>${s}</ul>`)
566
+ .replace(/\n\n+/g, '</p><p>')
567
+ .replace(/^(?!<[hup])(.+)$/gm, (m) => m.startsWith('<') ? m : m)
568
+ .replace(/^([^<\n].+)$/gm, '$1')
569
+ .split('\n').filter(l => l.trim()).join('\n');
570
+ }
571
+
572
+ function escHtml(s) {
573
+ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
574
+ }
575
+
576
+ function addMessage(role, content, isNew = true) {
577
+ const welcome = document.getElementById('welcome-screen');
578
+ if (welcome) welcome.remove();
579
+
580
+ const inner = document.getElementById('chat-inner');
581
+ const div = document.createElement('div');
582
+ div.className = `message ${role}`;
583
+
584
+ const avatarIcon = role === 'assistant' ? '⚡' : 'U';
585
+ const roleLabel = role === 'assistant' ? 'GroqChat' : 'You';
586
+
587
+ div.innerHTML = `
588
+ <div class="msg-role">
589
+ <div class="msg-avatar">${avatarIcon}</div>
590
+ ${roleLabel}
591
+ </div>
592
+ <div class="msg-bubble" id="msg-${Date.now()}">${role === 'assistant' ? '' : escHtml(content)}</div>
593
+ `;
594
+ inner.appendChild(div);
595
+ scrollToBottom();
596
+ return div.querySelector('.msg-bubble');
597
+ }
598
+
599
+ function scrollToBottom() {
600
+ const w = document.getElementById('chat-window');
601
+ w.scrollTop = w.scrollHeight;
602
+ }
603
+
604
+ async function sendMessage() {
605
+ if (isStreaming) return;
606
+ const input = document.getElementById('user-input');
607
+ const text = input.value.trim();
608
+ if (!text) return;
609
+
610
+ input.value = '';
611
+ input.style.height = 'auto';
612
+ document.getElementById('send-btn').disabled = true;
613
+ isStreaming = true;
614
+
615
+ messages.push({ role: 'user', content: text });
616
+ addMessage('user', text);
617
+
618
+ const assistantBubble = addMessage('assistant', '');
619
+ const cursor = document.createElement('span');
620
+ cursor.className = 'cursor';
621
+ assistantBubble.appendChild(cursor);
622
+ scrollToBottom();
623
+
624
+ let fullText = '';
625
+
626
+ try {
627
+ const res = await fetch('/chat', {
628
+ method: 'POST',
629
+ headers: { 'Content-Type': 'application/json' },
630
+ body: JSON.stringify({ messages, model: currentModel })
631
+ });
632
+
633
+ const reader = res.body.getReader();
634
+ const decoder = new TextDecoder();
635
+
636
+ while (true) {
637
+ const { done, value } = await reader.read();
638
+ if (done) break;
639
+
640
+ const chunk = decoder.decode(value);
641
+ const lines = chunk.split('\n');
642
+ for (const line of lines) {
643
+ if (line.startsWith('data: ')) {
644
+ const data = line.slice(6);
645
+ if (data === '[DONE]') break;
646
+ try {
647
+ const json = JSON.parse(data);
648
+ if (json.content) {
649
+ fullText += json.content;
650
+ assistantBubble.innerHTML = renderMarkdown(fullText);
651
+ assistantBubble.appendChild(cursor);
652
+ scrollToBottom();
653
+ }
654
+ } catch {}
655
+ }
656
+ }
657
+ }
658
+ } catch (err) {
659
+ fullText = '⚠️ Error connecting to the API. Please check your GROQ_API_KEY.';
660
+ }
661
+
662
+ cursor.remove();
663
+ assistantBubble.innerHTML = renderMarkdown(fullText) || '<em style="color:var(--text-dim)">No response.</em>';
664
+ messages.push({ role: 'assistant', content: fullText });
665
+
666
+ // Save to history
667
+ saveToHistory(text);
668
+
669
+ isStreaming = false;
670
+ document.getElementById('send-btn').disabled = false;
671
+ scrollToBottom();
672
+ }
673
+
674
+ function saveToHistory(firstMessage) {
675
+ if (!currentConvId) {
676
+ currentConvId = Date.now();
677
+ conversations.unshift({ id: currentConvId, title: firstMessage.slice(0, 40) + (firstMessage.length > 40 ? '…' : '') });
678
+ renderHistory();
679
+ }
680
+ }
681
+
682
+ function renderHistory() {
683
+ const list = document.getElementById('history-list');
684
+ if (conversations.length === 0) {
685
+ list.innerHTML = '<div class="history-empty">No conversations yet</div>';
686
+ return;
687
+ }
688
+ list.innerHTML = conversations.map(c =>
689
+ `<div class="history-item ${c.id === currentConvId ? 'active' : ''}" onclick="loadConv(${c.id})">${escHtml(c.title)}</div>`
690
+ ).join('');
691
+ }
692
+
693
+ function loadConv(id) {
694
+ // For simplicity, just highlight; full persistence would need backend
695
+ currentConvId = id;
696
+ renderHistory();
697
+ }
698
+
699
+ function newChat() {
700
+ messages = [];
701
+ currentConvId = null;
702
+ const inner = document.getElementById('chat-inner');
703
+ inner.innerHTML = `
704
+ <div class="welcome" id="welcome-screen">
705
+ <div class="welcome-icon">⚡</div>
706
+ <h1>Welcome to <span>GroqChat</span></h1>
707
+ <p>Ultra-fast AI conversations powered by Groq's LPU inference. Choose a model and start chatting.</p>
708
+ <div class="starter-grid">
709
+ <button class="starter-card" onclick="sendStarter(this)">
710
+ <span class="starter-icon">💡</span>
711
+ <div><strong>Explain a concept</strong></div>
712
+ <div class="starter-text">How does transformer attention work?</div>
713
+ </button>
714
+ <button class="starter-card" onclick="sendStarter(this)">
715
+ <span class="starter-icon">✍️</span>
716
+ <div><strong>Write for me</strong></div>
717
+ <div class="starter-text">Draft a professional email declining a meeting</div>
718
+ </button>
719
+ <button class="starter-card" onclick="sendStarter(this)">
720
+ <span class="starter-icon">🐛</span>
721
+ <div><strong>Debug code</strong></div>
722
+ <div class="starter-text">Help me find the bug in my Python code</div>
723
+ </button>
724
+ <button class="starter-card" onclick="sendStarter(this)">
725
+ <span class="starter-icon">🌐</span>
726
+ <div><strong>Translate text</strong></div>
727
+ <div class="starter-text">Translate this paragraph to French</div>
728
+ </button>
729
+ </div>
730
+ </div>`;
731
+ renderHistory();
732
+ }
733
+
734
+ function copyLastResponse() {
735
+ const bubbles = document.querySelectorAll('.message.assistant .msg-bubble');
736
+ if (bubbles.length === 0) return;
737
+ const last = bubbles[bubbles.length - 1];
738
+ navigator.clipboard.writeText(last.innerText).then(() => {
739
+ const btn = document.querySelector('.topbar-actions .icon-btn:last-child');
740
+ const old = btn.textContent;
741
+ btn.textContent = '✓ Copied';
742
+ setTimeout(() => btn.textContent = old, 1500);
743
+ });
744
+ }
745
+ </script>
746
+ </body>
747
+ </html>