From ae6e3baa35e5cde9db1715e30fdb65eb9e15562f Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Wed, 2 Jul 2025 14:47:50 -0400 Subject: [PATCH] feat: add TTS buttons to user messages and fix delete persistence bug --- src/ui.js | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 3 deletions(-) diff --git a/src/ui.js b/src/ui.js index 5b8996b..12c2f06 100644 --- a/src/ui.js +++ b/src/ui.js @@ -1205,6 +1205,90 @@ async function openUncloseaiEmbeddedModalNew() { justify-content: flex-end; `; + const userTtsBtn = document.createElement("button"); + userTtsBtn.textContent = "πŸ”Š"; + userTtsBtn.title = "Read aloud"; + userTtsBtn.style.cssText = ` + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 12px; + color: white; + transition: all 0.2s; + `; + userTtsBtn.onmouseenter = () => { + userTtsBtn.style.background = 'rgba(255, 255, 255, 0.3)'; + }; + userTtsBtn.onmouseleave = () => { + userTtsBtn.style.background = 'rgba(255, 255, 255, 0.2)'; + }; + + // Store audio for pause/resume functionality + let userAudio = null; + let isPlaying = false; + + userTtsBtn.onclick = async () => { + const modalVoiceSelect = document.getElementById('hermes-voice-select'); + const selectedVoice = modalVoiceSelect?.value || 'alloy'; + + // If audio is already playing, pause it + if (userAudio && !userAudio.paused) { + userAudio.pause(); + userTtsBtn.textContent = "πŸ”Š"; + isPlaying = false; + return; + } + + // If audio exists and is paused, resume it + if (userAudio && userAudio.paused && userAudio.currentTime > 0) { + userAudio.play(); + userTtsBtn.textContent = "⏸️"; + isPlaying = true; + return; + } + + // Generate new audio + const originalText = userTtsBtn.textContent; + userTtsBtn.textContent = "⏳"; + userTtsBtn.disabled = true; + + try { + const result = await speakChatText(message, selectedVoice, 1.0); + userAudio = result.audio; + + // Set up audio event handlers + userAudio.onplay = () => { + userTtsBtn.textContent = "⏸️"; + isPlaying = true; + }; + + userAudio.onpause = () => { + userTtsBtn.textContent = "πŸ”Š"; + isPlaying = false; + }; + + userAudio.onended = () => { + userTtsBtn.textContent = "πŸ”Š"; + isPlaying = false; + userAudio = null; + }; + + // Auto-play the generated audio + try { + await userAudio.play(); + } catch (playError) { + userTtsBtn.textContent = "πŸ”Š"; + } + } catch (error) { + alert("User TTS failed: " + error.message); + userTtsBtn.textContent = originalText; + } finally { + userTtsBtn.disabled = false; + } + }; + const userDeleteBtn = document.createElement("button"); userDeleteBtn.textContent = "πŸ—‘οΈ"; userDeleteBtn.title = "Delete message"; @@ -1234,6 +1318,7 @@ async function openUncloseaiEmbeddedModalNew() { userMsg.remove(); }; + userMessageActions.appendChild(userTtsBtn); userMessageActions.appendChild(userDeleteBtn); userMsg.appendChild(messageText); userMsg.appendChild(userMessageActions); @@ -1542,6 +1627,84 @@ You have complete knowledge of this page content and can reference any details, justify-content: flex-end; `; + const userTtsBtn = document.createElement("button"); + userTtsBtn.textContent = "πŸ”Š"; + userTtsBtn.title = "Read aloud"; + userTtsBtn.style.cssText = ` + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 12px; + color: white; + transition: all 0.2s; + `; + userTtsBtn.onmouseenter = () => { + userTtsBtn.style.background = 'rgba(255, 255, 255, 0.3)'; + }; + userTtsBtn.onmouseleave = () => { + userTtsBtn.style.background = 'rgba(255, 255, 255, 0.2)'; + }; + + // Store audio for pause/resume functionality + let historicalUserAudio = null; + + userTtsBtn.onclick = async () => { + const modalVoiceSelect = document.getElementById('hermes-voice-select'); + const selectedVoice = modalVoiceSelect?.value || 'alloy'; + + // If audio is already playing, pause it + if (historicalUserAudio && !historicalUserAudio.paused) { + historicalUserAudio.pause(); + userTtsBtn.textContent = "πŸ”Š"; + return; + } + + // If audio exists and is paused, resume it + if (historicalUserAudio && historicalUserAudio.paused && historicalUserAudio.currentTime > 0) { + historicalUserAudio.play(); + userTtsBtn.textContent = "⏸️"; + return; + } + + // Generate new audio + const originalText = userTtsBtn.textContent; + userTtsBtn.textContent = "⏳"; + userTtsBtn.disabled = true; + + try { + const result = await speakChatText(msg.content, selectedVoice, 1.0); + historicalUserAudio = result.audio; + + // Set up audio event handlers + historicalUserAudio.onplay = () => { + userTtsBtn.textContent = "⏸️"; + }; + + historicalUserAudio.onpause = () => { + userTtsBtn.textContent = "πŸ”Š"; + }; + + historicalUserAudio.onended = () => { + userTtsBtn.textContent = "πŸ”Š"; + historicalUserAudio = null; + }; + + // Auto-play the generated audio + try { + await historicalUserAudio.play(); + } catch (playError) { + userTtsBtn.textContent = "πŸ”Š"; + } + } catch (error) { + alert("User TTS failed: " + error.message); + userTtsBtn.textContent = originalText; + } finally { + userTtsBtn.disabled = false; + } + }; + const userDeleteBtn = document.createElement("button"); userDeleteBtn.textContent = "πŸ—‘οΈ"; userDeleteBtn.title = "Delete message"; @@ -1561,15 +1724,16 @@ You have complete knowledge of this page content and can reference any details, userDeleteBtn.onmouseleave = () => { userDeleteBtn.style.background = 'rgba(255, 255, 255, 0.2)'; }; - userDeleteBtn.onclick = () => { + userDeleteBtn.onclick = async () => { // Remove from chat history by index const currentHistory = loadConversationHistory(); if (index >= 0 && index < currentHistory.length) { currentHistory.splice(index, 1); saveConversationHistory(currentHistory); - // Update the chat.js module history - const systemMsg = currentHistory.find(msg => msg.role === 'system'); + // Update the chat.js module history to match localStorage + const { updateChatHistory } = await import('./chat.js'); + const { getSystemMessage } = await import('./config.js'); const newHistory = [ { role: 'system', content: getSystemMessage() }, ...currentHistory @@ -1580,6 +1744,7 @@ You have complete knowledge of this page content and can reference any details, userMsg.remove(); }; + userMessageActions.appendChild(userTtsBtn); userMessageActions.appendChild(userDeleteBtn); userMsg.appendChild(messageText); userMsg.appendChild(userMessageActions);