Skip to content

Commit 0361397

Browse files
authored
fix(voice): added voice functionality back to chat client (#676)
* fix(voice): added voice functioanlity back to chat clinet * add logic to support deployed chat in staging
1 parent ff2b1d3 commit 0361397

File tree

3 files changed

+153
-333
lines changed

3 files changed

+153
-333
lines changed

apps/sim/app/chat/[subdomain]/chat-client.tsx

Lines changed: 20 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -328,124 +328,27 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
328328
throw new Error('Response body is missing')
329329
}
330330

331-
const messageIdMap = new Map<string, string>()
332-
333-
// Get reader with proper cleanup
334-
const reader = response.body.getReader()
335-
const decoder = new TextDecoder()
336-
337-
const processStream = async () => {
338-
let streamAborted = false
339-
340-
// Add cleanup handler for abort
341-
const cleanup = () => {
342-
streamAborted = true
343-
try {
344-
reader.releaseLock()
345-
} catch (error) {
346-
// Reader might already be released
347-
logger.debug('Reader already released:', error)
348-
}
349-
setIsLoading(false)
350-
}
351-
352-
// Listen for abort events
353-
abortController.signal.addEventListener('abort', cleanup)
354-
355-
try {
356-
while (!streamAborted) {
357-
const { done, value } = await reader.read()
358-
359-
if (done) {
360-
setIsLoading(false)
361-
break
362-
}
363-
364-
if (streamAborted) {
365-
break
366-
}
367-
368-
const chunk = decoder.decode(value, { stream: true })
369-
const lines = chunk.split('\n\n')
370-
371-
for (const line of lines) {
372-
if (line.startsWith('data: ')) {
373-
try {
374-
const json = JSON.parse(line.substring(6))
375-
const { blockId, chunk: contentChunk, event: eventType } = json
376-
377-
if (eventType === 'final' && json.data) {
378-
setIsLoading(false)
379-
380-
// Process final execution result for field extraction
381-
const result = json.data
382-
const nonStreamingLogs =
383-
result.logs?.filter((log: any) => !messageIdMap.has(log.blockId)) || []
384-
385-
// Chat field extraction will be handled by the backend using deployment outputConfigs
386-
387-
return
388-
}
389-
390-
if (blockId && contentChunk) {
391-
if (!messageIdMap.has(blockId)) {
392-
const newMessageId = crypto.randomUUID()
393-
messageIdMap.set(blockId, newMessageId)
394-
setMessages((prev) => [
395-
...prev,
396-
{
397-
id: newMessageId,
398-
content: contentChunk,
399-
type: 'assistant',
400-
timestamp: new Date(),
401-
isStreaming: true,
402-
},
403-
])
404-
} else {
405-
const messageId = messageIdMap.get(blockId)
406-
if (messageId) {
407-
setMessages((prev) =>
408-
prev.map((msg) =>
409-
msg.id === messageId
410-
? { ...msg, content: msg.content + contentChunk }
411-
: msg
412-
)
413-
)
414-
}
415-
}
416-
} else if (blockId && eventType === 'end') {
417-
const messageId = messageIdMap.get(blockId)
418-
if (messageId) {
419-
setMessages((prev) =>
420-
prev.map((msg) =>
421-
msg.id === messageId ? { ...msg, isStreaming: false } : msg
422-
)
423-
)
424-
}
425-
}
426-
} catch (parseError) {
427-
logger.error('Error parsing stream data:', parseError)
428-
// Continue processing other lines even if one fails
429-
}
430-
}
431-
}
432-
}
433-
} catch (streamError: unknown) {
434-
if (streamError instanceof Error && streamError.name === 'AbortError') {
435-
logger.info('Stream processing aborted by user')
436-
return
437-
}
438-
439-
logger.error('Error processing stream:', streamError)
440-
throw streamError
441-
} finally {
442-
// Ensure cleanup always happens
443-
cleanup()
444-
abortController.signal.removeEventListener('abort', cleanup)
331+
// Use the streaming hook with audio support
332+
const shouldPlayAudio = isVoiceInput || isVoiceFirstMode
333+
const audioHandler = shouldPlayAudio
334+
? createAudioStreamHandler(streamTextToAudio, DEFAULT_VOICE_SETTINGS.voiceId)
335+
: undefined
336+
337+
await handleStreamedResponse(
338+
response,
339+
setMessages,
340+
setIsLoading,
341+
scrollToBottom,
342+
userHasScrolled,
343+
{
344+
voiceSettings: {
345+
isVoiceEnabled: shouldPlayAudio,
346+
voiceId: DEFAULT_VOICE_SETTINGS.voiceId,
347+
autoPlayResponses: shouldPlayAudio,
348+
},
349+
audioStreamHandler: audioHandler,
445350
}
446-
}
447-
448-
await processStream()
351+
)
449352
} catch (error: any) {
450353
// Clear timeout in case of error
451354
clearTimeout(timeoutId)

0 commit comments

Comments
 (0)