diff --git a/static/css/style.css b/static/css/style.css index 4b580e5..6d01ba1 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1392,3 +1392,39 @@ a:hover { -webkit-box-decoration-break: clone; box-decoration-break: clone; } + +/* TTS play-button states: queued (waiting in line) → loading (fetching) → playing. + Gives auto-play users a visible signal during the gap between message arrival + and audio start, so the wait doesn't feel like a hang. */ +.tts-queued { + cursor: wait !important; + opacity: 0.85; +} +.tts-queued::after { + content: "•"; + display: inline-block; + margin-left: 6px; + animation: ttsPulse 1.2s ease-in-out infinite; +} +.tts-loading { + cursor: progress !important; +} +.tts-loading::after { + content: ""; + display: inline-block; + width: 10px; + height: 10px; + margin-left: 8px; + vertical-align: -1px; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: ttsSpin 0.7s linear infinite; +} +@keyframes ttsPulse { + 0%, 100% { opacity: 0.3; } + 50% { opacity: 1; } +} +@keyframes ttsSpin { + to { transform: rotate(360deg); } +} diff --git a/templates/chat.html b/templates/chat.html index e2e7068..f7b58e1 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -1207,7 +1207,8 @@ async function speakText(text, playButton, messageId) { } // Set button to streaming state - playButton.textContent = "Streaming..."; + playButton.textContent = "Loading"; + playButton.classList.add("tts-loading"); playButton.disabled = true; // Use streaming TTS @@ -1215,6 +1216,7 @@ async function speakText(text, playButton, messageId) { const audio = result.audio; // Enable button immediately and start playback + playButton.classList.remove("tts-loading"); playButton.disabled = false; toggleAudioPlayback(audio, playButton); @@ -1234,6 +1236,7 @@ async function speakText(text, playButton, messageId) { } } catch (error) { console.error('Error in TTS:', error); + playButton.classList.remove("tts-loading"); playButton.textContent = "Play"; // Reset button text on error playButton.disabled = false; } @@ -1251,13 +1254,29 @@ async function speakTextQueued(text, playButton, messageId) { function bindLifecycle(audio) { currentQueuedAudio = audio; + audio.onplay = () => { + playButton.classList.remove("tts-queued", "tts-loading"); + playButton.disabled = false; + playButton.textContent = "Pause"; + currentAudio = audio; + currentAudio.playButton = playButton; + }; + audio.onpause = () => { + if (!audio.ended) playButton.textContent = "Play"; + }; audio.onended = () => { console.log("TTS finished for:", messageId); + playButton.classList.remove("tts-queued", "tts-loading"); + playButton.disabled = false; + playButton.textContent = "Play"; currentQueuedAudio = null; resolve(); }; audio.onerror = () => { console.error("TTS audio error for:", messageId); + playButton.classList.remove("tts-queued", "tts-loading"); + playButton.disabled = false; + playButton.textContent = "Play"; currentQueuedAudio = null; reject(new Error("Audio playback failed")); }; @@ -1319,6 +1338,9 @@ async function speakTextQueued(text, playButton, messageId) { audio.play().catch(reject); } } catch (error) { + playButton.classList.remove("tts-queued", "tts-loading"); + playButton.disabled = false; + playButton.textContent = "Play"; reject(error); } }); @@ -1328,6 +1350,11 @@ async function speakTextQueued(text, playButton, messageId) { function queueTTS(text, playButton, messageId) { ttsQueue.push({ text, playButton, messageId }); console.log("Added to TTS queue:", messageId, "Queue length:", ttsQueue.length); + // Immediately show "Queued" so users see TTS is incoming even before fetch starts. + playButton.classList.remove("tts-loading"); + playButton.classList.add("tts-queued"); + playButton.disabled = true; + playButton.textContent = "Queued"; processNextTTS(); } @@ -1342,6 +1369,12 @@ function processNextTTS() { currentQueuedMessageId = messageId; console.log("Processing TTS from queue:", messageId); + // Swap "Queued" indicator for active "Loading" spinner while we fetch audio. + playButton.classList.remove("tts-queued"); + playButton.classList.add("tts-loading"); + playButton.disabled = true; + playButton.textContent = "Loading"; + // Use non-blocking async processing speakTextQueued(text, playButton, messageId) .then(() => { @@ -1413,6 +1446,14 @@ function toggleAutoPlayTTS() { // If turning off, clear the queue and stop current audio if (!autoPlayTTS) { console.log("Clearing TTS queue, had", ttsQueue.length, "items"); + // Reset any buttons that were showing the Queued/Loading indicator. + ttsQueue.forEach((item) => { + if (item.playButton) { + item.playButton.classList.remove("tts-queued", "tts-loading"); + item.playButton.disabled = false; + item.playButton.textContent = "Play"; + } + }); ttsQueue = []; isPlayingTTS = false;