Feature/Full File Uploads & Message Delete API (#3314)

* add functionality for full file uploads, add remove messages from view dialog and API

* add attachments swagger

* update question to include uploadedFilesContent

* make config dialog modal lg size
This commit is contained in:
Henry Heng
2024-10-23 11:00:46 +01:00
committed by GitHub
parent 116d02d0bc
commit 53e504c32f
31 changed files with 1012 additions and 193 deletions
+10
View File
@@ -0,0 +1,10 @@
import client from './client'
const createAttachment = (chatflowid, chatid, formData) =>
client.post(`/attachments/${chatflowid}/${chatid}`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
export default {
createAttachment
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -11,6 +11,7 @@ import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
import Leads from '@/ui-component/extended/Leads'
import FollowUpPrompts from '@/ui-component/extended/FollowUpPrompts'
import FileUpload from '@/ui-component/extended/FileUpload'
const CHATFLOW_CONFIGURATION_TABS = [
{
@@ -44,6 +45,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
{
label: 'Leads',
id: 'leads'
},
{
label: 'File Upload',
id: 'fileUpload'
}
]
@@ -85,7 +90,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
onClose={onCancel}
open={show}
fullWidth
maxWidth={'md'}
maxWidth={'lg'}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
@@ -127,6 +132,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
{item.id === 'fileUpload' ? <FileUpload dialogProps={dialogProps} /> : null}
</TabPanel>
))}
</DialogContent>
@@ -25,7 +25,10 @@ import {
Chip,
Card,
CardMedia,
CardContent
CardContent,
FormControlLabel,
Checkbox,
DialogActions
} from '@mui/material'
import { useTheme } from '@mui/material/styles'
import DatePicker from 'react-datepicker'
@@ -84,6 +87,52 @@ const messageImageStyle = {
objectFit: 'cover'
}
const ConfirmDeleteMessageDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
const portalElement = document.getElementById('portal')
const [hardDelete, setHardDelete] = useState(false)
const onSubmit = () => {
onConfirm(hardDelete)
}
const component = show ? (
<Dialog
fullWidth
maxWidth='xs'
open={show}
onClose={onCancel}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
{dialogProps.title}
</DialogTitle>
<DialogContent>
<span style={{ marginTop: '20px', marginBottom: '20px' }}>{dialogProps.description}</span>
<FormControlLabel
control={<Checkbox checked={hardDelete} onChange={(event) => setHardDelete(event.target.checked)} />}
label='Remove messages from 3rd party Memory Node'
/>
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
<StyledButton variant='contained' onClick={onSubmit}>
{dialogProps.confirmButtonName}
</StyledButton>
</DialogActions>
</Dialog>
) : null
return createPortal(component, portalElement)
}
ConfirmDeleteMessageDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func,
onConfirm: PropTypes.func
}
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const dispatch = useDispatch()
@@ -103,6 +152,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
const [selectedChatId, setSelectedChatId] = useState('')
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
const [sourceDialogProps, setSourceDialogProps] = useState({})
const [hardDeleteDialogOpen, setHardDeleteDialogOpen] = useState(false)
const [hardDeleteDialogProps, setHardDeleteDialogProps] = useState({})
const [chatTypeFilter, setChatTypeFilter] = useState([])
const [feedbackTypeFilter, setFeedbackTypeFilter] = useState([])
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
@@ -175,6 +226,83 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
})
}
const onDeleteMessages = () => {
setHardDeleteDialogProps({
title: 'Delete Messages',
description: 'Are you sure you want to delete messages? This action cannot be undone.',
confirmButtonName: 'Delete',
cancelButtonName: 'Cancel'
})
setHardDeleteDialogOpen(true)
}
const deleteMessages = async (hardDelete) => {
setHardDeleteDialogOpen(false)
const chatflowid = dialogProps.chatflow.id
try {
const obj = { chatflowid, isClearFromViewMessageDialog: true }
let _chatTypeFilter = chatTypeFilter
if (typeof chatTypeFilter === 'string') {
_chatTypeFilter = JSON.parse(chatTypeFilter)
}
if (_chatTypeFilter.length === 1) {
obj.chatType = _chatTypeFilter[0]
}
let _feedbackTypeFilter = feedbackTypeFilter
if (typeof feedbackTypeFilter === 'string') {
_feedbackTypeFilter = JSON.parse(feedbackTypeFilter)
}
if (_feedbackTypeFilter.length === 1) {
obj.feedbackType = _feedbackTypeFilter[0]
}
if (startDate) obj.startDate = startDate
if (endDate) obj.endDate = endDate
if (hardDelete) obj.hardDelete = true
await chatmessageApi.deleteChatmessage(chatflowid, obj)
enqueueSnackbar({
message: 'Succesfully deleted messages',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
getChatmessageApi.request(chatflowid, {
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
startDate: startDate,
endDate: endDate
})
getStatsApi.request(chatflowid, {
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
startDate: startDate,
endDate: endDate
})
} catch (error) {
console.error(error)
enqueueSnackbar({
message: typeof error.response.data === 'object' ? error.response.data.message : error.response.data,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
const exportMessages = async () => {
if (!storagePath && getStoragePathFromServer.data) {
storagePath = getStoragePathFromServer.data.storagePath
@@ -675,7 +803,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
onClose={onCancel}
open={show}
fullWidth
maxWidth={chatlogs && chatlogs.length == 0 ? 'md' : 'lg'}
maxWidth={'lg'}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
@@ -781,6 +909,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
/>
</div>
<div style={{ flex: 1 }}></div>
{stats.totalMessages > 0 && (
<Button color='error' variant='outlined' onClick={() => onDeleteMessages()} startIcon={<IconEraser />}>
Delete Messages
</Button>
)}
</div>
<div
style={{
@@ -1375,6 +1508,12 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
)}
</div>
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
<ConfirmDeleteMessageDialog
show={hardDeleteDialogOpen}
dialogProps={hardDeleteDialogProps}
onCancel={() => setHardDeleteDialogOpen(false)}
onConfirm={(hardDelete) => deleteMessages(hardDelete)}
/>
</>
</DialogContent>
</Dialog>
@@ -0,0 +1,122 @@
import { useDispatch } from 'react-redux'
import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
// material-ui
import { Button, Box, Typography } from '@mui/material'
import { IconX } from '@tabler/icons-react'
// Project import
import { StyledButton } from '@/ui-component/button/StyledButton'
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
import { SwitchInput } from '@/ui-component/switch/Switch'
// store
import useNotifier from '@/utils/useNotifier'
// API
import chatflowsApi from '@/api/chatflows'
const message = `Allow files to be uploaded from the chat. Uploaded files will be parsed as string and sent to LLM. If File Upload is enabled on Vector Store as well, this will override and takes precedence.`
const FileUpload = ({ dialogProps }) => {
const dispatch = useDispatch()
useNotifier()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const [fullFileUpload, setFullFileUpload] = useState(false)
const [chatbotConfig, setChatbotConfig] = useState({})
const handleChange = (value) => {
setFullFileUpload(value)
}
const onSave = async () => {
try {
const value = {
status: fullFileUpload
}
chatbotConfig.fullFileUpload = value
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
chatbotConfig: JSON.stringify(chatbotConfig)
})
if (saveResp.data) {
enqueueSnackbar({
message: 'File Upload Configuration Saved',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
}
} catch (error) {
enqueueSnackbar({
message: `Failed to save File Upload Configuration: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
useEffect(() => {
if (dialogProps.chatflow) {
if (dialogProps.chatflow.chatbotConfig) {
try {
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
setChatbotConfig(chatbotConfig || {})
if (chatbotConfig.fullFileUpload) {
setFullFileUpload(chatbotConfig.fullFileUpload.status)
}
} catch (e) {
setChatbotConfig({})
}
}
}
return () => {}
}, [dialogProps])
return (
<>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', mb: 2 }}>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography>
Enable Full File Upload
<TooltipWithParser style={{ marginLeft: 10 }} title={message} />
</Typography>
</div>
<SwitchInput onChange={handleChange} value={fullFileUpload} />
</Box>
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
Save
</StyledButton>
</>
)
}
FileUpload.propTypes = {
dialogProps: PropTypes.object
}
export default FileUpload
@@ -5,6 +5,7 @@ import PerfectScrollbar from 'react-perfect-scrollbar'
import robotPNG from '@/assets/images/robot.png'
import chatPNG from '@/assets/images/chathistory.png'
import diskPNG from '@/assets/images/floppy-disc.png'
import fileAttachmentPNG from '@/assets/images/fileAttachment.png'
import { baseURL } from '@/store/constant'
const sequentialStateMessagesSelection = [
@@ -119,6 +120,45 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
/>
</ListItem>
</ListItemButton>
<ListItemButton
sx={{
p: 0,
borderRadius: `${customization.borderRadius}px`,
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
mb: 1
}}
disabled={disabled}
onClick={() => onSelectOutputResponseClick(null, 'file_attachment')}
>
<ListItem alignItems='center'>
<ListItemAvatar>
<div
style={{
width: 50,
height: 50,
borderRadius: '50%',
backgroundColor: 'white'
}}
>
<img
style={{
width: '100%',
height: '100%',
padding: 10,
objectFit: 'contain'
}}
alt='fileAttachment'
src={fileAttachmentPNG}
/>
</div>
</ListItemAvatar>
<ListItemText
sx={{ ml: 1 }}
primary='file_attachment'
secondary={`Files uploaded from the chat when Full File Upload is enabled on the Configuration`}
/>
</ListItem>
</ListItemButton>
{availableNodesForVariable &&
availableNodesForVariable.length > 0 &&
availableNodesForVariable.map((node, index) => {
+126 -39
View File
@@ -67,6 +67,7 @@ import chatmessageApi from '@/api/chatmessage'
import chatflowsApi from '@/api/chatflows'
import predictionApi from '@/api/prediction'
import vectorstoreApi from '@/api/vectorstore'
import attachmentsApi from '@/api/attachments'
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
import leadsApi from '@/api/lead'
@@ -88,7 +89,7 @@ const messageImageStyle = {
objectFit: 'cover'
}
const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
const CardWithDeleteOverlay = ({ item, disabled, customization, onDelete }) => {
const [isHovered, setIsHovered] = useState(false)
const defaultBackgroundColor = customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
@@ -125,8 +126,9 @@ const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
{item.name}
</span>
</Card>
{isHovered && (
{isHovered && !disabled && (
<Button
disabled={disabled}
onClick={() => onDelete(item)}
startIcon={<IconTrash color='white' size={22} />}
title='Remove attachment'
@@ -150,6 +152,7 @@ const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
CardWithDeleteOverlay.propTypes = {
item: PropTypes.object,
customization: PropTypes.object,
disabled: PropTypes.bool,
onDelete: PropTypes.func
}
@@ -191,6 +194,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
const [starterPrompts, setStarterPrompts] = useState([])
// full file upload
const [fullFileUpload, setFullFileUpload] = useState(false)
// feedback
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
const [feedbackId, setFeedbackId] = useState('')
@@ -213,6 +219,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
const fileUploadRef = useRef(null)
const [isChatFlowAvailableForImageUploads, setIsChatFlowAvailableForImageUploads] = useState(false)
const [isChatFlowAvailableForFileUploads, setIsChatFlowAvailableForFileUploads] = useState(false)
const [isChatFlowAvailableForRAGFileUploads, setIsChatFlowAvailableForRAGFileUploads] = useState(false)
const [isDragActive, setIsDragActive] = useState(false)
// recording
@@ -235,7 +242,10 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
})
}
if (constraints.isFileUploadAllowed) {
if (fullFileUpload) {
return true
} else if (constraints.isRAGFileUploadAllowed) {
const fileExt = file.name.split('.').pop()
if (fileExt) {
constraints.fileUploadSizeAndTypes.map((allowed) => {
@@ -271,7 +281,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
const { name } = file
// Only add files
if (!imageUploadAllowedTypes.includes(file.type)) {
uploadedFiles.push(file)
uploadedFiles.push({ file, type: fullFileUpload ? 'file:full' : 'file:rag' })
}
files.push(
new Promise((resolve) => {
@@ -350,7 +360,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
// Only add files
if (!imageUploadAllowedTypes.includes(file.type)) {
uploadedFiles.push(file)
uploadedFiles.push({ file, type: fullFileUpload ? 'file:full' : 'file:rag' })
}
const reader = new FileReader()
const { name } = file
@@ -683,6 +693,66 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
}
const handleFileUploads = async (uploads) => {
if (!uploadedFiles.length) return uploads
if (fullFileUpload) {
const filesWithFullUploadType = uploadedFiles.filter((file) => file.type === 'file:full')
if (filesWithFullUploadType.length > 0) {
const formData = new FormData()
for (const file of filesWithFullUploadType) {
formData.append('files', file.file)
}
formData.append('chatId', chatId)
const response = await attachmentsApi.createAttachment(chatflowid, chatId, formData)
const data = response.data
for (const extractedFileData of data) {
const content = extractedFileData.content
const fileName = extractedFileData.name
// find matching name in previews and replace data with content
const uploadIndex = uploads.findIndex((upload) => upload.name === fileName)
if (uploadIndex !== -1) {
uploads[uploadIndex] = {
...uploads[uploadIndex],
data: content,
name: fileName,
type: 'file:full'
}
}
}
}
} else if (isChatFlowAvailableForRAGFileUploads) {
const filesWithRAGUploadType = uploadedFiles.filter((file) => file.type === 'file:rag')
if (filesWithRAGUploadType.length > 0) {
const formData = new FormData()
for (const file of filesWithRAGUploadType) {
formData.append('files', file.file)
}
formData.append('chatId', chatId)
await vectorstoreApi.upsertVectorStoreWithFormData(chatflowid, formData)
// 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
uploads = uploads.map((upload) => {
return {
...upload,
type: 'file:rag'
}
})
}
}
return uploads
}
// Handle form submission
const handleSubmit = async (e, selectedInput, action) => {
if (e) e.preventDefault()
@@ -699,7 +769,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
if (selectedInput !== undefined && selectedInput.trim() !== '') input = selectedInput
setLoading(true)
const uploads = previews.map((item) => {
let uploads = previews.map((item) => {
return {
data: item.data,
type: item.type,
@@ -707,6 +777,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
mime: item.mime
}
})
try {
uploads = await handleFileUploads(uploads)
} catch (error) {
handleError('Unable to upload documents')
return
}
clearPreviews()
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: uploads }])
@@ -718,28 +796,8 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
}
if (uploads && uploads.length > 0) params.uploads = uploads
if (leadEmail) params.leadEmail = leadEmail
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
}
}
if (isChatFlowAvailableToStream) {
fetchResponseFromEventStream(chatflowid, params)
} else {
@@ -905,6 +963,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
return ''
}
const getFileUploadAllowedTypes = () => {
if (fullFileUpload) return '*'
return fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'
}
const downloadFile = async (fileAnnotation) => {
try {
const response = await axios.post(
@@ -993,7 +1056,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
useEffect(() => {
if (getAllowChatFlowUploads.data) {
setIsChatFlowAvailableForImageUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
setIsChatFlowAvailableForFileUploads(getAllowChatFlowUploads.data?.isFileUploadAllowed ?? false)
setIsChatFlowAvailableForRAGFileUploads(getAllowChatFlowUploads.data?.isRAGFileUploadAllowed ?? false)
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
setImageUploadAllowedTypes(getAllowChatFlowUploads.data?.imgUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
setFileUploadAllowedTypes(getAllowChatFlowUploads.data?.fileUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
@@ -1035,11 +1098,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
if (config.followUpPrompts) {
setFollowUpPromptsStatus(config.followUpPrompts.status)
}
if (config.fullFileUpload) {
setFullFileUpload(config.fullFileUpload.status)
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getChatflowConfig.data])
useEffect(() => {
if (fullFileUpload) {
setIsChatFlowAvailableForFileUploads(true)
} else if (isChatFlowAvailableForRAGFileUploads) {
setIsChatFlowAvailableForFileUploads(true)
} else {
setIsChatFlowAvailableForFileUploads(false)
}
}, [isChatFlowAvailableForRAGFileUploads, fullFileUpload])
// Auto scroll chat to bottom
useEffect(() => {
scrollToBottom()
@@ -1238,6 +1315,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
marginRight: '10px',
flex: '0 0 auto'
}}
disabled={getInputDisabled()}
onClick={() => handleDeletePreview(item)}
>
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
@@ -1263,13 +1341,20 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
variant='outlined'
>
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
<IconButton disabled={getInputDisabled()} onClick={() => handleDeletePreview(item)} size='small'>
<IconTrash size={20} color='white' />
</IconButton>
</Card>
)
} else {
return <CardWithDeleteOverlay item={item} customization={customization} onDelete={() => handleDeletePreview(item)} />
return (
<CardWithDeleteOverlay
disabled={getInputDisabled()}
item={item}
customization={customization}
onDelete={() => handleDeletePreview(item)}
/>
)
}
}
@@ -1415,11 +1500,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
onDrop={handleDrop}
/>
)}
{isDragActive && (getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isFileUploadAllowed) && (
<Box className='drop-overlay'>
<Typography variant='h2'>Drop here to upload</Typography>
{[...getAllowChatFlowUploads.data.imgUploadSizeAndTypes, ...getAllowChatFlowUploads.data.fileUploadSizeAndTypes].map(
(allowed) => {
{isDragActive &&
(getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isRAGFileUploadAllowed) && (
<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>
@@ -1428,10 +1516,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
)}
</>
)
}
)}
</Box>
)}
})}
</Box>
)}
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
<div id='messagelist' className={'messagelist'}>
{messages &&
@@ -2256,7 +2343,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
ref={fileUploadRef}
type='file'
onChange={handleFileChange}
accept={fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'}
accept={getFileUploadAllowedTypes()}
/>
)}
</form>