akhaliq HF Staff commited on
Commit
844d68f
·
verified ·
1 Parent(s): d4169e1

Upload style.css with huggingface_hub

Browse files
Files changed (1) hide show
  1. style.css +669 -51
style.css CHANGED
@@ -1,76 +1,694 @@
1
  * {
2
- box-sizing: border-box;
3
- padding: 0;
4
- margin: 0;
5
- font-family: sans-serif;
6
  }
7
 
8
- html,
9
- body {
10
- height: 100%;
 
 
 
 
 
 
 
 
 
11
  }
12
 
13
  body {
14
- padding: 32px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
- body,
18
- #container {
19
- display: flex;
20
- flex-direction: column;
21
- justify-content: center;
22
- align-items: center;
 
23
  }
24
 
25
- #container {
26
- position: relative;
27
- gap: 0.4rem;
 
 
 
 
 
28
 
29
- width: 640px;
30
- height: 640px;
31
- max-width: 100%;
32
- max-height: 100%;
33
 
34
- border: 2px dashed #D1D5DB;
35
- border-radius: 0.75rem;
36
- overflow: hidden;
37
- cursor: pointer;
38
- margin: 1rem;
 
 
39
 
40
- background-size: 100% 100%;
41
- background-position: center;
42
- background-repeat: no-repeat;
43
- font-size: 18px;
 
 
 
 
 
 
 
 
44
  }
45
 
46
- #upload {
47
- display: none;
 
 
48
  }
49
 
50
- svg {
51
- pointer-events: none;
 
 
 
 
 
 
52
  }
53
 
54
- #example {
55
- font-size: 14px;
56
- text-decoration: underline;
57
- cursor: pointer;
58
  }
59
 
60
- #example:hover {
61
- color: #2563EB;
 
 
62
  }
63
 
64
- .bounding-box {
65
- position: absolute;
66
- box-sizing: border-box;
67
- border: solid 2px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
- .bounding-box-label {
71
- color: white;
72
- position: absolute;
73
- font-size: 12px;
74
- margin: -16px 0 0 -2px;
75
- padding: 1px;
76
- }
 
1
  * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
 
5
  }
6
 
7
+ :root {
8
+ --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9
+ --bg-primary: #ffffff;
10
+ --bg-secondary: #f5f5f7;
11
+ --bg-tertiary: #e8e8ed;
12
+ --text-primary: #1d1d1f;
13
+ --text-secondary: #86868b;
14
+ --user-msg-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
+ --ai-msg-bg: #f5f5f7;
16
+ --border-color: #d2d2d7;
17
+ --shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
18
+ --shadow-hover: 0 8px 30px rgba(0, 0, 0, 0.12);
19
  }
20
 
