fix user message TTS

TerminallyLazy committed Aug 4, 2025 at 22:32 UTC f9958ae3d18547c594a88049ce112414d9afacee
1 file changed +59 -9
webui/components/messages/action-buttons/message-action-buttons.js
+59 -9
@@ -6,8 +6,10 @@ function extractTextContent(container) {
6 if (container.classList.contains("kvps-row")) {
7 return container.querySelector(".kvps-val")?.innerText || "";
8 } else if (container.classList.contains("message-text")) {
9 - return container.querySelector("span")?.innerText || "";
9 + // User messages use <pre> elements, not <span>
10 + return container.querySelector("pre")?.innerText || container.querySelector("span")?.innerText || "";
11 } else {
12 + // Other message types use <span> elements
13 return container.querySelector("span")?.innerText || "";
14 }
15 }
@@ -63,37 +65,85 @@ function showCopyFeedback(button, success) {
65 // Speak text using speech store
66 async function speakContent(text, button) {
67 try {
68 + // Validate input text
69 + if (!text || typeof text !== 'string' || text.trim().length === 0) {
70 + console.warn("TTS: No valid text to speak");
71 + return;
72 + }
73 +
74 // Update button state
75 button.classList.add("speaking");
76 const icon = button.querySelector(".material-symbols-outlined");
77 const originalIcon = icon.textContent;
78
79 // Check if already speaking
72 - if (speechStore.isSpeaking && speechStore.speakingText === text) {
73 - // Stop speaking
74 - speechStore.stopSpeaking();
80 + if (speechStore.isSpeaking) {
81 + // Stop speaking using the correct speech store method
82 + if (speechStore.stop) {
83 + speechStore.stop();
84 + } else {
85 + // Fallback if stop doesn't exist
86 + speechSynthesis.cancel();
87 + }
88 icon.textContent = originalIcon;
89 button.classList.remove("speaking");
90 return;
91 }
92
93 // Clean and speak text
81 - const cleanedText = speechStore.cleanText ? speechStore.cleanText(text) : text;
94 + const cleanedText = speechStore.cleanText ? speechStore.cleanText(text.trim()) : text.trim();
95 +
96 + // Validate cleaned text
97 + if (!cleanedText || cleanedText.length === 0) {
98 + console.warn("TTS: Text was empty after cleaning");
99 + button.classList.remove("speaking");
100 + return;
101 + }
102
103 // Update icon to show speaking state
104 icon.textContent = "stop_circle";
105
86 - // Start speaking
87 - await speechStore.speak(cleanedText);
106 + // Start speaking - use the speak function from speech store
107 + if (speechStore.speak) {
108 + await speechStore.speak(cleanedText);
109 + } else {
110 + // Fallback to browser TTS if speech store doesn't have speak function
111 + const utterance = new SpeechSynthesisUtterance(cleanedText);
112 + utterance.lang = 'en-US'; // Ensure English language
113 + utterance.rate = 1.0;
114 + utterance.pitch = 1.0;
115 + utterance.volume = 1.0;
116 +
117 + // Handle utterance events
118 + utterance.onend = () => {
119 + icon.textContent = originalIcon;
120 + button.classList.remove("speaking");
121 + };
122 +
123 + utterance.onerror = (event) => {
124 + console.error("TTS utterance error:", event.error);
125 + icon.textContent = originalIcon;
126 + button.classList.remove("speaking");
127 + };
128 +
129 + speechSynthesis.speak(utterance);
130 + return; // Don't reset button state immediately for browser TTS
131 + }
132
89 - // Reset button state when done
90 - icon.textContent = "volume_up";
133 + // Reset button state when done (for speech store)
134 + icon.textContent = originalIcon;
135 button.classList.remove("speaking");
136
137 } catch (err) {
138 console.error("Speech failed:", err);
139 button.classList.remove("speaking");
140
141 + // Reset icon
142 + const icon = button.querySelector(".material-symbols-outlined");
143 + if (icon) {
144 + icon.textContent = "volume_up";
145 + }
146 +
147 // Show error feedback
148 button.classList.add("error");
149