mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 23:01:09 +03:00
UI touchup
This commit is contained in:
@@ -214,3 +214,8 @@ export interface ICredentialReqBody {
|
||||
export interface ICredentialReturnResponse extends ICredential {
|
||||
plainDataObj: ICredentialDataDecrypted
|
||||
}
|
||||
|
||||
export interface IUploadFileSizeAndTypes {
|
||||
fileTypes: string[]
|
||||
maxUploadSize: number
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
chatType,
|
||||
IChatMessage,
|
||||
IDepthQueue,
|
||||
INodeDirectedGraph
|
||||
INodeDirectedGraph,
|
||||
IUploadFileSizeAndTypes
|
||||
} from './Interface'
|
||||
import {
|
||||
getNodeModulesPackagePath,
|
||||
@@ -57,7 +58,7 @@ import { Tool } from './database/entities/Tool'
|
||||
import { Assistant } from './database/entities/Assistant'
|
||||
import { ChatflowPool } from './ChatflowPool'
|
||||
import { CachePool } from './CachePool'
|
||||
import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters } from 'flowise-components'
|
||||
import { ICommonObject, IMessage, INodeOptionsValue, INodeParams, handleEscapeCharacters } from 'flowise-components'
|
||||
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
|
||||
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey'
|
||||
import { sanitizeMiddleware } from './utils/XSS'
|
||||
@@ -147,7 +148,9 @@ export class App {
|
||||
'/api/v1/node-icon/',
|
||||
'/api/v1/components-credentials-icon/',
|
||||
'/api/v1/chatflows-streaming',
|
||||
'/api/v1/chatflows-uploads',
|
||||
'/api/v1/openai-assistants-file',
|
||||
'/api/v1/get-upload-file',
|
||||
'/api/v1/ip'
|
||||
]
|
||||
this.app.use((req, res, next) => {
|
||||
@@ -464,8 +467,45 @@ export class App {
|
||||
})
|
||||
if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||
|
||||
const obj = this.shouldAllowUploads(chatflow)
|
||||
return res.json(obj)
|
||||
const uploadAllowedNodes = ['OpenAIMultiModalChain', 'OpenAIWhisper']
|
||||
|
||||
try {
|
||||
const flowObj = JSON.parse(chatflow.flowData)
|
||||
let isUploadAllowed = false
|
||||
const allowances: IUploadFileSizeAndTypes[] = []
|
||||
|
||||
flowObj.nodes.forEach((node: IReactFlowNode) => {
|
||||
if (uploadAllowedNodes.indexOf(node.data.type) > -1) {
|
||||
logger.debug(`[server]: Found Eligible Node ${node.data.type}, Allowing Uploads.`)
|
||||
isUploadAllowed = true
|
||||
|
||||
const allowance: IUploadFileSizeAndTypes = {
|
||||
fileTypes: [],
|
||||
maxUploadSize: 0
|
||||
}
|
||||
|
||||
node.data.inputParams.map((param: INodeParams) => {
|
||||
if (param.name === 'allowedUploadTypes') {
|
||||
allowance.fileTypes = (param.default as string).split(';')
|
||||
}
|
||||
if (param.name === 'maxUploadSize') {
|
||||
allowance.maxUploadSize = parseInt(param.default ? (param.default as string) : '0')
|
||||
}
|
||||
})
|
||||
|
||||
if (allowance.fileTypes && allowance.maxUploadSize) {
|
||||
allowances.push(allowance)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return res.json({
|
||||
isUploadAllowed,
|
||||
uploadFileSizeAndTypes: allowances
|
||||
})
|
||||
} catch (e) {
|
||||
return res.status(500).send(e)
|
||||
}
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
@@ -1058,10 +1098,14 @@ export class App {
|
||||
return res.status(500).send(`Invalid file path`)
|
||||
}
|
||||
const filePath = path.join(getUserHome(), '.flowise', 'gptvision', req.query.chatId as string, req.params.id)
|
||||
console.log(filePath)
|
||||
if (!path.isAbsolute(filePath) || !fs.existsSync(filePath)) {
|
||||
//raise error if file path is not absolute
|
||||
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
|
||||
//raise error if file path contains '..'
|
||||
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
|
||||
//only return from the .flowise gptvision folder
|
||||
if (!(filePath.includes('.flowise') && filePath.includes('gptvision') && filePath.includes(req.query.chatId as string)))
|
||||
return res.status(500).send(`Invalid file path`)
|
||||
}
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=' + path.basename(filePath))
|
||||
streamFileToUser(res, filePath)
|
||||
})
|
||||
|
||||
@@ -1350,35 +1394,6 @@ export class App {
|
||||
})
|
||||
}
|
||||
|
||||
private uploadAllowedNodes = ['OpenAIMultiModalChain', 'OpenAIWhisper']
|
||||
private shouldAllowUploads(result: ChatFlow): any {
|
||||
const flowObj = JSON.parse(result.flowData)
|
||||
let allowUploads = false
|
||||
const allowances: any = []
|
||||
flowObj.nodes.forEach((node: IReactFlowNode) => {
|
||||
if (this.uploadAllowedNodes.indexOf(node.data.type) > -1) {
|
||||
logger.debug(`[server]: Found Eligible Node ${node.data.type}, Allowing Uploads.`)
|
||||
allowUploads = true
|
||||
const allowance: any = {}
|
||||
node.data.inputParams.map((param: any) => {
|
||||
if (param.name === 'allowedUploadTypes') {
|
||||
allowance.allowedTypes = param.default.split(';')
|
||||
}
|
||||
if (param.name === 'maxUploadSize') {
|
||||
allowance.maxUploadSize = parseInt(param.default ? param.default : '0')
|
||||
}
|
||||
})
|
||||
if (allowance.allowedTypes && allowance.maxUploadSize) {
|
||||
allowances.push(allowance)
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
allowUploads,
|
||||
allowed: allowances
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate API Key
|
||||
* @param {Request} req
|
||||
|
||||
@@ -13,6 +13,7 @@ const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body)
|
||||
const deleteChatflow = (id) => client.delete(`/chatflows/${id}`)
|
||||
|
||||
const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`)
|
||||
|
||||
const getAllowChatflowUploads = (id) => client.get(`/chatflows-uploads/${id}`)
|
||||
|
||||
export default {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { styled } from '@mui/material/styles'
|
||||
import ButtonBase from '@mui/material/ButtonBase'
|
||||
|
||||
export const ImageButton = styled(ButtonBase)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
height: 200,
|
||||
borderRadius: '10px',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '100% !important', // Overrides inline-style
|
||||
height: 100
|
||||
},
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
zIndex: 1,
|
||||
'& .MuiImageBackdrop-root': {
|
||||
opacity: 0.4
|
||||
},
|
||||
'& .MuiImageMarked-root': {
|
||||
opacity: 1
|
||||
},
|
||||
'& .MuiTypography-root': {
|
||||
border: '4px solid currentColor'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
export const ImageSrc = styled('span')({
|
||||
position: 'absolute',
|
||||
borderRadius: '10px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center 40%'
|
||||
})
|
||||
|
||||
export const ImageBackdrop = styled('span')(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
borderRadius: '10px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.common.black,
|
||||
opacity: 0.1,
|
||||
transition: theme.transitions.create('opacity')
|
||||
}))
|
||||
|
||||
export const ImageMarked = styled('span')(() => ({
|
||||
height: 25,
|
||||
width: 25,
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute',
|
||||
top: 'auto',
|
||||
left: 'auto',
|
||||
opacity: 0
|
||||
}))
|
||||
@@ -1,6 +1,5 @@
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
|
||||
import { Chip } from '@mui/material'
|
||||
import './StarterPromptsCard.css'
|
||||
|
||||
const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => {
|
||||
const StarterPromptsCard = ({ isGrid, starterPrompts, sx, onPromptClick }) => {
|
||||
return (
|
||||
<Box className={'button-container'} sx={{ maxWidth: isGrid ? 'inherit' : '400px', m: 1 }}>
|
||||
<Box className={'button-container'} sx={{ bottom: 0, maxWidth: isGrid ? 'inherit' : '400px', m: 1, ...sx }}>
|
||||
{starterPrompts.map((sp, index) => (
|
||||
<Chip label={sp.prompt} className={'button'} key={index} onClick={(e) => onPromptClick(sp.prompt, e)} />
|
||||
))}
|
||||
@@ -15,7 +15,8 @@ const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => {
|
||||
|
||||
StarterPromptsCard.propTypes = {
|
||||
isGrid: PropTypes.bool,
|
||||
starterPrompts: PropTypes.arrayOf(PropTypes.string),
|
||||
starterPrompts: PropTypes.array,
|
||||
sx: PropTypes.object,
|
||||
onPromptClick: PropTypes.func
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,16 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */
|
||||
scrollbar-width: none; /* For Firefox */
|
||||
}
|
||||
|
||||
.file-drop-field {
|
||||
position: relative; /* Needed to position the icon correctly */
|
||||
/* Other styling for the field */
|
||||
@@ -162,26 +172,6 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10; /* Ensure it's above other content */
|
||||
z-index: 2000; /* Ensure it's above other content */
|
||||
border: 2px dashed #0094ff; /* Example style */
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
border: 2px solid #E7EDF3;
|
||||
border-radius: 16%;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.preview-card&:hover {
|
||||
border-color: #5B9FED;
|
||||
}
|
||||
|
||||
|
||||
.button {
|
||||
flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */
|
||||
margin: 5px; /* Adjust as needed for spacing between buttons */
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useState, useRef, useEffect, useCallback } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import socketIOClient from 'socket.io-client'
|
||||
@@ -8,30 +8,33 @@ import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
import axios from 'axios'
|
||||
import audioUploadSVG from 'assets/images/wave-sound.jpg'
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardMedia,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
OutlinedInput,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconCircleDot } from '@tabler/icons'
|
||||
import { IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconCircleDot, IconTrash } from '@tabler/icons'
|
||||
import robotPNG from 'assets/images/robot.png'
|
||||
import userPNG from 'assets/images/account.png'
|
||||
import audioUploadSVG from 'assets/images/wave-sound.jpg'
|
||||
|
||||
// project import
|
||||
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||
import SourceDocDialog from 'ui-component/dialog/SourceDocDialog'
|
||||
import StarterPromptsCard from 'ui-component/cards/StarterPromptsCard'
|
||||
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||
import { ImageButton, ImageSrc, ImageBackdrop, ImageMarked } from 'ui-component/button/ImageButton'
|
||||
import './ChatMessage.css'
|
||||
import './audio-recording.css'
|
||||
|
||||
@@ -46,12 +49,14 @@ import useApi from 'hooks/useApi'
|
||||
// Const
|
||||
import { baseURL, maxScroll } from 'store/constant'
|
||||
|
||||
import robotPNG from 'assets/images/robot.png'
|
||||
import userPNG from 'assets/images/account.png'
|
||||
import StarterPromptsCard from '../../ui-component/cards/StarterPromptsCard'
|
||||
// Utils
|
||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
||||
import DeleteIcon from '@mui/icons-material/Delete'
|
||||
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||
|
||||
const messageImageStyle = {
|
||||
width: '128px',
|
||||
height: '128px',
|
||||
objectFit: 'cover'
|
||||
}
|
||||
|
||||
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const theme = useTheme()
|
||||
@@ -76,13 +81,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const inputRef = useRef(null)
|
||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||
const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads)
|
||||
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
||||
|
||||
const [starterPrompts, setStarterPrompts] = useState([])
|
||||
|
||||
// drag & drop and file input
|
||||
const fileUploadRef = useRef(null)
|
||||
const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads)
|
||||
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
||||
const [previews, setPreviews] = useState([])
|
||||
const [isDragOver, setIsDragOver] = useState(false)
|
||||
@@ -91,20 +96,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [recordingNotSupported, setRecordingNotSupported] = useState(false)
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (!isChatFlowAvailableForUploads) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
const isFileAllowedForUpload = (file) => {
|
||||
const constraints = getAllowChatFlowUploads.data
|
||||
/**
|
||||
* {isUploadAllowed: boolean, uploadFileSizeAndTypes: Array<{ fileTypes: string[], maxUploadSize: number }>}
|
||||
*/
|
||||
let acceptFile = false
|
||||
if (constraints.allowUploads) {
|
||||
if (constraints.isUploadAllowed) {
|
||||
const fileType = file.type
|
||||
const sizeInMB = file.size / 1024 / 1024
|
||||
constraints.allowed.map((allowed) => {
|
||||
if (allowed.allowedTypes.includes(fileType) && sizeInMB <= allowed.maxUploadSize) {
|
||||
constraints.uploadFileSizeAndTypes.map((allowed) => {
|
||||
if (allowed.fileTypes.includes(fileType) && sizeInMB <= allowed.maxUploadSize) {
|
||||
acceptFile = true
|
||||
}
|
||||
})
|
||||
@@ -114,11 +116,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}
|
||||
return acceptFile
|
||||
}
|
||||
|
||||
const handleDrop = async (e) => {
|
||||
if (!isChatFlowAvailableForUploads) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragOver(false)
|
||||
let files = []
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
@@ -156,10 +160,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
|
||||
const newFiles = await Promise.all(files)
|
||||
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
|
||||
// if (newFiles.length > 0) {
|
||||
// document.getElementById('messagelist').style.height = '80%'
|
||||
// }
|
||||
}
|
||||
|
||||
if (e.dataTransfer.items) {
|
||||
for (const item of e.dataTransfer.items) {
|
||||
if (item.kind === 'string' && item.type.match('^text/uri-list')) {
|
||||
@@ -191,6 +193,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = async (event) => {
|
||||
const fileObj = event.target.files && event.target.files[0]
|
||||
if (!fileObj) {
|
||||
@@ -247,9 +250,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
if (isChatFlowAvailableForUploads) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragOver(true)
|
||||
}
|
||||
}
|
||||
@@ -257,34 +266,27 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const handleDragLeave = (e) => {
|
||||
if (isChatFlowAvailableForUploads) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (e.originalEvent?.pageX !== 0 || e.originalEvent?.pageY !== 0) {
|
||||
setIsDragOver(false)
|
||||
return false
|
||||
}
|
||||
setIsDragOver(false) // Set the drag over state to false when the drag leaves
|
||||
setIsDragOver(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeletePreview = (itemToDelete) => {
|
||||
if (itemToDelete.type === 'file') {
|
||||
URL.revokeObjectURL(itemToDelete.preview) // Clean up for file
|
||||
}
|
||||
setPreviews(previews.filter((item) => item !== itemToDelete))
|
||||
}
|
||||
|
||||
const handleUploadClick = () => {
|
||||
// 👇️ open file input box on click of another element
|
||||
fileUploadRef.current.click()
|
||||
}
|
||||
|
||||
const previewStyle = {
|
||||
width: '128px',
|
||||
height: '64px',
|
||||
objectFit: 'fit' // This makes the image cover the area, cropping it if necessary
|
||||
}
|
||||
const messageImageStyle = {
|
||||
width: '128px',
|
||||
height: '128px',
|
||||
objectFit: 'cover' // This makes the image cover the area, cropping it if necessary
|
||||
}
|
||||
|
||||
const clearPreviews = () => {
|
||||
// Revoke the data uris to avoid memory leaks
|
||||
previews.forEach((file) => URL.revokeObjectURL(file.preview))
|
||||
@@ -295,11 +297,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
setIsRecording(true)
|
||||
startAudioRecording(setIsRecording, setRecordingNotSupported)
|
||||
}
|
||||
|
||||
const onRecordingCancelled = () => {
|
||||
cancelAudioRecording()
|
||||
setIsRecording(false)
|
||||
setRecordingNotSupported(false)
|
||||
}
|
||||
|
||||
const onRecordingStopped = () => {
|
||||
stopAudioRecording(addRecordingToPreviews)
|
||||
setIsRecording(false)
|
||||
@@ -505,7 +509,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
// Get chatflow uploads capability
|
||||
useEffect(() => {
|
||||
if (getAllowChatFlowUploads.data) {
|
||||
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.allowUploads ?? false)
|
||||
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isUploadAllowed ?? false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllowChatFlowUploads.data])
|
||||
@@ -544,12 +548,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
useEffect(() => {
|
||||
let socket
|
||||
if (open && chatflowid) {
|
||||
// API request
|
||||
getChatmessageApi.request(chatflowid)
|
||||
getIsChatflowStreamingApi.request(chatflowid)
|
||||
getAllowChatFlowUploads.request(chatflowid)
|
||||
getChatflowConfig.request(chatflowid)
|
||||
|
||||
// Scroll to bottom
|
||||
scrollToBottom()
|
||||
|
||||
setIsRecording(false)
|
||||
|
||||
// SocketIO
|
||||
socket = socketIOClient(baseURL)
|
||||
|
||||
socket.on('connect', () => {
|
||||
@@ -584,20 +594,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}, [open, chatflowid])
|
||||
|
||||
return (
|
||||
<div
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
className={`file-drop-field`}
|
||||
>
|
||||
{isDragOver && getAllowChatFlowUploads.data?.allowUploads && (
|
||||
<>
|
||||
{isDragOver && getAllowChatFlowUploads.data?.isUploadAllowed && (
|
||||
<Box className='drop-overlay'>
|
||||
<Typography variant='h2'>Drop here to upload</Typography>
|
||||
{getAllowChatFlowUploads.data.allowed.map((allowed) => {
|
||||
{getAllowChatFlowUploads.data.uploadFileSizeAndTypes.map((allowed) => {
|
||||
return (
|
||||
<>
|
||||
<Typography variant='subtitle1'>{allowed.allowedTypes?.join(', ')}</Typography>
|
||||
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
|
||||
<Typography variant='subtitle1'>Max Allowed Size: {allowed.maxUploadSize} MB</Typography>
|
||||
</>
|
||||
)
|
||||
@@ -639,7 +643,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<div className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
||||
<div
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}
|
||||
>
|
||||
<div ref={ps} id='messagelist' className={'messagelist'}>
|
||||
{messages &&
|
||||
messages.map((message, index) => {
|
||||
@@ -687,6 +697,42 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
})}
|
||||
</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 <audio> tag.
|
||||
<source src={item.data} type={item.mime} />
|
||||
</audio>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
@@ -732,30 +778,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{message.fileUploads &&
|
||||
message.fileUploads.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
{item.mime.startsWith('image/') ? (
|
||||
<Card key={index} sx={{ maxWidth: 128, margin: 5 }}>
|
||||
<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 <audio> tag.
|
||||
<source src={item.data} type={item.mime} />
|
||||
</audio>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
{message.sourceDocuments && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
{removeDuplicateURL(message).map((source, index) => {
|
||||
@@ -796,55 +818,79 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{messages && messages.length === 1 && (
|
||||
<StarterPromptsCard starterPrompts={starterPrompts || []} onPromptClick={handlePromptClick} isGrid={isDialog} />
|
||||
<StarterPromptsCard
|
||||
sx={{ bottom: previews && previews.length > 0 ? 70 : 0 }}
|
||||
starterPrompts={starterPrompts || []}
|
||||
onPromptClick={handlePromptClick}
|
||||
isGrid={isDialog}
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
</div>
|
||||
<Divider />
|
||||
<div>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{previews && previews.length > 0 && (
|
||||
<div className='flex-col flex'>
|
||||
<div className='flex justify-between items-center h-[80px] px-1 py-1'>
|
||||
<Grid container spacing={2} sx={{ mt: '2px' }}>
|
||||
{previews.map((item, index) => (
|
||||
<>
|
||||
{item.mime.startsWith('image/') ? (
|
||||
<Grid item xs={6} sm={3} md={3} lg={3} key={index}>
|
||||
<Card key={index} sx={{ maxWidth: 128 }} variant='outlined'>
|
||||
<CardMedia style={previewStyle} component='img' image={item.data} alt={'preview'} />
|
||||
<CardActions className='center'>
|
||||
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item xs={12} sm={6} md={6} lg={6} key={index}>
|
||||
<Card key={index} variant='outlined'>
|
||||
<CardMedia component='audio' sx={{ h: 68 }} controls src={item.data} />
|
||||
<CardActions className='center'>
|
||||
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
<Box className={'preview'} sx={{ maxWidth: isDialog ? 'inherit' : '400px', m: 1, pb: 0.5 }}>
|
||||
{previews.map((item, index) => (
|
||||
<>
|
||||
{item.mime.startsWith('image/') ? (
|
||||
<ImageButton
|
||||
focusRipple
|
||||
key={index}
|
||||
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: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '48px',
|
||||
width: isDialog ? ps?.current?.offsetWidth / 4 : ps?.current?.offsetWidth / 2,
|
||||
pl: 0.5,
|
||||
mr: 1,
|
||||
backgroundColor: theme.palette.grey[500],
|
||||
flex: '0 0 auto'
|
||||
}}
|
||||
key={index}
|
||||
variant='outlined'
|
||||
>
|
||||
<CardMedia
|
||||
component='audio'
|
||||
sx={{ height: '40px', color: 'transparent' }}
|
||||
controls
|
||||
src={item.data}
|
||||
/>
|
||||
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
||||
<IconTrash size={20} color='white' />
|
||||
</IconButton>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<div className='center'>
|
||||
<div style={{ width: '100%' }}>
|
||||
<form style={{ width: '100%' }} onSubmit={handleSubmit}>
|
||||
<OutlinedInput
|
||||
inputRef={inputRef}
|
||||
// eslint-disable-next-line
|
||||
autoFocus
|
||||
autoFocus
|
||||
sx={{ width: '100%' }}
|
||||
disabled={loading || !chatflowid}
|
||||
onKeyDown={handleEnter}
|
||||
@@ -857,7 +903,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
maxRows={isDialog ? 7 : 2}
|
||||
startAdornment={
|
||||
isChatFlowAvailableForUploads && (
|
||||
<InputAdornment position='start' sx={{ padding: '15px' }}>
|
||||
<InputAdornment position='start' sx={{ pl: 2 }}>
|
||||
<IconButton
|
||||
onClick={handleUploadClick}
|
||||
type='button'
|
||||
@@ -910,13 +956,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}
|
||||
/>
|
||||
{isChatFlowAvailableForUploads && (
|
||||
<input style={{ display: 'none' }} ref={fileUploadRef} type='file' onChange={handleFileChange} />
|
||||
<input style={{ display: 'none' }} multiple ref={fileUploadRef} type='file' onChange={handleFileChange} />
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,30 +68,39 @@ export function startAudioRecording(onRecordingStart, onUnsupportedBrowser) {
|
||||
//Error handling structure
|
||||
switch (error.name) {
|
||||
case 'AbortError': //error from navigator.mediaDevices.getUserMedia
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An AbortError has occurred.')
|
||||
break
|
||||
case 'NotAllowedError': //error from navigator.mediaDevices.getUserMedia
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('A NotAllowedError has occurred. User might have denied permission.')
|
||||
break
|
||||
case 'NotFoundError': //error from navigator.mediaDevices.getUserMedia
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('A NotFoundError has occurred.')
|
||||
break
|
||||
case 'NotReadableError': //error from navigator.mediaDevices.getUserMedia
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('A NotReadableError has occurred.')
|
||||
break
|
||||
case 'SecurityError': //error from navigator.mediaDevices.getUserMedia or from the MediaRecorder.start
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('A SecurityError has occurred.')
|
||||
break
|
||||
case 'TypeError': //error from navigator.mediaDevices.getUserMedia
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('A TypeError has occurred.')
|
||||
break
|
||||
case 'InvalidStateError': //error from the MediaRecorder.start
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An InvalidStateError has occurred.')
|
||||
break
|
||||
case 'UnknownError': //error from the MediaRecorder.start
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An UnknownError has occurred.')
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An error occurred with the error name ' + error.name)
|
||||
}
|
||||
})
|
||||
@@ -113,9 +122,11 @@ export function stopAudioRecording(addRecordingToPreviews) {
|
||||
//Error handling structure
|
||||
switch (error.name) {
|
||||
case 'InvalidStateError': //error from the MediaRecorder.stop
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An InvalidStateError has occurred.')
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('An error occurred with the error name ' + error.name)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user