21
  body {
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
23
+ background: var(--bg-secondary);
24
+ color: var(--text-primary);
25
+ line-height: 1.6;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .container {
30
+ max-width: 1200px;
31
+ margin: 0 auto;
32
+ height: 100vh;
33
+ display: flex;
34
+ flex-direction: column;
35
+ background: var(--bg-primary);
36
+ }
37
+
38
+ .header {
39
+ background: var(--bg-primary);
40
+ border-bottom: 1px solid var(--border-color);
41
+ padding: 1rem 2rem;
42
+ backdrop-filter: blur(20px);
43
+ -webkit-backdrop-filter: blur(20px);
44
+ }
45
+
46
+ .header-content {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ }
51
+
52
+ .logo {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 1rem;
56
  }
57
 
58
+ .logo h1 {
59
+ font-size: 1.5rem;
60
+ font-weight: 600;
61
+ background: var(--primary-gradient);
62
+ -webkit-background-clip: text;
63
+ -webkit-text-fill-color: transparent;
64
+ background-clip: text;
65
  }
66
 
67
+ .built-with {
68
+ color: var(--text-secondary);
69
+ text-decoration: none;
70
+ font-size: 0.875rem;
71
+ padding: 0.5rem 1rem;
72
+ border-radius: 20px;
73
+ transition: all 0.3s ease;
74
+ }
75
 
76
+ .built-with:hover {
77
+ background: var(--bg-secondary);
78
+ color: var(--text-primary);
79
+ }
80
 
81
+ .chat-container {
82
+ flex: 1;
83
+ display: flex;
84
+ flex-direction: column;
85
+ overflow: hidden;
86
+ position: relative;
87
+ }
88
 
89
+ /* Loading Screen */
90
+ .loading-screen {
91
+ position: absolute;
92
+ top: 0;
93
+ left: 0;
94
+ right: 0;
95
+ bottom: 0;
96
+ background: var(--bg-primary);
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ z-index: 1000;
101
  }
102
 
103
+ .loading-content {
104
+ text-align: center;
105
+ padding: 2rem;
106
+ max-width: 400px;
107
  }
108
 
109
+ .spinner {
110
+ width: 60px;
111
+ height: 60px;
112
+ margin: 0 auto 2rem;
113
+ border: 4px solid var(--bg-tertiary);
114
+ border-top: 4px solid #667eea;
115
+ border-radius: 50%;
116
+ animation: spin 1s linear infinite;
117
  }
118
 
119
+ @keyframes spin {
120
+ 0% { transform: rotate(0deg); }
121
+ 100% { transform: rotate(360deg); }
 
122
  }
123
 
124
+ .loading-content h2 {
125
+ font-size: 1.5rem;
126
+ margin-bottom: 0.5rem;
127
+ font-weight: 600;
128
  }
129
 
130
+ .loading-content p {
131
+ color: var(--text-secondary);
132
+ margin-bottom: 1.5rem;
133
+ }
134
+
135
+ .progress-bar {
136
+ width: 100%;
137
+ height: 4px;
138
+ background: var(--bg-tertiary);
139
+ border-radius: 2px;
140
+ overflow: hidden;
141
+ margin-bottom: 1rem;
142
+ }
143
+
144
+ .progress-fill {
145
+ height: 100%;
146
+ background: var(--primary-gradient);
147
+ width: 0%;
148
+ transition: width 0.3s ease;
149
+ }
150
+
151
+ .loading-details {
152
+ font-size: 0.75rem;
153
+ color: var(--text-secondary);
154
+ min-height: 1.5rem;
155
+ }
156
+
157
+ /* Chat Interface */
158
+ .chat-interface {
159
+ flex: 1;
160
+ display: flex;
161
+ flex-direction: column;
162
+ overflow: hidden;
163
+ }
164
+
165
+ .messages-container {
166
+ flex: 1;
167
+ overflow-y: auto;
168
+ padding: 2rem;
169
+ scroll-behavior: smooth;
170
+ }
171
+
172
+ .messages-container::-webkit-scrollbar {
173
+ width: 8px;
174
+ }
175
+
176
+ .messages-container::-webkit-scrollbar-track {
177
+ background: transparent;
178
+ }
179
+
180
+ .messages-container::-webkit-scrollbar-thumb {
181
+ background: var(--border-color);
182
+ border-radius: 4px;
183
+ }
184
+
185
+ .messages-container::-webkit-scrollbar-thumb:hover {
186
+ background: var(--text-secondary);
187
+ }
188
+
189
+ .welcome-message {
190
+ text-align: center;
191
+ padding: 3rem 1rem;
192
+ max-width: 600px;
193
+ margin: 0 auto;
194
+ }
195
+
196
+ .welcome-message h2 {
197
+ font-size: 2rem;
198
+ margin-bottom: 0.5rem;
199
+ font-weight: 600;
200
+ }
201
+
202
+ .welcome-message p {
203
+ color: var(--text-secondary);
204
+ margin-bottom: 2rem;
205
+ }
206
+
207
+ .suggestions {
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ gap: 0.75rem;
211
+ justify-content: center;
212
+ }
213
+
214
+ .suggestion-btn {
215
+ background: var(--bg-secondary);
216
+ border: 1px solid var(--border-color);
217
+ padding: 0.75rem 1.25rem;
218
+ border-radius: 20px;
219
+ cursor: pointer;
220
+ font-size: 0.875rem;
221
+ transition: all 0.3s ease;
222
+ color: var(--text-primary);
223
+ }
224
+
225
+ .suggestion-btn:hover {
226
+ background: var(--bg-tertiary);
227
+ transform: translateY(-2px);
228
+ box-shadow: var(--shadow);
229
+ }
230
+
231
+ .message {
232
+ display: flex;
233
+ gap: 1rem;
234
+ margin-bottom: 1.5rem;
235
+ animation: fadeIn 0.3s ease;
236
+ }
237
+
238
+ @keyframes fadeIn {
239
+ from {
240
+ opacity: 0;
241
+ transform: translateY(10px);
242
+ }
243
+ to {
244
+ opacity: 1;
245
+ transform: translateY(0);
246
+ }
247
+ }
248
+
249
+ .message.user {
250
+ flex-direction: row-reverse;
251
+ }
252
+
253
+ .message-avatar {
254
+ width: 36px;
255
+ height: 36px;
256
+ border-radius: 50%;
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ flex-shrink: 0;
261
+ font-size: 1.25rem;
262
+ }
263
+
264
+ .message.user .message-avatar {
265
+ background: var(--user-msg-bg);
266
+ color: white;
267
+ }
268
+
269
+ .message.ai .message-avatar {
270
+ background: var(--bg-secondary);
271
+ }
272
+
273
+ .message-content {
274
+ max-width: 70%;
275
+ padding: 1rem 1.25rem;
276
+ border-radius: 20px;
277
+ word-wrap: break-word;
278
+ }
279
+
280
+ .message.user .message-content {
281
+ background: var(--user-msg-bg);
282
+ color: white;
283
+ border-bottom-right-radius: 4px;
284
+ }
285
+
286
+ .message.ai .message-content {
287
+ background: var(--ai-msg-bg);
288
+ border-bottom-left-radius: 4px;
289
+ }
290
+
291
+ .message-content p {
292
+ margin: 0;
293
+ white-space: pre-wrap;
294
+ }
295
+
296
+ .typing-indicator {
297
+ display: flex;
298
+ gap: 0.25rem;
299
+ padding: 0.5rem 0;
300
+ }
301
+
302
+ .typing-dot {
303
+ width: 8px;
304
+ height: 8px;
305
+ border-radius: 50%;
306
+ background: var(--text-secondary);
307
+ animation: typing 1.4s infinite;
308
+ }
309
+
310
+ .typing-dot:nth-child(2) {
311
+ animation-delay: 0.2s;
312
+ }
313
+
314
+ .typing-dot:nth-child(3) {
315
+ animation-delay: 0.4s;
316
+ }
317
+
318
+ @keyframes typing {
319
+ 0%, 60%, 100% {
320
+ opacity: 0.3;
321
+ transform: translateY(0);
322
+ }
323
+ 30% {
324
+ opacity: 1;
325
+ transform: translateY(-4px);
326
+ }
327
+ }
328
+
329
+ /* Input Container */
330
+ .input-container {
331
+ padding: 1.5rem 2rem 2rem;
332
+ background: var(--bg-primary);
333
+ border-top: 1px solid var(--border-color);
334
+ }
335
+
336
+ .input-wrapper {
337
+ display: flex;
338
+ gap: 0.75rem;
339
+ background: var(--bg-secondary);
340
+ border-radius: 24px;
341
+ padding: 0.75rem 1rem;
342
+ border: 2px solid transparent;
343
+ transition: all 0.3s ease;
344
+ }
345
+
346
+ .input-wrapper:focus-within {
347
+ border-color: #667eea;
348
+ background: var(--bg-primary);
349
+ box-shadow: var(--shadow);
350
+ }
351
+
352
+ #userInput {
353
+ flex: 1;
354
+ border: none;
355
+ background: transparent;
356
+ font-size: 1rem;
357
+ resize: none;
358
+ outline: none;
359
+ font-family: inherit;
360
+ max-height: 120px;
361
+ color: var(--text-primary);
362
+ }
363
+
364
+ #userInput::placeholder {
365
+ color: var(--text-secondary);
366
+ }
367
+
368
+ .send-btn {
369
+ width: 40px;
370
+ height: 40px;
371
+ border-radius: 50%;
372
+ border: none;
373
+ background: var(--primary-gradient);
374
+ color: white;
375
+ cursor: pointer;
376
+ display: flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ transition: all 0.3s ease;
380
+ flex-shrink: 0;
381
+ }
382
+
383
+ .send-btn:hover:not(:disabled) {
384
+ transform: scale(1.05);
385
+ box-shadow: var(--shadow);
386
+ }
387
+
388
+ .send-btn:disabled {
389
+ opacity: 0.5;
390
+ cursor: not-allowed;
391
+ }
392
+
393
+ .input-info {
394
+ display: flex;
395
+ justify-content: space-between;
396
+ margin-top: 0.5rem;
397
+ padding: 0 0.5rem;
398
+ font-size: 0.75rem;
399
+ color: var(--text-secondary);
400
+ }
401
+
402
+ .model-info {
403
+ font-weight: 500;
404
+ }
405
+
406
+ /* Responsive */
407
+ @media (max-width: 768px) {
408
+ .header {
409
+ padding: 1rem;
410
+ }
411
+
412
+ .logo h1 {
413
+ font-size: 1.25rem;
414
+ }
415
+
416
+ .messages-container {
417
+ padding: 1rem;
418
+ }
419
+
420
+ .message-content {
421
+ max-width: 85%;
422
+ }
423
+
424
+ .input-container {
425
+ padding: 1rem;
426
+ }
427
+
428
+ .suggestions {
429
+ flex-direction: column;
430
+ }
431
+
432
+ .suggestion-btn {
433
+ width: 100%;
434
+ }
435
+ }
436
+
437
+ === app.js ===
438
+ import { pipeline, TextStreamer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.1.2";
439
+
440
+ class ChatApp {
441
+ constructor() {
442
+ this.generator = null;
443
+ this.messages = [
444
+ { role: "system", content: "You are a helpful, friendly, and knowledgeable AI assistant. Provide clear, concise, and
445
+ accurate responses." }
446
+ ];
447
+ this.isGenerating = false;
448
+ this.currentStreamingMessage = null;
449
+
450
+ this.initElements();
451
+ this.attachEventListeners();
452
+ this.initModel();
453
+ }
454
+
455
+ initElements() {
456
+ this.loadingScreen = document.getElementById('loadingScreen');
457
+ this.chatInterface = document.getElementById('chatInterface');
458
+ this.messagesContainer = document.getElementById('messagesContainer');
459
+ this.userInput = document.getElementById('userInput');
460
+ this.sendBtn = document.getElementById('sendBtn');
461
+ this.charCount = document.getElementById('charCount');
462
+ this.loadingStatus = document.getElementById('loadingStatus');
463
+ this.progressFill = document.getElementById('progressFill');
464
+ this.loadingDetails = document.getElementById('loadingDetails');
465
+ }
466
+
467
+ attachEventListeners() {
468
+ this.sendBtn.addEventListener('click', () => this.handleSend());
469
+ this.userInput.addEventListener('keydown', (e) => {
470
+ if (e.key === 'Enter' && !e.shiftKey) {
471
+ e.preventDefault();
472
+ this.handleSend();
473
+ }
474
+ });
475
+
476
+ this.userInput.addEventListener('input', () => {
477
+ this.updateCharCount();
478
+ this.autoResizeTextarea();
479
+ this.updateSendButton();
480
+ });
481
+
482
+ // Suggestion buttons
483
+ document.addEventListener('click', (e) => {
484
+ if (e.target.classList.contains('suggestion-btn')) {
485
+ const prompt = e.target.dataset.prompt;
486
+ this.userInput.value = prompt;
487
+ this.updateCharCount();
488
+ this.updateSendButton();
489
+ this.userInput.focus();
490
+ }
491
+ });
492
+ }
493
+
494
+ async initModel() {
495
+ try {
496
+ this.updateLoadingStatus('Loading AI model...', 10);
497
+
498
+ // Create a text generation pipeline with progress tracking
499
+ this.generator = await pipeline(
500
+ "text-generation",
501
+ "onnx-community/Llama-3.2-1B-Instruct-q4f16",
502
+ {
503
+ dtype: "q4f16",
504
+ device: "webgpu",
505
+ progress_callback: (progress) => {
506
+ if (progress.status === 'progress') {
507
+ const percentage = Math.round((progress.loaded / progress.total) * 100);
508
+ this.updateLoadingStatus(
509
+ `Downloading model: ${progress.file}`,
510
+ percentage,
511
+ `${this.formatBytes(progress.loaded)} / ${this.formatBytes(progress.total)}`
512
+ );
513
+ } else if (progress.status === 'done') {
514
+ this.updateLoadingStatus('Model loaded successfully!', 100);
515
+ }
516
+ }
517
+ }
518
+ );
519
+
520
+ // Model loaded successfully
521
+ setTimeout(() => {
522
+ this.loadingScreen.style.display = 'none';
523
+ this.chatInterface.style.display = 'flex';
524
+ this.userInput.focus();
525
+ }, 500);
526
+
527
+ } catch (error) {
528
+ console.error('Error loading model:', error);
529
+ this.updateLoadingStatus('Error loading model. Please refresh the page.', 0);
530
+ this.loadingDetails.textContent = error.message;
531
+ }
532
+ }
533
+
534
+ updateLoadingStatus(status, progress, details = '') {
535
+ this.loadingStatus.textContent = status;
536
+ this.progressFill.style.width = `${progress}%`;
537
+ this.loadingDetails.textContent = details;
538
+ }
539
+
540
+ formatBytes(bytes) {
541
+ if (bytes === 0) return '0 Bytes';
542
+ const k = 1024;
543
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
544
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
545
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
546
+ }
547
+
548
+ updateCharCount() {
549
+ const count = this.userInput.value.length;
550
+ this.charCount.textContent = `${count}/500`;
551
+ }
552
+
553
+ autoResizeTextarea() {
554
+ this.userInput.style.height = 'auto';
555
+ this.userInput.style.height = Math.min(this.userInput.scrollHeight, 120) + 'px';
556
+ }
557
+
558
+ updateSendButton() {
559
+ const hasText = this.userInput.value.trim().length > 0;
560
+ this.sendBtn.disabled = !hasText || this.isGenerating;
561
+ }
562
+
563
+ async handleSend() {
564
+ const message = this.userInput.value.trim();
565
+ if (!message || this.isGenerating) return;
566
+
567
+ // Add user message
568
+ this.addMessage(message, 'user');
569
+ this.messages.push({ role: "user", content: message });
570
+
571
+ // Clear input
572
+ this.userInput.value = '';
573
+ this.updateCharCount();
574
+ this.updateSendButton();
575
+ this.userInput.style.height = 'auto';
576
+
577
+ // Show typing indicator
578
+ const typingIndicator = this.addTypingIndicator();
579
+ this.isGenerating = true;
580
+
581
+ try {
582
+ // Create AI message container
583
+ const aiMessageDiv = this.createMessageElement('', 'ai');
584
+ typingIndicator.remove();
585
+ this.messagesContainer.appendChild(aiMessageDiv);
586
+
587
+ const contentDiv = aiMessageDiv.querySelector('.message-content p');
588
+ let fullResponse = '';
589
+
590
+ // Generate response with streaming
591
+ const output = await this.generator(this.messages, {
592
+ max_new_tokens: 512,
593
+ do_sample: false,
594
+ temperature: 0.7,
595
+ top_p: 0.9,
596
+ streamer: new TextStreamer(this.generator.tokenizer, {
597
+ skip_prompt: true,
598
+ skip_special_tokens: true,
599
+ callback_function: (text) => {
600
+ fullResponse += text;
601
+ contentDiv.textContent = fullResponse;
602
+ this.scrollToBottom();
603
+ },
604
+ }),
605
+ });
606
+
607
+ // Get final response
608
+ const finalResponse = output[0].generated_text.at(-1).content;
609
+ contentDiv.textContent = finalResponse;
610
+
611
+ // Add to message history
612
+ this.messages.push({ role: "assistant", content: finalResponse });
613
+
614
+ } catch (error) {
615
+ console.error('Error generating response:', error);
616
+ typingIndicator.remove();
617
+ this.addMessage('Sorry, I encountered an error. Please try again.', 'ai');
618
+ } finally {
619
+ this.isGenerating = false;
620
+ this.updateSendButton();
621
+ this.scrollToBottom();
622
+ }
623
+ }
624
+
625
+ addMessage(content, role) {
626
+ const messageDiv = this.createMessageElement(content, role);
627
+
628
+ // Remove welcome message if it exists
629
+ const welcomeMsg = this.messagesContainer.querySelector('.welcome-message');
630
+ if (welcomeMsg) {
631
+ welcomeMsg.remove();
632
+ }
633
+
634
+ this.messagesContainer.appendChild(messageDiv);
635
+ this.scrollToBottom();
636
+ }
637
+
638
+ createMessageElement(content, role) {
639
+ const messageDiv = document.createElement('div');
640
+ messageDiv.className = `message ${role}`;
641
+
642
+ const avatar = document.createElement('div');
643
+ avatar.className = 'message-avatar';
644
+ avatar.textContent = role === 'user' ? '👤' : '🤖';
645
+
646
+ const contentDiv = document.createElement('div');
647
+ contentDiv.className = 'message-content';
648
+
649
+ const textP = document.createElement('p');
650
+ textP.textContent = content;
651
+ contentDiv.appendChild(textP);
652
+
653
+ messageDiv.appendChild(avatar);
654
+ messageDiv.appendChild(contentDiv);
655
+
656
+ return messageDiv;
657
+ }
658
+
659
+ addTypingIndicator() {
660
+ const messageDiv = document.createElement('div');
661
+ messageDiv.className = 'message ai';
662
+
663
+ const avatar = document.createElement('div');
664
+ avatar.className = 'message-avatar';
665
+ avatar.textContent = '🤖';
666
+
667
+ const contentDiv = document.createElement('div');
668
+ contentDiv.className = 'message-content';
669
+
670
+ const typingDiv = document.createElement('div');
671
+ typingDiv.className = 'typing-indicator';
672
+ typingDiv.innerHTML = '<div class="typing-dot"></div>
673
+ <div class="typing-dot"></div>
674
+ <div class="typing-dot"></div>';
675
+
676
+ contentDiv.appendChild(typingDiv);
677
+ messageDiv.appendChild(avatar);
678
+ messageDiv.appendChild(contentDiv);
679
+
680
+ this.messagesContainer.appendChild(messageDiv);
681
+ this.scrollToBottom();
682
+
683
+ return messageDiv;
684
+ }
685
+
686
+ scrollToBottom() {
687
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
688
+ }
689
  }
690
 
691
+ // Initialize the app when DOM is ready
692
+ document.addEventListener('DOMContentLoaded', () => {
693
+ new ChatApp();
694
+ });