Athspi commited on
Commit
a78680e
·
verified ·
1 Parent(s): ec9b387

Update static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +140 -295
static/index.html CHANGED
@@ -1,9 +1,10 @@
 
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, maximum-scale=1.0, user-scalable=no">
6
- <title>AstroChat - AI Assistant</title>
7
  <style>
8
  :root {
9
  --background-color: #212121;
@@ -19,8 +20,6 @@
19
  --code-text-color: #f8f8f2;
20
  --link-color: #64b5f6;
21
  --quote-color: #4CAF50;
22
- --audio-color: #00b4d8;
23
- --error-color: #f44336;
24
  }
25
 
26
  * {
@@ -32,31 +31,35 @@
32
  html, body {
33
  height: 100%;
34
  overflow: hidden;
35
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
36
  }
37
 
38
  body {
 
39
  background-color: var(--background-color);
40
  color: var(--text-color);
41
  display: flex;
42
  flex-direction: column;
 
43
  line-height: 1.6;
44
  }
45
 
46
  .chat-container {
47
  display: flex;
48
  flex-direction: column;
49
- height: 100vh;
 
50
  max-width: 800px;
51
  margin: 0 auto;
52
- width: 100%;
 
53
  }
54
 
55
  .chat-area {
56
- flex: 1;
57
  overflow-y: auto;
58
  padding: 20px 16px;
59
- scroll-behavior: smooth;
 
60
  }
61
 
62
  .welcome-screen {
@@ -64,7 +67,6 @@
64
  margin: auto;
65
  color: var(--placeholder-color);
66
  padding: 20px;
67
- animation: fadeIn 1s ease-out;
68
  }
69
 
70
  .welcome-screen h2 {
@@ -72,11 +74,10 @@
72
  font-weight: 400;
73
  margin-bottom: 10px;
74
  }
75
-
76
  .welcome-screen p {
77
  font-size: 1rem;
78
  line-height: 1.5;
79
- margin-bottom: 8px;
80
  }
81
 
82
  .welcome-screen.hidden {
@@ -93,13 +94,6 @@
93
  font-size: 1rem;
94
  position: relative;
95
  animation: fadeIn 0.3s ease-out;
96
- opacity: 0;
97
- animation-fill-mode: forwards;
98
- }
99
-
100
- @keyframes fadeIn {
101
- from { opacity: 0; transform: translateY(10px); }
102
- to { opacity: 1; transform: translateY(0); }
103
  }
104
 
105
  .user-message {
@@ -118,45 +112,14 @@
118
  .ai-message-content {
119
  overflow: hidden;
120
  }
121
-
122
- .ai-message-content p {
123
- margin-bottom: 12px;
124
- }
125
-
126
- .ai-message-content p:last-child {
127
- margin-bottom: 0;
128
- }
129
-
130
- .ai-message-content strong {
131
- color: #ffffff;
132
- font-weight: 600;
133
- }
134
-
135
- .ai-message-content em {
136
- color: #d1d1d1;
137
- font-style: italic;
138
- }
139
-
140
- .ai-message-content a {
141
- color: var(--link-color);
142
- text-decoration: none;
143
- word-break: break-all;
144
- }
145
-
146
- .ai-message-content a:hover {
147
- text-decoration: underline;
148
- }
149
-
150
- .ai-message-content ul,
151
- .ai-message-content ol {
152
- padding-left: 24px;
153
- margin-bottom: 12px;
154
- }
155
-
156
- .ai-message-content li {
157
- margin-bottom: 6px;
158
- }
159
-
160
  .ai-message-content blockquote {
161
  border-left: 3px solid var(--quote-color);
162
  padding-left: 12px;
@@ -164,7 +127,6 @@
164
  color: #bdbdbd;
165
  margin-bottom: 12px;
166
  }
167
-
168
  .ai-message-content pre {
169
  background-color: var(--code-bg-color);
170
  border-radius: 6px;
@@ -172,7 +134,6 @@
172
  overflow-x: auto;
173
  margin-bottom: 12px;
174
  }
175
-
176
  .ai-message-content code {
177
  font-family: 'Courier New', Courier, monospace;
178
  background-color: var(--code-bg-color);
@@ -182,7 +143,6 @@
182
  font-size: 0.9em;
183
  white-space: pre-wrap;
184
  }
185
-
186
  .ai-message-content .code-block code {
187
  padding: 0;
188
  background-color: transparent;
@@ -193,7 +153,7 @@
193
  position: absolute;
194
  bottom: 6px;
195
  right: 8px;
196
- background: rgba(0, 180, 216, 0.1);
197
  border: none;
198
  border-radius: 50%;
199
  width: 30px;
@@ -202,42 +162,36 @@
202
  align-items: center;
203
  justify-content: center;
204
  cursor: pointer;
205
- transition: all 0.2s;
206
  z-index: 10;
207
  }
208
-
209
  .audio-button:hover {
210
- background: rgba(0, 180, 216, 0.2);
211
- transform: scale(1.1);
212
  }
213
-
214
- .audio-button.playing {
215
- background: rgba(0, 180, 216, 0.3);
216
- box-shadow: 0 0 0 2px var(--audio-color);
 
217
  }
218
-
219
- .audio-button.playing svg {
220
- fill: var(--audio-color);
221
  }
222
-
223
  .audio-button.loading .speaker-icon {
224
  display: none;
225
  }
226
-
227
  .audio-button .loader {
228
  display: none;
229
  width: 18px;
230
  height: 18px;
231
  border: 2px solid #f3f3f3;
232
- border-top: 2px solid var(--audio-color);
233
  border-radius: 50%;
234
  animation: spin 1s linear infinite;
235
  }
236
-
237
  .audio-button.loading .loader {
238
  display: block;
239
  }
240
-
241
  @keyframes spin {
242
  0% { transform: rotate(0deg); }
243
  100% { transform: rotate(360deg); }
@@ -248,24 +202,12 @@
248
  align-items: center;
249
  padding: 12px 16px;
250
  }
251
-
252
  .typing-indicator span {
253
- height: 8px;
254
- width: 8px;
255
- background-color: var(--icon-color);
256
- border-radius: 50%;
257
- display: inline-block;
258
- margin: 0 2px;
259
  animation: bounce 1.4s infinite both;
260
  }
261
-
262
- .typing-indicator span:nth-child(2) {
263
- animation-delay: 0.2s;
264
- }
265
-
266
- .typing-indicator span:nth-child(3) {
267
- animation-delay: 0.4s;
268
- }
269
 
270
  @keyframes bounce {
271
  0%, 80%, 100% { transform: scale(0); }
@@ -280,7 +222,6 @@
280
  background-color: var(--input-area-color);
281
  flex-shrink: 0;
282
  }
283
-
284
  .input-wrapper {
285
  display: flex;
286
  align-items: center;
@@ -289,7 +230,6 @@
289
  border-radius: 24px;
290
  padding: 4px;
291
  }
292
-
293
  .input-area textarea {
294
  flex-grow: 1;
295
  border: none;
@@ -304,12 +244,9 @@
304
  font-family: inherit;
305
  outline: none;
306
  }
 
307
 
308
- .input-area textarea::placeholder {
309
- color: var(--placeholder-color);
310
- }
311
-
312
- .icon-button {
313
  background: none;
314
  border: none;
315
  padding: 8px;
@@ -319,66 +256,50 @@
319
  justify-content: center;
320
  transition: opacity 0.2s;
321
  }
322
-
323
- .icon-button:hover {
324
  opacity: 0.8;
325
  }
326
-
327
- .icon-button svg {
328
- width: 24px;
329
- height: 24px;
330
- fill: var(--icon-color);
331
- }
332
-
333
  .send-button {
334
  background-color: var(--send-button-color);
335
  border-radius: 50%;
336
  padding: 8px;
337
  transition: background-color 0.2s, opacity 0.2s;
338
  }
339
-
340
  .send-button.disabled {
341
  background-color: #555;
342
  cursor: not-allowed;
343
  opacity: 0.6;
344
  }
345
-
346
  .send-button.disabled svg {
347
  fill: #888;
348
  }
 
349
 
350
- .send-button svg {
351
- fill: white;
352
- width: 24px;
353
- height: 24px;
354
- }
355
-
356
- .error-message {
357
- color: var(--error-color);
358
- padding: 8px 16px;
359
- text-align: center;
360
- font-size: 0.9rem;
361
- animation: fadeIn 0.3s ease-out;
362
  }
363
  </style>
364
  </head>
365
  <body>
366
  <div class="chat-container">
367
- <main class="chat-area" id="chat-area">
368
  <div class="welcome-screen">
369
- <h2>Welcome to AstroChat!</h2>
370
- <p>Hello! I'm your AI assistant powered by Gemini. I can help with explanations, ideas, and even read responses aloud.</p>
371
- <p>Try saying: <em>"Explain quantum computing"</em> or <em>"Read this poem aloud"</em></p>
372
  </div>
373
  </main>
374
 
375
  <footer class="input-area">
376
  <form id="chat-form" class="input-wrapper">
377
- <button type="button" class="icon-button" id="attach-button" aria-label="Attach file">
378
  <svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5V6H10v9.5a2.5 2.5 0 0 0 5 0V5c-1.38 0-2.5 1.12-2.5 2.5v10.5c0 1.38-1.12 2.5-2.5 2.5s-2.5-1.12-2.5-2.5V5a4 4 0 0 1 8 0v11.5c-1.1 0-2-.9-2-2V6h-1.5z"></path></svg>
379
  </button>
380
- <textarea id="message-input" placeholder="Ask anything..." rows="1" aria-label="Message input"></textarea>
381
- <button type="submit" id="send-button" class="icon-button send-button disabled" aria-label="Send message">
382
  <svg viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2v7z"></path></svg>
383
  </button>
384
  </form>
@@ -389,218 +310,149 @@
389
  document.addEventListener('DOMContentLoaded', () => {
390
  const chatForm = document.getElementById('chat-form');
391
  const messageInput = document.getElementById('message-input');
392
- const chatArea = document.getElementById('chat-area');
393
  const welcomeScreen = document.querySelector('.welcome-screen');
394
  const sendButton = document.getElementById('send-button');
395
  const attachButton = document.getElementById('attach-button');
396
 
397
- // Audio state management
398
- const audioPlayer = {
399
- currentAudio: null,
400
- currentButton: null,
401
- isPlaying: false,
402
-
403
- play: async function(buttonElement, text) {
404
- // Stop any current playback
405
- this.stop();
406
-
407
- // Set loading state
408
- buttonElement.classList.add('loading');
409
- buttonElement.disabled = true;
410
-
411
- try {
412
- // Generate audio
413
- const response = await fetch('/generate-audio', {
414
- method: 'POST',
415
- headers: { 'Content-Type': 'application/json' },
416
- body: JSON.stringify({ text })
417
- });
418
-
419
- if (!response.ok) {
420
- throw new Error(await response.text());
421
- }
422
-
423
- const data = await response.json();
424
-
425
- // Create new audio element
426
- this.currentAudio = new Audio(data.audio_url);
427
- this.currentButton = buttonElement;
428
-
429
- // Event handlers
430
- this.currentAudio.onplay = () => {
431
- this.isPlaying = true;
432
- buttonElement.classList.remove('loading');
433
- buttonElement.classList.add('playing');
434
- };
435
-
436
- this.currentAudio.onended = () => {
437
- this.reset();
438
- };
439
-
440
- this.currentAudio.onerror = () => {
441
- this.reset();
442
- showError("Audio playback failed");
443
- };
444
-
445
- // Start playback
446
- this.currentAudio.play();
447
-
448
- } catch (error) {
449
- console.error("Audio error:", error);
450
- buttonElement.classList.remove('loading', 'playing');
451
- showError("Couldn't generate audio");
452
- }
453
- },
454
-
455
- stop: function() {
456
- if (this.currentAudio) {
457
- this.currentAudio.pause();
458
- this.currentAudio.currentTime = 0;
459
- }
460
- if (this.currentButton) {
461
- this.currentButton.classList.remove('playing');
462
- }
463
- this.reset();
464
- },
465
-
466
- reset: function() {
467
- this.isPlaying = false;
468
- this.currentAudio = null;
469
- if (this.currentButton) {
470
- this.currentButton.classList.remove('loading', 'playing');
471
- this.currentButton.disabled = false;
472
- }
473
- this.currentButton = null;
474
- }
475
- };
476
 
477
- // UI Helpers
478
- function adjustTextareaHeight() {
479
  messageInput.style.height = 'auto';
480
  messageInput.style.height = `${messageInput.scrollHeight}px`;
481
  updateSendButtonState();
482
- }
483
 
484
- function updateSendButtonState() {
485
  const isDisabled = messageInput.value.trim() === '';
486
  sendButton.classList.toggle('disabled', isDisabled);
487
  sendButton.disabled = isDisabled;
488
- }
489
-
490
- function scrollToBottom() {
491
  chatArea.scrollTop = chatArea.scrollHeight;
492
- }
493
-
494
- function showError(message) {
495
- const existingError = document.querySelector('.error-message');
496
- if (existingError) {
497
- existingError.remove();
498
- }
499
-
500
- const errorElement = document.createElement('div');
501
- errorElement.className = 'error-message';
502
- errorElement.textContent = message;
503
- chatArea.appendChild(errorElement);
504
- scrollToBottom();
505
-
506
- setTimeout(() => {
507
- errorElement.remove();
508
- }, 5000);
509
- }
510
 
511
- // Message handling
512
- function addMessage(content, sender, isHTML = false, plainText = '') {
513
  welcomeScreen.classList.add('hidden');
514
 
515
  const messageElement = document.createElement('div');
516
- messageElement.className = `message ${sender}-message`;
517
 
518
  const contentDiv = document.createElement('div');
519
  if (isHTML) {
520
- contentDiv.className = `${sender}-message-content`;
521
  contentDiv.innerHTML = content;
522
  } else {
523
  contentDiv.textContent = content;
524
  }
525
-
526
  messageElement.appendChild(contentDiv);
527
-
528
- // Add audio button for AI messages
529
  if (sender === 'ai' && plainText) {
530
  const audioButton = document.createElement('button');
531
- audioButton.className = 'audio-button';
532
- audioButton.title = "Play audio";
533
  audioButton.innerHTML = `
534
  <svg class="speaker-icon" viewBox="0 0 24 24">
535
  <path d="M3 10v4c0 .55.45 1 1 1h3.5l5.5 5V5L7.5 9H4c-.55 0-1 .45-1 1zm14 0h-1.5c-2.31 0-4.22-1.74-4.47-4H10v12h1.5v-2.22c.25-2.26 2.16-4.22 4.47-4.22H17c1.1 0 2 .9 2 2s-.9 2-2 2h-1.5v2H17c2.21 0 4-1.79 4-4s-1.79-4-4-4z"/>
536
  </svg>
537
  <div class="loader"></div>
538
  `;
539
-
540
- audioButton.addEventListener('click', () => {
541
- audioPlayer.play(audioButton, plainText);
542
- });
543
-
544
  messageElement.appendChild(audioButton);
545
  }
546
 
547
  chatArea.appendChild(messageElement);
548
  scrollToBottom();
549
  return messageElement;
550
- }
551
-
552
- function showTypingIndicator() {
553
- const indicator = document.createElement('div');
554
- indicator.className = 'message ai-message typing-indicator';
555
- indicator.innerHTML = '<span></span><span></span><span></span>';
556
- chatArea.appendChild(indicator);
557
  scrollToBottom();
558
- return indicator;
559
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
 
561
- // AI Communication
562
- async function getAIResponse(userMessage) {
563
  const indicator = showTypingIndicator();
564
 
565
  try {
566
  const response = await fetch('/chat', {
567
  method: 'POST',
568
- headers: { 'Content-Type': 'application/json' },
 
 
569
  body: JSON.stringify({ message: userMessage })
570
  });
571
 
572
- if (!response.ok) {
573
- throw new Error(await response.text());
574
- }
575
-
576
  const data = await response.json();
577
- chatArea.removeChild(indicator);
578
-
579
- const messageElement = addMessage(
580
- data.response_html,
581
- 'ai',
582
- true,
583
- data.response_text
584
- );
585
 
586
- // Auto-play if audio was requested
587
- if (data.audio_requested) {
588
- const audioButton = messageElement.querySelector('.audio-button');
589
- if (audioButton) {
590
- setTimeout(() => {
591
- audioPlayer.play(audioButton, data.response_text);
592
- }, 300);
593
- }
594
  }
595
 
 
 
 
596
  } catch (error) {
597
- console.error("Chat error:", error);
598
  chatArea.removeChild(indicator);
599
- showError("Failed to get response from AI");
 
600
  }
601
- }
602
 
603
- // Event Listeners
604
  chatForm.addEventListener('submit', async (e) => {
605
  e.preventDefault();
606
  const message = messageInput.value.trim();
@@ -624,24 +476,17 @@
624
  messageInput.addEventListener('input', adjustTextareaHeight);
625
 
626
  attachButton.addEventListener('click', () => {
627
- // Future implementation for file attachments
628
- showError("File attachments coming soon!");
629
  messageInput.focus();
630
  });
631
 
632
- // Initial setup
633
  updateSendButtonState();
634
  messageInput.focus();
635
 
636
- // First visit detection
637
- if (!localStorage.getItem('chatVisited')) {
638
  localStorage.setItem('chatVisited', 'true');
639
- setTimeout(() => {
640
- addMessage(
641
- "Pro tip: You can say 'read aloud' to hear my responses!",
642
- 'ai'
643
- );
644
- }, 3000);
645
  }
646
  });
647
  </script>
 
1
+ <!-- static/index.html -->
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
+ <title>AI Chat with Athspi</title>
8
  <style>
9
  :root {
10
  --background-color: #212121;
 
20
  --code-text-color: #f8f8f2;
21
  --link-color: #64b5f6;
22
  --quote-color: #4CAF50;
 
 
23
  }
24
 
25
  * {
 
31
  html, body {
32
  height: 100%;
33
  overflow: hidden;
 
34
  }
35
 
36
  body {
37
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
38
  background-color: var(--background-color);
39
  color: var(--text-color);
40
  display: flex;
41
  flex-direction: column;
42
+ height: 100%;
43
  line-height: 1.6;
44
  }
45
 
46
  .chat-container {
47
  display: flex;
48
  flex-direction: column;
49
+ height: 100%;
50
+ width: 100%;
51
  max-width: 800px;
52
  margin: 0 auto;
53
+ border-left: 1px solid var(--border-color);
54
+ border-right: 1px solid var(--border-color);
55
  }
56
 
57
  .chat-area {
58
+ flex-grow: 1;
59
  overflow-y: auto;
60
  padding: 20px 16px;
61
+ display: flex;
62
+ flex-direction: column;
63
  }
64
 
65
  .welcome-screen {
 
67
  margin: auto;
68
  color: var(--placeholder-color);
69
  padding: 20px;
 
70
  }
71
 
72
  .welcome-screen h2 {
 
74
  font-weight: 400;
75
  margin-bottom: 10px;
76
  }
77
+
78
  .welcome-screen p {
79
  font-size: 1rem;
80
  line-height: 1.5;
 
81
  }
82
 
83
  .welcome-screen.hidden {
 
94
  font-size: 1rem;
95
  position: relative;
96
  animation: fadeIn 0.3s ease-out;
 
 
 
 
 
 
 
97
  }
98
 
99
  .user-message {
 
112
  .ai-message-content {
113
  overflow: hidden;
114
  }
115
+ .ai-message-content p { margin-bottom: 12px; }
116
+ .ai-message-content p:last-child { margin-bottom: 0; }
117
+ .ai-message-content strong { color: #ffffff; font-weight: 600; }
118
+ .ai-message-content em { color: #d1d1d1; font-style: italic; }
119
+ .ai-message-content a { color: var(--link-color); text-decoration: none; word-break: break-all; }
120
+ .ai-message-content a:hover { text-decoration: underline; }
121
+ .ai-message-content ul, .ai-message-content ol { padding-left: 24px; margin-bottom: 12px; }
122
+ .ai-message-content li { margin-bottom: 6px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  .ai-message-content blockquote {
124
  border-left: 3px solid var(--quote-color);
125
  padding-left: 12px;
 
127
  color: #bdbdbd;
128
  margin-bottom: 12px;
129
  }
 
130
  .ai-message-content pre {
131
  background-color: var(--code-bg-color);
132
  border-radius: 6px;
 
134
  overflow-x: auto;
135
  margin-bottom: 12px;
136
  }
 
137
  .ai-message-content code {
138
  font-family: 'Courier New', Courier, monospace;
139
  background-color: var(--code-bg-color);
 
143
  font-size: 0.9em;
144
  white-space: pre-wrap;
145
  }
 
146
  .ai-message-content .code-block code {
147
  padding: 0;
148
  background-color: transparent;
 
153
  position: absolute;
154
  bottom: 6px;
155
  right: 8px;
156
+ background: rgba(255, 255, 255, 0.1);
157
  border: none;
158
  border-radius: 50%;
159
  width: 30px;
 
162
  align-items: center;
163
  justify-content: center;
164
  cursor: pointer;
165
+ transition: background-color 0.2s;
166
  z-index: 10;
167
  }
 
168
  .audio-button:hover {
169
+ background: rgba(255, 255, 255, 0.2);
 
170
  }
171
+ .audio-button svg {
172
+ width: 18px;
173
+ height: 18px;
174
+ fill: var(--icon-color);
175
+ transition: fill 0.2s;
176
  }
177
+ .audio-button:hover svg {
178
+ fill: white;
 
179
  }
 
180
  .audio-button.loading .speaker-icon {
181
  display: none;
182
  }
 
183
  .audio-button .loader {
184
  display: none;
185
  width: 18px;
186
  height: 18px;
187
  border: 2px solid #f3f3f3;
188
+ border-top: 2px solid var(--send-button-color);
189
  border-radius: 50%;
190
  animation: spin 1s linear infinite;
191
  }
 
192
  .audio-button.loading .loader {
193
  display: block;
194
  }
 
195
  @keyframes spin {
196
  0% { transform: rotate(0deg); }
197
  100% { transform: rotate(360deg); }
 
202
  align-items: center;
203
  padding: 12px 16px;
204
  }
 
205
  .typing-indicator span {
206
+ height: 8px; width: 8px; background-color: var(--icon-color); border-radius: 50%; display: inline-block; margin: 0 2px;
 
 
 
 
 
207
  animation: bounce 1.4s infinite both;
208
  }
209
+ .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
210
+ .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
 
 
 
 
 
 
211
 
212
  @keyframes bounce {
213
  0%, 80%, 100% { transform: scale(0); }
 
222
  background-color: var(--input-area-color);
223
  flex-shrink: 0;
224
  }
 
225
  .input-wrapper {
226
  display: flex;
227
  align-items: center;
 
230
  border-radius: 24px;
231
  padding: 4px;
232
  }
 
233
  .input-area textarea {
234
  flex-grow: 1;
235
  border: none;
 
244
  font-family: inherit;
245
  outline: none;
246
  }
247
+ .input-area textarea::placeholder { color: var(--placeholder-color); }
248
 
249
+ .input-area .icon-button {
 
 
 
 
250
  background: none;
251
  border: none;
252
  padding: 8px;
 
256
  justify-content: center;
257
  transition: opacity 0.2s;
258
  }
259
+ .input-area .icon-button:hover {
 
260
  opacity: 0.8;
261
  }
262
+ .input-area .icon-button svg { width: 24px; height: 24px; fill: var(--icon-color); }
263
+
 
 
 
 
 
264
  .send-button {
265
  background-color: var(--send-button-color);
266
  border-radius: 50%;
267
  padding: 8px;
268
  transition: background-color 0.2s, opacity 0.2s;
269
  }
 
270
  .send-button.disabled {
271
  background-color: #555;
272
  cursor: not-allowed;
273
  opacity: 0.6;
274
  }
 
275
  .send-button.disabled svg {
276
  fill: #888;
277
  }
278
+ .send-button svg { fill: white; width: 24px; height: 24px; }
279
 
280
+ @keyframes fadeIn {
281
+ from { opacity: 0; transform: translateY(10px); }
282
+ to { opacity: 1; transform: translateY(0); }
 
 
 
 
 
 
 
 
 
283
  }
284
  </style>
285
  </head>
286
  <body>
287
  <div class="chat-container">
288
+ <main class="chat-area">
289
  <div class="welcome-screen">
290
+ <h2>Welcome to Athspi!</h2>
291
+ <p>Hello! I'm your AI assistant Athspi powered by Gemini. Ask me anything - I can explain concepts, generate ideas, or help with coding!</p>
292
+ <p>Try asking me something like: <em>"Explain quantum computing in simple terms"</em> or <em>"Help me debug this Python code"</em></p>
293
  </div>
294
  </main>
295
 
296
  <footer class="input-area">
297
  <form id="chat-form" class="input-wrapper">
298
+ <button type="button" class="icon-button" id="attach-button">
299
  <svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5V6H10v9.5a2.5 2.5 0 0 0 5 0V5c-1.38 0-2.5 1.12-2.5 2.5v10.5c0 1.38-1.12 2.5-2.5 2.5s-2.5-1.12-2.5-2.5V5a4 4 0 0 1 8 0v11.5c-1.1 0-2-.9-2-2V6h-1.5z"></path></svg>
300
  </button>
301
+ <textarea id="message-input" placeholder="Ask anything..." rows="1"></textarea>
302
+ <button type="submit" id="send-button" class="icon-button send-button disabled">
303
  <svg viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2v7z"></path></svg>
304
  </button>
305
  </form>
 
310
  document.addEventListener('DOMContentLoaded', () => {
311
  const chatForm = document.getElementById('chat-form');
312
  const messageInput = document.getElementById('message-input');
313
+ const chatArea = document.querySelector('.chat-area');
314
  const welcomeScreen = document.querySelector('.welcome-screen');
315
  const sendButton = document.getElementById('send-button');
316
  const attachButton = document.getElementById('attach-button');
317
 
318
+ let currentAudio = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
+ const adjustTextareaHeight = () => {
 
321
  messageInput.style.height = 'auto';
322
  messageInput.style.height = `${messageInput.scrollHeight}px`;
323
  updateSendButtonState();
324
+ };
325
 
326
+ const updateSendButtonState = () => {
327
  const isDisabled = messageInput.value.trim() === '';
328
  sendButton.classList.toggle('disabled', isDisabled);
329
  sendButton.disabled = isDisabled;
330
+ };
331
+
332
+ const scrollToBottom = () => {
333
  chatArea.scrollTop = chatArea.scrollHeight;
334
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ const addMessage = (content, sender, isHTML = false, plainText = '') => {
 
337
  welcomeScreen.classList.add('hidden');
338
 
339
  const messageElement = document.createElement('div');
340
+ messageElement.classList.add('message', `${sender}-message`);
341
 
342
  const contentDiv = document.createElement('div');
343
  if (isHTML) {
344
+ contentDiv.classList.add(`${sender}-message-content`);
345
  contentDiv.innerHTML = content;
346
  } else {
347
  contentDiv.textContent = content;
348
  }
 
349
  messageElement.appendChild(contentDiv);
350
+
 
351
  if (sender === 'ai' && plainText) {
352
  const audioButton = document.createElement('button');
353
+ audioButton.classList.add('audio-button');
354
+ audioButton.dataset.plainText = plainText;
355
  audioButton.innerHTML = `
356
  <svg class="speaker-icon" viewBox="0 0 24 24">
357
  <path d="M3 10v4c0 .55.45 1 1 1h3.5l5.5 5V5L7.5 9H4c-.55 0-1 .45-1 1zm14 0h-1.5c-2.31 0-4.22-1.74-4.47-4H10v12h1.5v-2.22c.25-2.26 2.16-4.22 4.47-4.22H17c1.1 0 2 .9 2 2s-.9 2-2 2h-1.5v2H17c2.21 0 4-1.79 4-4s-1.79-4-4-4z"/>
358
  </svg>
359
  <div class="loader"></div>
360
  `;
361
+ audioButton.title = "Listen to response";
362
+ audioButton.addEventListener('click', () => playAudio(audioButton, plainText));
 
 
 
363
  messageElement.appendChild(audioButton);
364
  }
365
 
366
  chatArea.appendChild(messageElement);
367
  scrollToBottom();
368
  return messageElement;
369
+ };
370
+
371
+ const showTypingIndicator = () => {
372
+ const indicatorElement = document.createElement('div');
373
+ indicatorElement.classList.add('message', 'ai-message', 'typing-indicator');
374
+ indicatorElement.innerHTML = '<span></span><span></span><span></span>';
375
+ chatArea.appendChild(indicatorElement);
376
  scrollToBottom();
377
+ return indicatorElement;
378
+ };
379
+
380
+ const playAudio = async (buttonElement, text) => {
381
+ if (currentAudio) {
382
+ currentAudio.pause();
383
+ currentAudio.currentTime = 0;
384
+ currentAudio = null;
385
+ }
386
+
387
+ buttonElement.classList.add('loading');
388
+ buttonElement.disabled = true;
389
+
390
+ try {
391
+ const response = await fetch('/generate-audio', {
392
+ method: 'POST',
393
+ headers: { 'Content-Type': 'application/json' },
394
+ body: JSON.stringify({ text: text })
395
+ });
396
+
397
+ const data = await response.json();
398
+
399
+ if (data.error) {
400
+ throw new Error(data.error);
401
+ }
402
+
403
+ currentAudio = new Audio(data.audio_url);
404
+ currentAudio.play();
405
+
406
+ currentAudio.onended = () => {
407
+ buttonElement.classList.remove('loading');
408
+ buttonElement.disabled = false;
409
+ currentAudio = null;
410
+ };
411
+
412
+ currentAudio.onerror = () => {
413
+ console.error("Error playing audio.");
414
+ alert("Could not play audio. Please try again.");
415
+ buttonElement.classList.remove('loading');
416
+ buttonElement.disabled = false;
417
+ currentAudio = null;
418
+ };
419
+
420
+ } catch (error) {
421
+ console.error("Error generating audio:", error);
422
+ alert("Failed to generate audio. " + (error.message || "Please try again."));
423
+ buttonElement.classList.remove('loading');
424
+ buttonElement.disabled = false;
425
+ }
426
+ };
427
 
428
+ const getAIResponse = async (userMessage) => {
 
429
  const indicator = showTypingIndicator();
430
 
431
  try {
432
  const response = await fetch('/chat', {
433
  method: 'POST',
434
+ headers: {
435
+ 'Content-Type': 'application/json',
436
+ },
437
  body: JSON.stringify({ message: userMessage })
438
  });
439
 
 
 
 
 
440
  const data = await response.json();
 
 
 
 
 
 
 
 
441
 
442
+ if (data.error) {
443
+ throw new Error(data.error);
 
 
 
 
 
 
444
  }
445
 
446
+ chatArea.removeChild(indicator);
447
+ addMessage(data.response_html, 'ai', true, data.response_text);
448
+
449
  } catch (error) {
 
450
  chatArea.removeChild(indicator);
451
+ addMessage("Sorry, I encountered an error. Please try again.", 'ai');
452
+ console.error("Error:", error);
453
  }
454
+ };
455
 
 
456
  chatForm.addEventListener('submit', async (e) => {
457
  e.preventDefault();
458
  const message = messageInput.value.trim();
 
476
  messageInput.addEventListener('input', adjustTextareaHeight);
477
 
478
  attachButton.addEventListener('click', () => {
479
+ alert("Attachment feature not implemented yet!");
 
480
  messageInput.focus();
481
  });
482
 
 
483
  updateSendButtonState();
484
  messageInput.focus();
485
 
486
+ const isFirstVisit = !localStorage.getItem('chatVisited');
487
+ if (isFirstVisit) {
488
  localStorage.setItem('chatVisited', 'true');
489
+ setTimeout(() => {}, 500);
 
 
 
 
 
490
  }
491
  });
492
  </script>