Update static/index.html
Browse files- static/index.html +11 -50
static/index.html
CHANGED
@@ -21,18 +21,15 @@
|
|
21 |
--link-color: #64b5f6;
|
22 |
--quote-color: #4CAF50;
|
23 |
}
|
24 |
-
|
25 |
* {
|
26 |
box-sizing: border-box;
|
27 |
margin: 0;
|
28 |
padding: 0;
|
29 |
}
|
30 |
-
|
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);
|
@@ -42,7 +39,6 @@
|
|
42 |
height: 100%;
|
43 |
line-height: 1.6;
|
44 |
}
|
45 |
-
|
46 |
.chat-container {
|
47 |
display: flex;
|
48 |
flex-direction: column;
|
@@ -53,7 +49,6 @@
|
|
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;
|
@@ -61,29 +56,24 @@
|
|
61 |
display: flex;
|
62 |
flex-direction: column;
|
63 |
}
|
64 |
-
|
65 |
.welcome-screen {
|
66 |
text-align: center;
|
67 |
margin: auto;
|
68 |
color: var(--placeholder-color);
|
69 |
padding: 20px;
|
70 |
}
|
71 |
-
|
72 |
.welcome-screen h2 {
|
73 |
font-size: 1.8rem;
|
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 {
|
84 |
display: none;
|
85 |
}
|
86 |
-
|
87 |
.message {
|
88 |
max-width: 85%;
|
89 |
padding: 12px 16px;
|
@@ -95,20 +85,17 @@
|
|
95 |
position: relative;
|
96 |
animation: fadeIn 0.3s ease-out;
|
97 |
}
|
98 |
-
|
99 |
.user-message {
|
100 |
background-color: var(--user-bubble-color);
|
101 |
align-self: flex-end;
|
102 |
border-bottom-right-radius: 4px;
|
103 |
}
|
104 |
-
|
105 |
.ai-message {
|
106 |
background-color: var(--ai-bubble-color);
|
107 |
align-self: flex-start;
|
108 |
border-bottom-left-radius: 4px;
|
109 |
padding-bottom: 30px;
|
110 |
}
|
111 |
-
|
112 |
.ai-message-content {
|
113 |
overflow: hidden;
|
114 |
}
|
@@ -148,7 +135,6 @@
|
|
148 |
background-color: transparent;
|
149 |
color: inherit;
|
150 |
}
|
151 |
-
|
152 |
.audio-button {
|
153 |
position: absolute;
|
154 |
bottom: 6px;
|
@@ -192,7 +178,6 @@
|
|
192 |
.audio-button.loading .loader {
|
193 |
display: block;
|
194 |
}
|
195 |
-
|
196 |
.download-button {
|
197 |
position: absolute;
|
198 |
bottom: 6px;
|
@@ -221,12 +206,10 @@
|
|
221 |
.download-button:hover svg {
|
222 |
fill: #4CAF50;
|
223 |
}
|
224 |
-
|
225 |
@keyframes spin {
|
226 |
0% { transform: rotate(0deg); }
|
227 |
100% { transform: rotate(360deg); }
|
228 |
}
|
229 |
-
|
230 |
.typing-indicator {
|
231 |
display: flex;
|
232 |
align-items: center;
|
@@ -238,12 +221,10 @@
|
|
238 |
}
|
239 |
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
240 |
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
241 |
-
|
242 |
@keyframes bounce {
|
243 |
0%, 80%, 100% { transform: scale(0); }
|
244 |
40% { transform: scale(1.0); }
|
245 |
}
|
246 |
-
|
247 |
.input-area {
|
248 |
display: flex;
|
249 |
align-items: flex-end;
|
@@ -275,7 +256,6 @@
|
|
275 |
outline: none;
|
276 |
}
|
277 |
.input-area textarea::placeholder { color: var(--placeholder-color); }
|
278 |
-
|
279 |
.input-area .icon-button {
|
280 |
background: none;
|
281 |
border: none;
|
@@ -290,7 +270,6 @@
|
|
290 |
opacity: 0.8;
|
291 |
}
|
292 |
.input-area .icon-button svg { width: 24px; height: 24px; fill: var(--icon-color); }
|
293 |
-
|
294 |
.send-button {
|
295 |
background-color: var(--send-button-color);
|
296 |
border-radius: 50%;
|
@@ -306,7 +285,6 @@
|
|
306 |
fill: #888;
|
307 |
}
|
308 |
.send-button svg { fill: white; width: 24px; height: 24px; }
|
309 |
-
|
310 |
@keyframes fadeIn {
|
311 |
from { opacity: 0; transform: translateY(10px); }
|
312 |
to { opacity: 1; transform: translateY(0); }
|
@@ -322,7 +300,6 @@
|
|
322 |
<p>For audio responses, try: <em>"Tell me a bedtime story with audio"</em></p>
|
323 |
</div>
|
324 |
</main>
|
325 |
-
|
326 |
<footer class="input-area">
|
327 |
<form id="chat-form" class="input-wrapper">
|
328 |
<textarea id="message-input" placeholder="Ask anything..." rows="1"></textarea>
|
@@ -340,7 +317,6 @@
|
|
340 |
const chatArea = document.querySelector('.chat-area');
|
341 |
const welcomeScreen = document.querySelector('.welcome-screen');
|
342 |
const sendButton = document.getElementById('send-button');
|
343 |
-
|
344 |
let currentAudio = null;
|
345 |
|
346 |
const adjustTextareaHeight = () => {
|
@@ -354,17 +330,16 @@
|
|
354 |
sendButton.classList.toggle('disabled', isDisabled);
|
355 |
sendButton.disabled = isDisabled;
|
356 |
};
|
357 |
-
|
358 |
const scrollToBottom = () => {
|
359 |
chatArea.scrollTop = chatArea.scrollHeight;
|
360 |
};
|
361 |
|
362 |
const addMessage = (content, sender, isHTML = false, audioFilename = null) => {
|
363 |
welcomeScreen.classList.add('hidden');
|
364 |
-
|
365 |
const messageElement = document.createElement('div');
|
366 |
messageElement.classList.add('message', `${sender}-message`);
|
367 |
-
|
368 |
const contentDiv = document.createElement('div');
|
369 |
if (isHTML) {
|
370 |
contentDiv.classList.add(`${sender}-message-content`);
|
@@ -375,7 +350,7 @@
|
|
375 |
messageElement.appendChild(contentDiv);
|
376 |
|
377 |
if (sender === 'ai' && audioFilename) {
|
378 |
-
//
|
379 |
const downloadButton = document.createElement('button');
|
380 |
downloadButton.classList.add('download-button');
|
381 |
downloadButton.title = "Download audio";
|
@@ -388,8 +363,8 @@
|
|
388 |
window.location.href = `/download/${audioFilename}`;
|
389 |
});
|
390 |
messageElement.appendChild(downloadButton);
|
391 |
-
|
392 |
-
//
|
393 |
const playButton = document.createElement('button');
|
394 |
playButton.classList.add('audio-button');
|
395 |
playButton.dataset.audioUrl = `/static/audio/${audioFilename}`;
|
@@ -405,16 +380,16 @@
|
|
405 |
playAudio(e.currentTarget, url);
|
406 |
});
|
407 |
messageElement.appendChild(playButton);
|
408 |
-
|
409 |
-
// Auto-play audio
|
410 |
setTimeout(() => playButton.click(), 300);
|
411 |
}
|
412 |
-
|
413 |
chatArea.appendChild(messageElement);
|
414 |
scrollToBottom();
|
415 |
return messageElement;
|
416 |
};
|
417 |
-
|
418 |
const showTypingIndicator = () => {
|
419 |
const indicatorElement = document.createElement('div');
|
420 |
indicatorElement.classList.add('message', 'ai-message', 'typing-indicator');
|
@@ -430,29 +405,23 @@
|
|
430 |
currentAudio.currentTime = 0;
|
431 |
currentAudio = null;
|
432 |
}
|
433 |
-
|
434 |
buttonElement.classList.add('loading');
|
435 |
buttonElement.disabled = true;
|
436 |
-
|
437 |
try {
|
438 |
if (!audioUrl) throw new Error("No audio available");
|
439 |
-
|
440 |
currentAudio = new Audio(audioUrl);
|
441 |
currentAudio.play();
|
442 |
-
|
443 |
currentAudio.onended = () => {
|
444 |
buttonElement.classList.remove('loading');
|
445 |
buttonElement.disabled = false;
|
446 |
currentAudio = null;
|
447 |
};
|
448 |
-
|
449 |
currentAudio.onerror = () => {
|
450 |
console.error("Error playing audio");
|
451 |
buttonElement.classList.remove('loading');
|
452 |
buttonElement.disabled = false;
|
453 |
currentAudio = null;
|
454 |
};
|
455 |
-
|
456 |
} catch (error) {
|
457 |
console.error("Audio error:", error);
|
458 |
alert("Failed to play audio. " + (error.message || "Please try again."));
|
@@ -463,28 +432,23 @@
|
|
463 |
|
464 |
const getAIResponse = async (userMessage) => {
|
465 |
const indicator = showTypingIndicator();
|
466 |
-
|
467 |
try {
|
468 |
const response = await fetch('/chat', {
|
469 |
method: 'POST',
|
470 |
headers: { 'Content-Type': 'application/json' },
|
471 |
body: JSON.stringify({ message: userMessage })
|
472 |
});
|
473 |
-
|
474 |
const data = await response.json();
|
475 |
-
|
476 |
if (data.error) {
|
477 |
throw new Error(data.error);
|
478 |
}
|
479 |
-
|
480 |
chatArea.removeChild(indicator);
|
481 |
addMessage(
|
482 |
-
data.response_html,
|
483 |
-
'ai',
|
484 |
true,
|
485 |
data.audio_filename
|
486 |
);
|
487 |
-
|
488 |
} catch (error) {
|
489 |
chatArea.removeChild(indicator);
|
490 |
addMessage("Sorry, I encountered an error. Please try again.", 'ai');
|
@@ -495,12 +459,10 @@
|
|
495 |
chatForm.addEventListener('submit', async (e) => {
|
496 |
e.preventDefault();
|
497 |
const message = messageInput.value.trim();
|
498 |
-
|
499 |
if (message) {
|
500 |
addMessage(message, 'user');
|
501 |
messageInput.value = '';
|
502 |
adjustTextareaHeight();
|
503 |
-
|
504 |
await getAIResponse(message);
|
505 |
}
|
506 |
});
|
@@ -513,7 +475,6 @@
|
|
513 |
});
|
514 |
|
515 |
messageInput.addEventListener('input', adjustTextareaHeight);
|
516 |
-
|
517 |
updateSendButtonState();
|
518 |
messageInput.focus();
|
519 |
});
|
|
|
21 |
--link-color: #64b5f6;
|
22 |
--quote-color: #4CAF50;
|
23 |
}
|
|
|
24 |
* {
|
25 |
box-sizing: border-box;
|
26 |
margin: 0;
|
27 |
padding: 0;
|
28 |
}
|
|
|
29 |
html, body {
|
30 |
height: 100%;
|
31 |
overflow: hidden;
|
32 |
}
|
|
|
33 |
body {
|
34 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
35 |
background-color: var(--background-color);
|
|
|
39 |
height: 100%;
|
40 |
line-height: 1.6;
|
41 |
}
|
|
|
42 |
.chat-container {
|
43 |
display: flex;
|
44 |
flex-direction: column;
|
|
|
49 |
border-left: 1px solid var(--border-color);
|
50 |
border-right: 1px solid var(--border-color);
|
51 |
}
|
|
|
52 |
.chat-area {
|
53 |
flex-grow: 1;
|
54 |
overflow-y: auto;
|
|
|
56 |
display: flex;
|
57 |
flex-direction: column;
|
58 |
}
|
|
|
59 |
.welcome-screen {
|
60 |
text-align: center;
|
61 |
margin: auto;
|
62 |
color: var(--placeholder-color);
|
63 |
padding: 20px;
|
64 |
}
|
|
|
65 |
.welcome-screen h2 {
|
66 |
font-size: 1.8rem;
|
67 |
font-weight: 400;
|
68 |
margin-bottom: 10px;
|
69 |
}
|
|
|
70 |
.welcome-screen p {
|
71 |
font-size: 1rem;
|
72 |
line-height: 1.5;
|
73 |
}
|
|
|
74 |
.welcome-screen.hidden {
|
75 |
display: none;
|
76 |
}
|
|
|
77 |
.message {
|
78 |
max-width: 85%;
|
79 |
padding: 12px 16px;
|
|
|
85 |
position: relative;
|
86 |
animation: fadeIn 0.3s ease-out;
|
87 |
}
|
|
|
88 |
.user-message {
|
89 |
background-color: var(--user-bubble-color);
|
90 |
align-self: flex-end;
|
91 |
border-bottom-right-radius: 4px;
|
92 |
}
|
|
|
93 |
.ai-message {
|
94 |
background-color: var(--ai-bubble-color);
|
95 |
align-self: flex-start;
|
96 |
border-bottom-left-radius: 4px;
|
97 |
padding-bottom: 30px;
|
98 |
}
|
|
|
99 |
.ai-message-content {
|
100 |
overflow: hidden;
|
101 |
}
|
|
|
135 |
background-color: transparent;
|
136 |
color: inherit;
|
137 |
}
|
|
|
138 |
.audio-button {
|
139 |
position: absolute;
|
140 |
bottom: 6px;
|
|
|
178 |
.audio-button.loading .loader {
|
179 |
display: block;
|
180 |
}
|
|
|
181 |
.download-button {
|
182 |
position: absolute;
|
183 |
bottom: 6px;
|
|
|
206 |
.download-button:hover svg {
|
207 |
fill: #4CAF50;
|
208 |
}
|
|
|
209 |
@keyframes spin {
|
210 |
0% { transform: rotate(0deg); }
|
211 |
100% { transform: rotate(360deg); }
|
212 |
}
|
|
|
213 |
.typing-indicator {
|
214 |
display: flex;
|
215 |
align-items: center;
|
|
|
221 |
}
|
222 |
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
223 |
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
|
|
224 |
@keyframes bounce {
|
225 |
0%, 80%, 100% { transform: scale(0); }
|
226 |
40% { transform: scale(1.0); }
|
227 |
}
|
|
|
228 |
.input-area {
|
229 |
display: flex;
|
230 |
align-items: flex-end;
|
|
|
256 |
outline: none;
|
257 |
}
|
258 |
.input-area textarea::placeholder { color: var(--placeholder-color); }
|
|
|
259 |
.input-area .icon-button {
|
260 |
background: none;
|
261 |
border: none;
|
|
|
270 |
opacity: 0.8;
|
271 |
}
|
272 |
.input-area .icon-button svg { width: 24px; height: 24px; fill: var(--icon-color); }
|
|
|
273 |
.send-button {
|
274 |
background-color: var(--send-button-color);
|
275 |
border-radius: 50%;
|
|
|
285 |
fill: #888;
|
286 |
}
|
287 |
.send-button svg { fill: white; width: 24px; height: 24px; }
|
|
|
288 |
@keyframes fadeIn {
|
289 |
from { opacity: 0; transform: translateY(10px); }
|
290 |
to { opacity: 1; transform: translateY(0); }
|
|
|
300 |
<p>For audio responses, try: <em>"Tell me a bedtime story with audio"</em></p>
|
301 |
</div>
|
302 |
</main>
|
|
|
303 |
<footer class="input-area">
|
304 |
<form id="chat-form" class="input-wrapper">
|
305 |
<textarea id="message-input" placeholder="Ask anything..." rows="1"></textarea>
|
|
|
317 |
const chatArea = document.querySelector('.chat-area');
|
318 |
const welcomeScreen = document.querySelector('.welcome-screen');
|
319 |
const sendButton = document.getElementById('send-button');
|
|
|
320 |
let currentAudio = null;
|
321 |
|
322 |
const adjustTextareaHeight = () => {
|
|
|
330 |
sendButton.classList.toggle('disabled', isDisabled);
|
331 |
sendButton.disabled = isDisabled;
|
332 |
};
|
333 |
+
|
334 |
const scrollToBottom = () => {
|
335 |
chatArea.scrollTop = chatArea.scrollHeight;
|
336 |
};
|
337 |
|
338 |
const addMessage = (content, sender, isHTML = false, audioFilename = null) => {
|
339 |
welcomeScreen.classList.add('hidden');
|
|
|
340 |
const messageElement = document.createElement('div');
|
341 |
messageElement.classList.add('message', `${sender}-message`);
|
342 |
+
|
343 |
const contentDiv = document.createElement('div');
|
344 |
if (isHTML) {
|
345 |
contentDiv.classList.add(`${sender}-message-content`);
|
|
|
350 |
messageElement.appendChild(contentDiv);
|
351 |
|
352 |
if (sender === 'ai' && audioFilename) {
|
353 |
+
// Download button
|
354 |
const downloadButton = document.createElement('button');
|
355 |
downloadButton.classList.add('download-button');
|
356 |
downloadButton.title = "Download audio";
|
|
|
363 |
window.location.href = `/download/${audioFilename}`;
|
364 |
});
|
365 |
messageElement.appendChild(downloadButton);
|
366 |
+
|
367 |
+
// Play button
|
368 |
const playButton = document.createElement('button');
|
369 |
playButton.classList.add('audio-button');
|
370 |
playButton.dataset.audioUrl = `/static/audio/${audioFilename}`;
|
|
|
380 |
playAudio(e.currentTarget, url);
|
381 |
});
|
382 |
messageElement.appendChild(playButton);
|
383 |
+
|
384 |
+
// Auto-play first AI audio
|
385 |
setTimeout(() => playButton.click(), 300);
|
386 |
}
|
387 |
+
|
388 |
chatArea.appendChild(messageElement);
|
389 |
scrollToBottom();
|
390 |
return messageElement;
|
391 |
};
|
392 |
+
|
393 |
const showTypingIndicator = () => {
|
394 |
const indicatorElement = document.createElement('div');
|
395 |
indicatorElement.classList.add('message', 'ai-message', 'typing-indicator');
|
|
|
405 |
currentAudio.currentTime = 0;
|
406 |
currentAudio = null;
|
407 |
}
|
|
|
408 |
buttonElement.classList.add('loading');
|
409 |
buttonElement.disabled = true;
|
|
|
410 |
try {
|
411 |
if (!audioUrl) throw new Error("No audio available");
|
|
|
412 |
currentAudio = new Audio(audioUrl);
|
413 |
currentAudio.play();
|
|
|
414 |
currentAudio.onended = () => {
|
415 |
buttonElement.classList.remove('loading');
|
416 |
buttonElement.disabled = false;
|
417 |
currentAudio = null;
|
418 |
};
|
|
|
419 |
currentAudio.onerror = () => {
|
420 |
console.error("Error playing audio");
|
421 |
buttonElement.classList.remove('loading');
|
422 |
buttonElement.disabled = false;
|
423 |
currentAudio = null;
|
424 |
};
|
|
|
425 |
} catch (error) {
|
426 |
console.error("Audio error:", error);
|
427 |
alert("Failed to play audio. " + (error.message || "Please try again."));
|
|
|
432 |
|
433 |
const getAIResponse = async (userMessage) => {
|
434 |
const indicator = showTypingIndicator();
|
|
|
435 |
try {
|
436 |
const response = await fetch('/chat', {
|
437 |
method: 'POST',
|
438 |
headers: { 'Content-Type': 'application/json' },
|
439 |
body: JSON.stringify({ message: userMessage })
|
440 |
});
|
|
|
441 |
const data = await response.json();
|
|
|
442 |
if (data.error) {
|
443 |
throw new Error(data.error);
|
444 |
}
|
|
|
445 |
chatArea.removeChild(indicator);
|
446 |
addMessage(
|
447 |
+
data.response_html,
|
448 |
+
'ai',
|
449 |
true,
|
450 |
data.audio_filename
|
451 |
);
|
|
|
452 |
} catch (error) {
|
453 |
chatArea.removeChild(indicator);
|
454 |
addMessage("Sorry, I encountered an error. Please try again.", 'ai');
|
|
|
459 |
chatForm.addEventListener('submit', async (e) => {
|
460 |
e.preventDefault();
|
461 |
const message = messageInput.value.trim();
|
|
|
462 |
if (message) {
|
463 |
addMessage(message, 'user');
|
464 |
messageInput.value = '';
|
465 |
adjustTextareaHeight();
|
|
|
466 |
await getAIResponse(message);
|
467 |
}
|
468 |
});
|
|
|
475 |
});
|
476 |
|
477 |
messageInput.addEventListener('input', adjustTextareaHeight);
|
|
|
478 |
updateSendButtonState();
|
479 |
messageInput.focus();
|
480 |
});
|