mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +03:00
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:
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user