Abort TTS SSE when clicking the stop button

This commit is contained in:
Ilango Rajagopal 2025-08-25 03:42:38 +05:30
parent 55b6be24df
commit d42c096164
1 changed files with 63 additions and 35 deletions

View File

@ -266,7 +266,8 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
audio: null, audio: null,
chunkQueue: [], chunkQueue: [],
isBuffering: false, isBuffering: false,
audioFormat: null audioFormat: null,
abortController: null
}) })
const isFileAllowedForUpload = (file) => { const isFileAllowedForUpload = (file) => {
@ -1590,6 +1591,28 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
}) })
} }
const stopAllTTS = () => {
Object.keys(ttsAudio).forEach((messageId) => {
if (ttsAudio[messageId]) {
ttsAudio[messageId].pause()
ttsAudio[messageId].currentTime = 0
}
})
setTtsAudio({})
if (ttsStreamingState.abortController) {
ttsStreamingState.abortController.abort()
}
if (ttsStreamingState.audio) {
ttsStreamingState.audio.pause()
cleanupTTSStreaming()
}
setIsTTSPlaying({})
setIsTTSLoading({})
}
const handleTTSClick = async (messageId, messageText) => { const handleTTSClick = async (messageId, messageText) => {
if (isTTSLoading[messageId]) return if (isTTSLoading[messageId]) return
@ -1598,6 +1621,8 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
return return
} }
stopAllTTS()
handleTTSStart({ chatMessageId: messageId, format: 'mp3' }) handleTTSStart({ chatMessageId: messageId, format: 'mp3' })
try { try {
let ttsConfig = null let ttsConfig = null
@ -1631,6 +1656,9 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
return return
} }
const abortController = new AbortController()
setTtsStreamingState((prev) => ({ ...prev, abortController }))
const response = await fetch('/api/v1/text-to-speech/generate', { const response = await fetch('/api/v1/text-to-speech/generate', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -1638,6 +1666,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
'x-request-from': 'internal' 'x-request-from': 'internal'
}, },
credentials: 'include', credentials: 'include',
signal: abortController.signal,
body: JSON.stringify({ body: JSON.stringify({
chatId: chatId, chatId: chatId,
chatMessageId: messageId, chatMessageId: messageId,
@ -1659,6 +1688,10 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
let done = false let done = false
while (!done) { while (!done) {
if (abortController.signal.aborted) {
break
}
const result = await reader.read() const result = await reader.read()
done = result.done done = result.done
if (done) { if (done) {
@ -1679,12 +1712,14 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
case 'tts_start': case 'tts_start':
break break
case 'tts_data': case 'tts_data':
if (!abortController.signal.aborted) {
handleTTSDataChunk(event.data.audioChunk) handleTTSDataChunk(event.data.audioChunk)
}
break break
case 'tts_end': case 'tts_end':
if (!abortController.signal.aborted) {
handleTTSEnd() handleTTSEnd()
break }
default:
break break
} }
} }
@ -1692,11 +1727,15 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
} }
} }
} catch (error) { } catch (error) {
if (error.name === 'AbortError') {
console.error('TTS request was aborted')
} else {
console.error('Error with TTS:', error) console.error('Error with TTS:', error)
enqueueSnackbar({ enqueueSnackbar({
message: `TTS failed: ${error.message}`, message: `TTS failed: ${error.message}`,
options: { variant: 'error' } options: { variant: 'error' }
}) })
}
} finally { } finally {
setIsTTSLoading((prev) => { setIsTTSLoading((prev) => {
const newState = { ...prev } const newState = { ...prev }
@ -1805,6 +1844,10 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
const cleanupTTSStreaming = () => { const cleanupTTSStreaming = () => {
setTtsStreamingState((prevState) => { setTtsStreamingState((prevState) => {
if (prevState.abortController) {
prevState.abortController.abort()
}
if (prevState.audio) { if (prevState.audio) {
prevState.audio.pause() prevState.audio.pause()
prevState.audio.removeAttribute('src') prevState.audio.removeAttribute('src')
@ -1830,7 +1873,8 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
audio: null, audio: null,
chunkQueue: [], chunkQueue: [],
isBuffering: false, isBuffering: false,
audioFormat: null audioFormat: null,
abortController: null
} }
}) })
} }
@ -1870,30 +1914,14 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP
allMessages[allMessages.length - 1].id = data.chatMessageId allMessages[allMessages.length - 1].id = data.chatMessageId
return allMessages return allMessages
}) })
setTtsStreamingState((prevState) => { setTtsStreamingState({
if (prevState.audio) {
prevState.audio.pause()
if (prevState.audio.src) {
URL.revokeObjectURL(prevState.audio.src)
}
}
if (prevState.mediaSource && prevState.mediaSource.readyState === 'open') {
try {
prevState.mediaSource.endOfStream()
} catch (error) {
console.error('Error stopping previous media source:', error)
}
}
return {
mediaSource: null, mediaSource: null,
sourceBuffer: null, sourceBuffer: null,
audio: null, audio: null,
chunkQueue: [], chunkQueue: [],
isBuffering: false, isBuffering: false,
audioFormat: data.format audioFormat: data.format,
} abortController: null
}) })
setTimeout(() => initializeTTSStreaming(data), 0) setTimeout(() => initializeTTSStreaming(data), 0)