Fix issue where audio recording is not sent on stopping recording

This commit is contained in:
Ilango
2024-01-23 11:03:38 +05:30
parent f384ad9086
commit 318686e622
+183 -176
View File
@@ -1,4 +1,4 @@
import { useState, useRef, useEffect, useCallback } from 'react' import { useState, useRef, useEffect, useCallback, Fragment } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import socketIOClient from 'socket.io-client' import socketIOClient from 'socket.io-client'
@@ -96,6 +96,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
// recording // recording
const [isRecording, setIsRecording] = useState(false) const [isRecording, setIsRecording] = useState(false)
const [recordingNotSupported, setRecordingNotSupported] = useState(false) const [recordingNotSupported, setRecordingNotSupported] = useState(false)
const [isLoadingRecording, setIsLoadingRecording] = useState(false)
const isFileAllowedForUpload = (file) => { const isFileAllowedForUpload = (file) => {
const constraints = getAllowChatFlowUploads.data const constraints = getAllowChatFlowUploads.data
@@ -292,10 +293,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
} }
const onRecordingStopped = async () => { const onRecordingStopped = async () => {
setIsLoadingRecording(true)
stopAudioRecording(addRecordingToPreviews) stopAudioRecording(addRecordingToPreviews)
setIsRecording(false)
setRecordingNotSupported(false)
handlePromptClick('')
} }
const onSourceDialogClick = (data, title) => { const onSourceDialogClick = (data, title) => {
@@ -364,14 +363,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
if (promptStarterInput !== undefined && promptStarterInput.trim() !== '') input = promptStarterInput if (promptStarterInput !== undefined && promptStarterInput.trim() !== '') input = promptStarterInput
setLoading(true) setLoading(true)
const urls = [] const urls = previews.map((item) => {
previews.map((item) => { return {
urls.push({
data: item.data, data: item.data,
type: item.type, type: item.type,
name: item.name, name: item.name,
mime: item.mime mime: item.mime
}) }
}) })
clearPreviews() clearPreviews()
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }]) setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }])
@@ -383,7 +381,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'), history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'),
chatId chatId
} }
if (urls) params.uploads = urls if (urls && urls.length > 0) params.uploads = urls
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params) const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
@@ -584,6 +582,16 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, chatflowid]) }, [open, chatflowid])
useEffect(() => {
// wait for audio recording to load and then send
if (previews.length === 1 && previews[0].type === 'audio') {
setIsRecording(false)
setRecordingNotSupported(false)
handlePromptClick('')
}
// eslint-disable-next-line
}, [previews])
return ( return (
<div onDragEnter={handleDrag}> <div onDragEnter={handleDrag}>
{isDragActive && ( {isDragActive && (
@@ -614,169 +622,167 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
messages.map((message, index) => { messages.map((message, index) => {
return ( return (
// The latest message sent by the user will be animated while waiting for a response // The latest message sent by the user will be animated while waiting for a response
<> <Box
<Box sx={{
sx={{ background: message.type === 'apiMessage' ? theme.palette.asyncSelect.main : ''
background: message.type === 'apiMessage' ? theme.palette.asyncSelect.main : '' }}
}} key={index}
key={index} style={{ display: 'flex' }}
style={{ display: 'flex' }} className={
className={ message.type === 'userMessage' && loading && index === messages.length - 1
message.type === 'userMessage' && loading && index === messages.length - 1 ? customization.isDarkMode
? customization.isDarkMode ? 'usermessagewaiting-dark'
? 'usermessagewaiting-dark' : 'usermessagewaiting-light'
: 'usermessagewaiting-light' : message.type === 'usermessagewaiting'
: message.type === 'usermessagewaiting' ? 'apimessage'
? 'apimessage' : 'usermessage'
: 'usermessage' }
} >
> {/* Display the correct icon depending on the message type */}
{/* Display the correct icon depending on the message type */} {message.type === 'apiMessage' ? (
{message.type === 'apiMessage' ? ( <img src={robotPNG} alt='AI' width='30' height='30' className='boticon' />
<img src={robotPNG} alt='AI' width='30' height='30' className='boticon' /> ) : (
) : ( <img src={userPNG} alt='Me' width='30' height='30' className='usericon' />
<img src={userPNG} alt='Me' width='30' height='30' className='usericon' /> )}
)} <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}> {message.usedTools && (
{message.usedTools && ( <div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}> {message.usedTools.map((tool, index) => {
{message.usedTools.map((tool, index) => { return (
return ( <Chip
<Chip size='small'
size='small' key={index}
key={index} label={tool.tool}
label={tool.tool} component='a'
component='a' sx={{ mr: 1, mt: 1 }}
sx={{ mr: 1, mt: 1 }} variant='outlined'
variant='outlined' clickable
clickable onClick={() => onSourceDialogClick(tool, 'Used Tools')}
onClick={() => onSourceDialogClick(tool, 'Used Tools')} />
/> )
) })}
})}
</div>
)}
{message.fileUploads && message.fileUploads.length > 0 && (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
width: '100%'
}}
>
{message.fileUploads.map((item, index) => {
return (
<>
{item.mime.startsWith('image/') ? (
<Card
key={index}
sx={{
p: 0,
m: 0,
maxWidth: 128,
marginRight: '10px',
flex: '0 0 auto'
}}
>
<CardMedia
component='img'
image={item.data}
sx={{ height: 64 }}
alt={'preview'}
style={messageImageStyle}
/>
</Card>
) : (
// eslint-disable-next-line jsx-a11y/media-has-caption
<audio controls='controls'>
Your browser does not support the &lt;audio&gt; tag.
<source src={item.data} type={item.mime} />
</audio>
)}
</>
)
})}
</div>
)}
<div className='markdownanswer'>
{/* Messages are being rendered in Markdown format */}
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax, rehypeRaw]}
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
key={Math.random()}
chatflowid={chatflowid}
isDialog={isDialog}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}
>
{message.message}
</MemoizedReactMarkdown>
</div> </div>
{message.fileAnnotations && ( )}
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}> {message.fileUploads && message.fileUploads.length > 0 && (
{message.fileAnnotations.map((fileAnnotation, index) => { <div
return ( style={{
<Button display: 'flex',
sx={{ fontSize: '0.85rem', textTransform: 'none', mb: 1 }} flexWrap: 'wrap',
key={index} flexDirection: 'row',
variant='outlined' width: '100%'
onClick={() => downloadFile(fileAnnotation)} }}
endIcon={<IconDownload color={theme.palette.primary.main} />} >
> {message.fileUploads.map((item, index) => {
{fileAnnotation.fileName} return (
</Button> <>
) {item.mime.startsWith('image/') ? (
})} <Card
</div> key={index}
)} sx={{
{message.sourceDocuments && ( p: 0,
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}> m: 0,
{removeDuplicateURL(message).map((source, index) => { maxWidth: 128,
const URL = marginRight: '10px',
source.metadata && source.metadata.source flex: '0 0 auto'
? isValidURL(source.metadata.source) }}
: undefined >
return ( <CardMedia
<Chip component='img'
size='small' image={item.data}
key={index} sx={{ height: 64 }}
label={ alt={'preview'}
URL style={messageImageStyle}
? URL.pathname.substring(0, 15) === '/' />
? URL.host </Card>
: `${URL.pathname.substring(0, 15)}...` ) : (
: `${source.pageContent.substring(0, 15)}...` // eslint-disable-next-line jsx-a11y/media-has-caption
} <audio controls='controls'>
component='a' Your browser does not support the &lt;audio&gt; tag.
sx={{ mr: 1, mb: 1 }} <source src={item.data} type={item.mime} />
variant='outlined' </audio>
clickable )}
onClick={() => </>
URL ? onURLClick(source.metadata.source) : onSourceDialogClick(source) )
} })}
</div>
)}
<div className='markdownanswer'>
{/* Messages are being rendered in Markdown format */}
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax, rehypeRaw]}
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
key={Math.random()}
chatflowid={chatflowid}
isDialog={isDialog}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/> />
) : (
<code className={className} {...props}>
{children}
</code>
) )
})} }
</div> }}
)} >
{message.message}
</MemoizedReactMarkdown>
</div> </div>
</Box> {message.fileAnnotations && (
</> <div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
{message.fileAnnotations.map((fileAnnotation, index) => {
return (
<Button
sx={{ fontSize: '0.85rem', textTransform: 'none', mb: 1 }}
key={index}
variant='outlined'
onClick={() => downloadFile(fileAnnotation)}
endIcon={<IconDownload color={theme.palette.primary.main} />}
>
{fileAnnotation.fileName}
</Button>
)
})}
</div>
)}
{message.sourceDocuments && (
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
{removeDuplicateURL(message).map((source, index) => {
const URL =
source.metadata && source.metadata.source
? isValidURL(source.metadata.source)
: undefined
return (
<Chip
size='small'
key={index}
label={
URL
? URL.pathname.substring(0, 15) === '/'
? URL.host
: `${URL.pathname.substring(0, 15)}...`
: `${source.pageContent.substring(0, 15)}...`
}
component='a'
sx={{ mr: 1, mb: 1 }}
variant='outlined'
clickable
onClick={() =>
URL ? onURLClick(source.metadata.source) : onSourceDialogClick(source)
}
/>
)
})}
</div>
)}
</div>
</Box>
) )
})} })}
</div> </div>
@@ -800,11 +806,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{previews && previews.length > 0 && ( {previews && previews.length > 0 && (
<Box sx={{ width: '100%', mb: 1.5 }}> <Box sx={{ width: '100%', mb: 1.5 }}>
{previews.map((item, index) => ( {previews.map((item, index) => (
<> <Fragment key={index}>
{item.mime.startsWith('image/') ? ( {item.mime.startsWith('image/') ? (
<ImageButton <ImageButton
focusRipple focusRipple
key={index}
style={{ style={{
width: '48px', width: '48px',
height: '48px', height: '48px',
@@ -831,7 +836,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
backgroundColor: theme.palette.grey[500], backgroundColor: theme.palette.grey[500],
flex: '0 0 auto' flex: '0 0 auto'
}} }}
key={index}
variant='outlined' variant='outlined'
> >
<CardMedia <CardMedia
@@ -845,7 +849,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
</IconButton> </IconButton>
</Card> </Card>
)} )}
</> </Fragment>
))} ))}
</Box> </Box>
)} )}
@@ -854,7 +858,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{recordingNotSupported && ( {recordingNotSupported && (
<div className='overlay hide'> <div className='overlay hide'>
<div className='browser-not-supporting-audio-recording-box'> <div className='browser-not-supporting-audio-recording-box'>
<p>To record audio, use modern browsers like Chrome or Firefox that support audio recording.</p> <Typography variant='body1'>
To record audio, use modern browsers like Chrome or Firefox that support audio recording.
</Typography>
<Button <Button
variant='contained' variant='contained'
color='error' color='error'
@@ -880,10 +886,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
}} }}
> >
<div className='recording-elapsed-time'> <div className='recording-elapsed-time'>
<i className='red-recording-dot'> <span className='red-recording-dot'>
<IconCircleDot /> <IconCircleDot />
</i> </span>
<p id='elapsed-time'>00:00</p> <Typography id='elapsed-time'>00:00</Typography>
{isLoadingRecording && <Typography ml={1.5}>Sending...</Typography>}
</div> </div>
<div className='recording-control-buttons-container'> <div className='recording-control-buttons-container'>
<IconButton onClick={onRecordingCancelled} size='small'> <IconButton onClick={onRecordingCancelled} size='small'>