mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-26 17:30:27 -04:00
fix: stop voice call hanging in "speaking" when TTS fails (#30372)
When the configured TTS provider fails during a voice call, the text answer arrives but the overlay stays in "speaking" with no audio and no error until the user taps to interrupt. The sentence that failed never reaches the audio cache, so the playback loop re-queues it every 200 ms forever. A failed sentence now marks its message as failed. The playback loop drops that message's unplayed sentences, the rest of the turn requests no more TTS, and the overlay returns to listening once the text finishes. The OpenAI-compatible path now shows the provider error once per turn, the same way Read Aloud and the Kokoro path already do. The next turn tries TTS again. Failure is tracked per message so an outage (the report shows 16 parallel requests all failing) costs one toast and no further requests. The trade-off is that a one-off failure mutes the rest of that reply. Verified with the real fetch/playback code in a harness: base loops forever with no toast; with the fix, one toast, the loop ends, later sentences are not requested, a new turn plays normally, and a late failure from a previous turn does not affect the next one. Fixes #30052
This commit is contained in:
@@ -379,6 +379,7 @@
|
||||
};
|
||||
|
||||
let finishedMessages = {};
|
||||
let failedMessages = {};
|
||||
let currentMessageId = null;
|
||||
let currentUtterance: SpeechSynthesisUtterance | null = null;
|
||||
|
||||
@@ -502,7 +503,9 @@
|
||||
const emojiCache = new Map();
|
||||
|
||||
const fetchAudio = async (content) => {
|
||||
if (!audioCache.has(content)) {
|
||||
const id = currentMessageId;
|
||||
|
||||
if (!audioCache.has(content) && !failedMessages[id]) {
|
||||
try {
|
||||
// Set the emoji for the content if needed
|
||||
if ($settings?.showEmojiInCall ?? false) {
|
||||
@@ -535,6 +538,10 @@
|
||||
const res = await synthesizeOpenAISpeech(localStorage.token, getVoiceId(), content).catch(
|
||||
(error) => {
|
||||
console.error(error);
|
||||
if (!failedMessages[id]) {
|
||||
failedMessages[id] = true;
|
||||
toast.error(`${error}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
@@ -550,6 +557,10 @@
|
||||
} catch (error) {
|
||||
console.error('Error synthesizing speech:', error);
|
||||
}
|
||||
|
||||
if (!audioCache.has(content)) {
|
||||
failedMessages[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return audioCache.get(content);
|
||||
@@ -591,7 +602,7 @@
|
||||
} else {
|
||||
await speakSpeechSynthesisHandler(content);
|
||||
}
|
||||
} else {
|
||||
} else if (!failedMessages[id]) {
|
||||
// If not available in the cache, push it back to the queue and delay
|
||||
messages[id].unshift(content); // Re-queue the content at the start
|
||||
console.log(`Audio for "${content}" not yet available in the cache, re-queued...`);
|
||||
|
||||
Reference in New Issue
Block a user