Feature/add ability to upload file from chat (#3059)

add ability to upload file from chat
This commit is contained in:
Henry Heng
2024-08-25 13:22:48 +01:00
committed by GitHub
parent e8f5f07735
commit 66acd0c000
37 changed files with 1111 additions and 259 deletions
+5
View File
@@ -1,11 +1,16 @@
import client from './client'
const upsertVectorStore = (id, input) => client.post(`/vector/internal-upsert/${id}`, input)
const upsertVectorStoreWithFormData = (id, formData) =>
client.post(`/vector/internal-upsert/${id}`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
const getUpsertHistory = (id, params = {}) => client.get(`/upsert-history/${id}`, { params: { order: 'DESC', ...params } })
const deleteUpsertHistory = (ids) => client.patch(`/upsert-history`, { ids })
export default {
getUpsertHistory,
upsertVectorStore,
upsertVectorStoreWithFormData,
deleteUpsertHistory
}
@@ -34,7 +34,7 @@ import userPNG from '@/assets/images/account.png'
import msgEmptySVG from '@/assets/images/message_empty.svg'
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload } from '@tabler/icons-react'
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload, IconPaperclip } from '@tabler/icons-react'
// Project import
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
@@ -438,6 +438,59 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
setSourceDialogOpen(true)
}
const renderFileUploads = (item, index) => {
if (item?.mime?.startsWith('image/')) {
return (
<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>
)
} else if (item?.mime?.startsWith('audio/')) {
return (
/* eslint-disable 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>
)
} else {
return (
<Card
sx={{
display: 'inline-flex',
alignItems: 'center',
height: '48px',
width: 'max-content',
p: 2,
mr: 1,
flex: '0 0 auto',
backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
}}
variant='outlined'
>
<IconPaperclip size={20} />
<span
style={{
marginLeft: '5px',
color: customization.isDarkMode ? 'white' : 'inherit'
}}
>
{item.name}
</span>
</Card>
)
}
}
useEffect(() => {
const leadEmailFromChatMessages = chatMessages.filter((message) => message.type === 'userMessage' && message.leadEmail)
if (leadEmailFromChatMessages.length) {
@@ -855,37 +908,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
}}
>
{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>
)}
</>
)
return <>{renderFileUploads(item, index)}</>
})}
</div>
)}
+329 -105
View File
@@ -37,7 +37,8 @@ import {
IconTool,
IconSquareFilled,
IconDeviceSdCard,
IconCheck
IconCheck,
IconPaperclip
} from '@tabler/icons-react'
import robotPNG from '@/assets/images/robot.png'
import userPNG from '@/assets/images/account.png'
@@ -64,6 +65,7 @@ import './ChatMessage.css'
import chatmessageApi from '@/api/chatmessage'
import chatflowsApi from '@/api/chatflows'
import predictionApi from '@/api/prediction'
import vectorstoreApi from '@/api/vectorstore'
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
import leadsApi from '@/api/lead'
@@ -84,6 +86,71 @@ const messageImageStyle = {
objectFit: 'cover'
}
const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
const [isHovered, setIsHovered] = useState(false)
const defaultBackgroundColor = customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ position: 'relative', display: 'inline-block' }}
>
<Card
sx={{
display: 'inline-flex',
alignItems: 'center',
height: '48px',
width: 'max-content',
p: 2,
mr: 1,
flex: '0 0 auto',
transition: 'opacity 0.3s',
opacity: isHovered ? 1 : 1,
backgroundColor: isHovered ? 'rgba(0, 0, 0, 0.3)' : defaultBackgroundColor
}}
variant='outlined'
>
<IconPaperclip size={20} style={{ transition: 'filter 0.3s', filter: isHovered ? 'blur(2px)' : 'none' }} />
<span
style={{
marginLeft: '5px',
color: customization.isDarkMode ? 'white' : 'inherit',
transition: 'filter 0.3s',
filter: isHovered ? 'blur(2px)' : 'none'
}}
>
{item.name}
</span>
</Card>
{isHovered && (
<Button
onClick={() => onDelete(item)}
startIcon={<IconTrash color='white' size={22} />}
title='Remove attachment'
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'transparent'
}
}}
></Button>
)}
</div>
)
}
CardWithDeleteOverlay.propTypes = {
item: PropTypes.object,
customization: PropTypes.object,
onDelete: PropTypes.func
}
export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setPreviews }) => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
@@ -111,6 +178,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
const [sourceDialogProps, setSourceDialogProps] = useState({})
const [chatId, setChatId] = useState(uuidv4())
const [isMessageStopping, setIsMessageStopping] = useState(false)
const [uploadedFiles, setUploadedFiles] = useState([])
const [imageUploadAllowedTypes, setImageUploadAllowedTypes] = useState('')
const [fileUploadAllowedTypes, setFileUploadAllowedTypes] = useState('')
const inputRef = useRef(null)
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
@@ -134,8 +204,10 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
const [isLeadSaved, setIsLeadSaved] = useState(false)
// drag & drop and file input
const imgUploadRef = useRef(null)
const fileUploadRef = useRef(null)
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
const [isChatFlowAvailableForImageUploads, setIsChatFlowAvailableForImageUploads] = useState(false)
const [isChatFlowAvailableForFileUploads, setIsChatFlowAvailableForFileUploads] = useState(false)
const [isDragActive, setIsDragActive] = useState(false)
// recording
@@ -158,6 +230,18 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
})
}
if (constraints.isFileUploadAllowed) {
const fileExt = file.name.split('.').pop()
if (fileExt) {
constraints.fileUploadSizeAndTypes.map((allowed) => {
if (allowed.fileTypes.length === 1 && allowed.fileTypes[0] === '*') {
acceptFile = true
} else if (allowed.fileTypes.includes(`.${fileExt}`)) {
acceptFile = true
}
})
}
}
if (!acceptFile) {
alert(`Cannot upload file. Kindly check the allowed file types and maximum allowed size.`)
}
@@ -165,12 +249,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
const handleDrop = async (e) => {
if (!isChatFlowAvailableForUploads) {
if (!isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads) {
return
}
e.preventDefault()
setIsDragActive(false)
let files = []
let uploadedFiles = []
if (e.dataTransfer.files.length > 0) {
for (const file of e.dataTransfer.files) {
if (isFileAllowedForUpload(file) === false) {
@@ -178,6 +264,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
const reader = new FileReader()
const { name } = file
uploadedFiles.push(file)
files.push(
new Promise((resolve) => {
reader.onload = (evt) => {
@@ -188,7 +275,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
let previewUrl
if (file.type.startsWith('audio/')) {
previewUrl = audioUploadSVG
} else if (file.type.startsWith('image/')) {
} else {
previewUrl = URL.createObjectURL(file)
}
resolve({
@@ -205,10 +292,12 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
const newFiles = await Promise.all(files)
setUploadedFiles(uploadedFiles)
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
}
if (e.dataTransfer.items) {
//TODO set files
for (const item of e.dataTransfer.items) {
if (item.kind === 'string' && item.type.match('^text/uri-list')) {
item.getAsString((s) => {
@@ -246,10 +335,12 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
return
}
let files = []
let uploadedFiles = []
for (const file of event.target.files) {
if (isFileAllowedForUpload(file) === false) {
return
}
uploadedFiles.push(file)
const reader = new FileReader()
const { name } = file
files.push(
@@ -273,6 +364,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
const newFiles = await Promise.all(files)
setUploadedFiles(uploadedFiles)
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
// 👇️ reset file input
event.target.value = null
@@ -303,7 +395,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
const handleDrag = (e) => {
if (isChatFlowAvailableForUploads) {
if (isChatFlowAvailableForImageUploads || isChatFlowAvailableForFileUploads) {
e.preventDefault()
e.stopPropagation()
if (e.type === 'dragenter' || e.type === 'dragover') {
@@ -343,11 +435,16 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
setPreviews(previews.filter((item) => item !== itemToDelete))
}
const handleUploadClick = () => {
const handleFileUploadClick = () => {
// 👇️ open file input box on click of another element
fileUploadRef.current.click()
}
const handleImageUploadClick = () => {
// 👇️ open file input box on click of another element
imgUploadRef.current.click()
}
const clearPreviews = () => {
// Revoke the data uris to avoid memory leaks
previews.forEach((file) => URL.revokeObjectURL(file.preview))
@@ -489,6 +586,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
setLoading(false)
setUserInput('')
setUploadedFiles([])
setTimeout(() => {
inputRef.current?.focus()
}, 100)
@@ -526,7 +624,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
if (selectedInput !== undefined && selectedInput.trim() !== '') input = selectedInput
setLoading(true)
const urls = previews.map((item) => {
const uploads = previews.map((item) => {
return {
data: item.data,
type: item.type,
@@ -535,7 +633,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
})
clearPreviews()
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }])
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: uploads }])
// Send user question to Prediction Internal API
try {
@@ -543,11 +641,30 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
question: input,
chatId
}
if (urls && urls.length > 0) params.uploads = urls
if (uploads && uploads.length > 0) params.uploads = uploads
if (leadEmail) params.leadEmail = leadEmail
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
if (action) params.action = action
if (uploadedFiles.length > 0) {
const formData = new FormData()
for (const file of uploadedFiles) {
formData.append('files', file)
}
formData.append('chatId', chatId)
const response = await vectorstoreApi.upsertVectorStoreWithFormData(chatflowid, formData)
if (!response.data) {
setMessages((prevMessages) => [...prevMessages, { message: 'Unable to upload documents', type: 'apiMessage' }])
} else {
// delay for vector store to be updated
const delay = (delayInms) => {
return new Promise((resolve) => setTimeout(resolve, delayInms))
}
await delay(2500) //TODO: check if embeddings can be retrieved using file name as metadata filter
}
}
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
if (response.data) {
@@ -598,6 +715,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
setLocalStorageChatflow(chatflowid, data.chatId)
setLoading(false)
setUserInput('')
setUploadedFiles([])
setTimeout(() => {
inputRef.current?.focus()
scrollToBottom()
@@ -717,8 +835,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
// Get chatflow uploads capability
useEffect(() => {
if (getAllowChatFlowUploads.data) {
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
setIsChatFlowAvailableForImageUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
setIsChatFlowAvailableForFileUploads(getAllowChatFlowUploads.data?.isFileUploadAllowed ?? false)
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
setImageUploadAllowedTypes(getAllowChatFlowUploads.data?.imgUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
setFileUploadAllowedTypes(getAllowChatFlowUploads.data?.fileUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getAllowChatFlowUploads.data])
@@ -822,6 +943,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
return () => {
setUserInput('')
setUploadedFiles([])
setLoading(false)
setMessages([
{
@@ -965,6 +1087,105 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
)
}
const previewDisplay = (item) => {
if (item.mime.startsWith('image/')) {
return (
<ImageButton
focusRipple
style={{
width: '48px',
height: '48px',
marginRight: '10px',
flex: '0 0 auto'
}}
onClick={() => handleDeletePreview(item)}
>
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
<ImageBackdrop className='MuiImageBackdrop-root' />
<ImageMarked className='MuiImageMarked-root'>
<IconTrash size={20} color='white' />
</ImageMarked>
</ImageButton>
)
} else if (item.mime.startsWith('audio/')) {
return (
<Card
sx={{
display: 'inline-flex',
alignItems: 'center',
height: '48px',
width: isDialog ? ps?.current?.offsetWidth / 4 : ps?.current?.offsetWidth / 2,
p: 0.5,
mr: 1,
backgroundColor: theme.palette.grey[500],
flex: '0 0 auto'
}}
variant='outlined'
>
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
<IconTrash size={20} color='white' />
</IconButton>
</Card>
)
} else {
return <CardWithDeleteOverlay item={item} customization={customization} onDelete={() => handleDeletePreview(item)} />
}
}
const renderFileUploads = (item, index) => {
if (item?.mime?.startsWith('image/')) {
return (
<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>
)
} else if (item?.mime?.startsWith('audio/')) {
return (
/* eslint-disable 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>
)
} else {
return (
<Card
sx={{
display: 'inline-flex',
alignItems: 'center',
height: '48px',
width: 'max-content',
p: 2,
mr: 1,
flex: '0 0 auto',
backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
}}
variant='outlined'
>
<IconPaperclip size={20} />
<span
style={{
marginLeft: '5px',
color: customization.isDarkMode ? 'white' : 'inherit'
}}
>
{item.name}
</span>
</Card>
)
}
}
return (
<div onDragEnter={handleDrag}>
{isDragActive && (
@@ -976,19 +1197,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
onDrop={handleDrop}
/>
)}
{isDragActive && getAllowChatFlowUploads.data?.isImageUploadAllowed && (
<Box className='drop-overlay'>
<Typography variant='h2'>Drop here to upload</Typography>
{getAllowChatFlowUploads.data.imgUploadSizeAndTypes.map((allowed) => {
return (
<>
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
<Typography variant='subtitle1'>Max Allowed Size: {allowed.maxUploadSize} MB</Typography>
</>
)
})}
</Box>
)}
{isDragActive &&
(getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isFileAllowedForUpload) && (
<Box className='drop-overlay'>
<Typography variant='h2'>Drop here to upload</Typography>
{[
...getAllowChatFlowUploads.data.imgUploadSizeAndTypes,
...getAllowChatFlowUploads.data.fileUploadSizeAndTypes
].map((allowed) => {
return (
<>
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
{allowed.maxUploadSize && (
<Typography variant='subtitle1'>Max Allowed Size: {allowed.maxUploadSize} MB</Typography>
)}
</>
)
})}
</Box>
)}
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
<div id='messagelist' className={'messagelist'}>
{messages &&
@@ -1038,36 +1265,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}}
>
{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>
)}
</>
)
return <>{renderFileUploads(item, index)}</>
})}
</div>
)}
@@ -1564,45 +1762,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
{previews && previews.length > 0 && (
<Box sx={{ width: '100%', mb: 1.5, display: 'flex', alignItems: 'center' }}>
{previews.map((item, index) => (
<Fragment key={index}>
{item.mime.startsWith('image/') ? (
<ImageButton
focusRipple
style={{
width: '48px',
height: '48px',
marginRight: '10px',
flex: '0 0 auto'
}}
onClick={() => handleDeletePreview(item)}
>
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
<ImageBackdrop className='MuiImageBackdrop-root' />
<ImageMarked className='MuiImageMarked-root'>
<IconTrash size={20} color='white' />
</ImageMarked>
</ImageButton>
) : (
<Card
sx={{
display: 'inline-flex',
alignItems: 'center',
height: '48px',
width: isDialog ? ps?.current?.offsetWidth / 4 : ps?.current?.offsetWidth / 2,
p: 0.5,
mr: 1,
backgroundColor: theme.palette.grey[500],
flex: '0 0 auto'
}}
variant='outlined'
>
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
<IconTrash size={20} color='white' />
</IconButton>
</Card>
)}
</Fragment>
<Fragment key={index}>{previewDisplay(item)}</Fragment>
))}
</Box>
)}
@@ -1679,15 +1839,62 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
multiline={true}
maxRows={isDialog ? 7 : 2}
startAdornment={
isChatFlowAvailableForUploads && (
<InputAdornment position='start' sx={{ pl: 2 }}>
<IconButton onClick={handleUploadClick} type='button' disabled={getInputDisabled()} edge='start'>
<IconPhotoPlus
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
/>
</IconButton>
</InputAdornment>
)
<>
{isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads && (
<InputAdornment position='start' sx={{ ml: 2 }}>
<IconButton
onClick={handleImageUploadClick}
type='button'
disabled={getInputDisabled()}
edge='start'
>
<IconPhotoPlus
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
/>
</IconButton>
</InputAdornment>
)}
{!isChatFlowAvailableForImageUploads && isChatFlowAvailableForFileUploads && (
<InputAdornment position='start' sx={{ ml: 2 }}>
<IconButton
onClick={handleFileUploadClick}
type='button'
disabled={getInputDisabled()}
edge='start'
>
<IconPaperclip
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
/>
</IconButton>
</InputAdornment>
)}
{isChatFlowAvailableForImageUploads && isChatFlowAvailableForFileUploads && (
<InputAdornment position='start' sx={{ ml: 2 }}>
<IconButton
onClick={handleImageUploadClick}
type='button'
disabled={getInputDisabled()}
edge='start'
>
<IconPhotoPlus
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
/>
</IconButton>
<IconButton
sx={{ ml: 0 }}
onClick={handleFileUploadClick}
type='button'
disabled={getInputDisabled()}
edge='start'
>
<IconPaperclip
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
/>
</IconButton>
</InputAdornment>
)}
{!isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads && <Box sx={{ pl: 1 }} />}
</>
}
endAdornment={
<>
@@ -1707,7 +1914,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
</InputAdornment>
)}
{!isAgentCanvas && (
<InputAdornment position='end' sx={{ padding: '15px' }}>
<InputAdornment position='end' sx={{ paddingRight: '15px' }}>
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
{loading ? (
<div>
@@ -1727,7 +1934,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
{isAgentCanvas && (
<>
{!loading && (
<InputAdornment position='end' sx={{ padding: '15px' }}>
<InputAdornment position='end' sx={{ paddingRight: '15px' }}>
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
<IconSend
color={
@@ -1765,8 +1972,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
</>
}
/>
{isChatFlowAvailableForUploads && (
<input style={{ display: 'none' }} multiple ref={fileUploadRef} type='file' onChange={handleFileChange} />
{isChatFlowAvailableForImageUploads && (
<input
style={{ display: 'none' }}
multiple
ref={imgUploadRef}
type='file'
onChange={handleFileChange}
accept={imageUploadAllowedTypes || '*'}
/>
)}
{isChatFlowAvailableForFileUploads && (
<input
style={{ display: 'none' }}
multiple
ref={fileUploadRef}
type='file'
onChange={handleFileChange}
accept={fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'}
/>
)}
</form>
)}