diff --git a/templates/chat.html b/templates/chat.html index f58f057..eeb848f 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -33,12 +33,7 @@
@@ -161,7 +156,7 @@ function syncInputsAndQueryString() { // Get current values const currentUsername = usernameInputDesktop?.value || username || "guest"; const currentModel = modelSelectDesktop.value; - const currentVoice = voiceSelectDesktop.value || 'tts-1:onyx'; + const currentVoice = voiceSelectDesktop.value; // Update global username variable username = currentUsername; @@ -261,11 +256,13 @@ document.addEventListener('DOMContentLoaded', (event) => { } }); - // Set initial value from URL or default to first option + // Set initial value from URL, localStorage, or first available option const urlParams = new URLSearchParams(window.location.search); - const initialVoice = urlParams.get("voice") || localStorage.getItem('selectedVoice') || voiceSelectDesktop.options[0]?.value || "tts-1:onyx"; - voiceSelectDesktop.value = initialVoice; - if (voiceSelectMobile) voiceSelectMobile.value = initialVoice; + const initialVoice = urlParams.get("voice") || localStorage.getItem('selectedVoice') || voiceSelectDesktop.options[0]?.value; + if (initialVoice) { + voiceSelectDesktop.value = initialVoice; + if (voiceSelectMobile) voiceSelectMobile.value = initialVoice; + } } // Memoization with localStorage (1-minute cache) @@ -322,9 +319,9 @@ document.addEventListener('DOMContentLoaded', (event) => { }) .catch(error => { console.error("Error fetching voices:", error); - // Fallback to default voice if fetch fails - voiceSelectDesktop.innerHTML = ''; - if (voiceSelectMobile) voiceSelectMobile.innerHTML = ''; + // Leave dropdown empty if API fails + voiceSelectDesktop.innerHTML = ''; + if (voiceSelectMobile) voiceSelectMobile.innerHTML = ''; }); } @@ -339,7 +336,7 @@ document.addEventListener('DOMContentLoaded', (event) => { // Set initial model, voice, and username from URL, localStorage, or defaults const initialModel = urlParams.get("model") || storedModel || "None"; - const initialVoice = urlParams.get("voice") || storedVoice || "tts-1:onyx"; + const initialVoice = urlParams.get("voice") || storedVoice || null; const initialUsername = username; // Already set to URL param or "guest" modelSelectDesktop.value = initialModel; @@ -541,6 +538,24 @@ socket.on('update_room_list', function(updatedRoom) { } }); +// Helper function to enable and wire download button +function enableDownloadButton(messageId, playButton, audioUrl, voice) { + const messageWrapper = playButton.closest('.message-wrapper'); + if (!messageWrapper) return; + + const downloadButton = messageWrapper.querySelector('.tts-download-button'); + // Use == instead of === because dataset values are strings, messageId might be number + if (downloadButton && downloadButton.dataset.messageId == messageId) { + downloadButton.style.display = "inline-block"; + downloadButton.onclick = () => { + const link = document.createElement('a'); + link.href = audioUrl; + link.download = `tts-${messageId}-${voice}.mp3`; + link.click(); + }; + } +} + // Function to read text using TTS (for manual button clicks) async function speakText(text, playButton, messageId) { console.log("speakText called with:", {text: text, messageId, autoPlay: autoPlayTTS}); @@ -557,6 +572,7 @@ async function speakText(text, playButton, messageId) { // Check if the audio is already cached if (audioCache[cacheKey]) { const audio = audioCache[cacheKey]; + enableDownloadButton(messageId, playButton, audio.src, voice); toggleAudioPlayback(audio, playButton); return; } @@ -590,6 +606,9 @@ async function speakText(text, playButton, messageId) { // Cache the audio only after it is successfully created audioCache[cacheKey] = audio; + // Enable and show download button + enableDownloadButton(messageId, playButton, audioUrl, voice); + // Enable button and change text to "Pause" playButton.disabled = false; toggleAudioPlayback(audio, playButton); @@ -627,7 +646,9 @@ async function speakTextQueued(text, playButton, messageId) { // Check if audio is cached if (audioCache[cacheKey]) { - playAudio(audioCache[cacheKey]); + const audio = audioCache[cacheKey]; + enableDownloadButton(messageId, playButton, audio.src, voice); + playAudio(audio); return; } @@ -655,6 +676,10 @@ async function speakTextQueued(text, playButton, messageId) { const audio = new Audio(audioUrl); audio.playbackRate = 0.9; audioCache[cacheKey] = audio; + + // Enable and show download button + enableDownloadButton(messageId, playButton, audioUrl, voice); + playAudio(audio); }) .catch(reject); @@ -828,6 +853,14 @@ socket.on("chat_message", (data) => { playButton.onclick = () => speakText(data.content, playButton, data.id); buttonContainer.appendChild(playButton); + // Create the download button for TTS audio (hidden initially) + const downloadButton = document.createElement("button"); + downloadButton.textContent = "Download"; + downloadButton.className = "tts-download-button"; + downloadButton.style.display = "none"; + downloadButton.dataset.messageId = data.id; + buttonContainer.appendChild(downloadButton); + messageWrapper.appendChild(buttonContainer); } @@ -919,6 +952,14 @@ socket.on("previous_messages", (data) => { playButton.onclick = () => speakText(data.content, playButton, data.id); buttonContainer.appendChild(playButton); + // Create the download button for TTS audio (hidden initially) + const downloadButton = document.createElement("button"); + downloadButton.textContent = "Download"; + downloadButton.className = "tts-download-button"; + downloadButton.style.display = "none"; + downloadButton.dataset.messageId = data.id; + buttonContainer.appendChild(downloadButton); + messageWrapper.appendChild(buttonContainer); messageWrapper.appendChild(newMessage); @@ -1065,6 +1106,14 @@ socket.on("message_chunk", (data) => { }; buttonContainer.appendChild(playButton); + // Create the download button for TTS audio (hidden initially) + const downloadButton = document.createElement("button"); + downloadButton.textContent = "Download"; + downloadButton.className = "tts-download-button"; + downloadButton.style.display = "none"; + downloadButton.dataset.messageId = data.id; + buttonContainer.appendChild(downloadButton); + // Insert the button container at the beginning of the message wrapper (before header and content) messageWrapper.insertBefore(buttonContainer, messageWrapper.firstChild);