From 721fd551d98a0e43d451932453e6704c0ae7ace8 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Thu, 17 Apr 2025 08:01:29 +0530 Subject: [PATCH 1/8] Chatflow config - rate limit and override config were overwriting changes. --- .../ui-component/extended/OverrideConfig.jsx | 23 ++++++++----------- .../src/ui-component/extended/RateLimit.jsx | 19 ++++++++------- .../ui/src/ui-component/extended/Security.jsx | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/ui-component/extended/OverrideConfig.jsx b/packages/ui/src/ui-component/extended/OverrideConfig.jsx index 5f398fe01d6..c9d1c5a0af7 100644 --- a/packages/ui/src/ui-component/extended/OverrideConfig.jsx +++ b/packages/ui/src/ui-component/extended/OverrideConfig.jsx @@ -116,25 +116,22 @@ const OverrideConfig = ({ dialogProps }) => { } const formatObj = () => { - const obj = { - overrideConfig: { status: overrideConfigStatus } + let apiConfig = JSON.parse(dialogProps.chatflow.apiConfig) + if (apiConfig === null || apiConfig === undefined) { + apiConfig = {} } + let overrideConfig = { status: overrideConfigStatus } if (overrideConfigStatus) { - // loop through each key in nodeOverrides and filter out the enabled ones - const filteredNodeOverrides = {} - for (const key in nodeOverrides) { - filteredNodeOverrides[key] = nodeOverrides[key].filter((node) => node.enabled) - } - - obj.overrideConfig = { - ...obj.overrideConfig, - nodes: filteredNodeOverrides, - variables: variableOverrides.filter((node) => node.enabled) + overrideConfig = { + ...overrideConfig, + nodes: nodeOverrides, + variables: variableOverrides } } + apiConfig.overrideConfig = overrideConfig - return obj + return apiConfig } const onNodeOverrideToggle = (node, property, status) => { diff --git a/packages/ui/src/ui-component/extended/RateLimit.jsx b/packages/ui/src/ui-component/extended/RateLimit.jsx index c57b20e79e2..1b3ca3b0105 100644 --- a/packages/ui/src/ui-component/extended/RateLimit.jsx +++ b/packages/ui/src/ui-component/extended/RateLimit.jsx @@ -19,7 +19,7 @@ import chatflowsApi from '@/api/chatflows' // utils import useNotifier from '@/utils/useNotifier' -const RateLimit = () => { +const RateLimit = ({ dialogProps }) => { const dispatch = useDispatch() const chatflow = useSelector((state) => state.canvas.chatflow) const chatflowid = chatflow.id @@ -36,9 +36,11 @@ const RateLimit = () => { const [limitMsg, setLimitMsg] = useState(apiConfig?.rateLimit?.limitMsg ?? '') const formatObj = () => { - const obj = { - rateLimit: { status: rateLimitStatus } + let apiConfig = JSON.parse(dialogProps.chatflow.apiConfig) + if (apiConfig === null || apiConfig === undefined) { + apiConfig = {} } + let obj = { status: rateLimitStatus } if (rateLimitStatus) { const rateLimitValuesBoolean = [!limitMax, !limitDuration, !limitMsg] @@ -46,16 +48,16 @@ const RateLimit = () => { if (rateLimitFilledValues.length >= 1 && rateLimitFilledValues.length <= 2) { throw new Error('Need to fill all rate limit input fields') } else if (rateLimitFilledValues.length === 3) { - obj.rateLimit = { - ...obj.rateLimit, + obj = { + ...obj, limitMax, limitDuration, limitMsg } } } - - return obj + apiConfig.rateLimit = obj + return apiConfig } const handleChange = (value) => { @@ -173,7 +175,8 @@ const RateLimit = () => { } RateLimit.propTypes = { - isSessionMemory: PropTypes.bool + isSessionMemory: PropTypes.bool, + dialogProps: PropTypes.object } export default RateLimit diff --git a/packages/ui/src/ui-component/extended/Security.jsx b/packages/ui/src/ui-component/extended/Security.jsx index b46847fcba0..57fff04babf 100644 --- a/packages/ui/src/ui-component/extended/Security.jsx +++ b/packages/ui/src/ui-component/extended/Security.jsx @@ -12,7 +12,7 @@ const Security = ({ dialogProps }) => { return ( } spacing={4}> - + From 12626662623d61febd7c11b6eeea891d8b137bc8 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Thu, 17 Apr 2025 18:20:41 +0530 Subject: [PATCH 2/8] prevent post processing JS function from escaping as JSON... --- packages/server/src/utils/buildChatflow.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 739177ea416..7300ad734cb 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -665,13 +665,15 @@ export const executeFlow = async ({ const postProcessingFunction = JSON.parse(chatflowConfig?.postProcessing?.customFunction) const nodeInstanceFilePath = componentNodes['customFunction'].filePath as string const nodeModule = await import(nodeInstanceFilePath) + //set the outputs.output to EndingNode to prevent json escaping of content... const nodeData = { inputs: { javascriptFunction: postProcessingFunction }, - outputs: { output: 'output' } + outputs: { output: 'EndingNode' } } const options: ICommonObject = { chatflowid: chatflow.id, sessionId, + isRun: true, chatId, input: question, rawOutput: resultText, From 8844f93ff42e1bd5e21fde15f5c8f189c91c3714 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Thu, 17 Apr 2025 18:21:30 +0530 Subject: [PATCH 3/8] non-streaming follow up prompts need to be escaped twice --- .../cards/FollowUpPromptsCard.jsx | 39 ++++++++++--------- .../ui/src/views/chatmessage/ChatMessage.jsx | 14 ++++++- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/ui-component/cards/FollowUpPromptsCard.jsx b/packages/ui/src/ui-component/cards/FollowUpPromptsCard.jsx index 75aceb85b75..0ad96fb3626 100644 --- a/packages/ui/src/ui-component/cards/FollowUpPromptsCard.jsx +++ b/packages/ui/src/ui-component/cards/FollowUpPromptsCard.jsx @@ -12,25 +12,26 @@ const FollowUpPromptsCard = ({ isGrid, followUpPrompts, sx, onPromptClick }) => className={'button-container'} sx={{ width: '100%', maxWidth: isGrid ? 'inherit' : '400px', p: 1.5, display: 'flex', gap: 1, ...sx }} > - {followUpPrompts.map((fp, index) => ( - onPromptClick(fp, e)} - sx={{ - backgroundColor: 'transparent', - border: '1px solid', - boxShadow: '0px 2px 1px -1px rgba(0,0,0,0.2)', - color: '#2196f3', - transition: 'all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - '&:hover': { - backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.12)' : 'rgba(0, 0, 0, 0.05)', - border: '1px solid' - } - }} - /> - ))} + {Array.isArray(followUpPrompts) && + followUpPrompts.map((fp, index) => ( + onPromptClick(fp, e)} + sx={{ + backgroundColor: 'transparent', + border: '1px solid', + boxShadow: '0px 2px 1px -1px rgba(0,0,0,0.2)', + color: '#2196f3', + transition: 'all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + '&:hover': { + backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.12)' : 'rgba(0, 0, 0, 0.05)', + border: '1px solid' + } + }} + /> + ))} ) } diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index 687602144b7..b6739a96005 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -693,7 +693,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview if (data.followUpPrompts) { const followUpPrompts = JSON.parse(data.followUpPrompts) - setFollowUpPrompts(followUpPrompts) + if (typeof followUpPrompts === 'string') { + setFollowUpPrompts(JSON.parse(followUpPrompts)) + } else { + setFollowUpPrompts(followUpPrompts) + } } } @@ -1198,7 +1202,13 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview if (followUpPromptsStatus && messages.length > 0) { const lastMessage = messages[messages.length - 1] if (lastMessage.type === 'apiMessage' && lastMessage.followUpPrompts) { - setFollowUpPrompts(lastMessage.followUpPrompts) + if (Array.isArray(lastMessage.followUpPrompts)) { + setFollowUpPrompts(lastMessage.followUpPrompts) + } + if (typeof lastMessage.followUpPrompts === 'string') { + const followUpPrompts = JSON.parse(lastMessage.followUpPrompts) + setFollowUpPrompts(followUpPrompts) + } } else if (lastMessage.type === 'userMessage') { setFollowUpPrompts([]) } From 85ce6c5522744d6546b717fa9cf89536210ce876 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Thu, 17 Apr 2025 18:43:42 +0530 Subject: [PATCH 4/8] prevent post processing JS function from escaping as JSON... --- packages/server/src/utils/buildChatflow.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 7300ad734cb..0332cd6b128 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -14,7 +14,8 @@ import { mapMimeTypeToInputField, mapExtToInputField, getFileFromUpload, - removeSpecificFileFromUpload + removeSpecificFileFromUpload, + handleEscapeCharacters } from 'flowise-components' import { StatusCodes } from 'http-status-codes' import { @@ -668,12 +669,11 @@ export const executeFlow = async ({ //set the outputs.output to EndingNode to prevent json escaping of content... const nodeData = { inputs: { javascriptFunction: postProcessingFunction }, - outputs: { output: 'EndingNode' } + outputs: { output: 'output' } } const options: ICommonObject = { chatflowid: chatflow.id, sessionId, - isRun: true, chatId, input: question, rawOutput: resultText, @@ -683,7 +683,13 @@ export const executeFlow = async ({ } const customFuncNodeInstance = new nodeModule.nodeClass() let moderatedResponse = await customFuncNodeInstance.init(nodeData, question, options) - result.text = moderatedResponse + if (typeof moderatedResponse === 'string') { + result.text = handleEscapeCharacters(moderatedResponse, true) + } else if (typeof moderatedResponse === 'object') { + result.text = '```json\n' + JSON.stringify(moderatedResponse, null, 2) + '\n```' + } else { + result.text = moderatedResponse + } resultText = result.text } catch (e) { logger.log('[server]: Post Processing Error:', e) From 3d884b27d13ec63f57a2023a6cc82d2a9b97ea47 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Fri, 18 Apr 2025 11:45:28 +0530 Subject: [PATCH 5/8] Adding file mimetypes for full file upload... --- .../src/ui-component/extended/FileUpload.jsx | 74 ++++++++++++++++++- .../ui/src/views/chatmessage/ChatMessage.jsx | 10 ++- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/ui-component/extended/FileUpload.jsx b/packages/ui/src/ui-component/extended/FileUpload.jsx index d6fc8f02aba..34a6f3e509d 100644 --- a/packages/ui/src/ui-component/extended/FileUpload.jsx +++ b/packages/ui/src/ui-component/extended/FileUpload.jsx @@ -5,7 +5,7 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import parser from 'html-react-parser' // material-ui -import { Button, Box } from '@mui/material' +import { Button, Box, Typography } from '@mui/material' import { IconX, IconBulb } from '@tabler/icons-react' // Project import @@ -22,6 +22,18 @@ const message = `Uploaded files will be parsed as strings and sent to the LLM. I
Refer docs for more details.` +const availableFileTypes = [ + { name: 'CSS', ext: 'text/css' }, + { name: 'CSV', ext: 'text/csv' }, + { name: 'HTML', ext: 'text/html' }, + { name: 'JSON', ext: 'application/json' }, + { name: 'Markdown', ext: 'text/markdown' }, + { name: 'PDF', ext: 'application/pdf' }, + { name: 'SQL', ext: 'application/sql' }, + { name: 'Text File', ext: 'text/plain' }, + { name: 'XML', ext: 'application/xml' } +] + const FileUpload = ({ dialogProps }) => { const dispatch = useDispatch() @@ -31,16 +43,27 @@ const FileUpload = ({ dialogProps }) => { const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [fullFileUpload, setFullFileUpload] = useState(false) + const [allowedFileTypes, setAllowedFileTypes] = useState([]) const [chatbotConfig, setChatbotConfig] = useState({}) const handleChange = (value) => { setFullFileUpload(value) } + const handleAllowedFileTypesChange = (event) => { + const { checked, value } = event.target + if (checked) { + setAllowedFileTypes((prev) => [...prev, value]) + } else { + setAllowedFileTypes((prev) => prev.filter((item) => item !== value)) + } + } + const onSave = async () => { try { const value = { - status: fullFileUpload + status: fullFileUpload, + allowedUploadFileTypes: allowedFileTypes.join(',') } chatbotConfig.fullFileUpload = value @@ -82,6 +105,9 @@ const FileUpload = ({ dialogProps }) => { } useEffect(() => { + /* backward compatibility - by default, allow all */ + const allowedFileTypes = availableFileTypes.map((fileType) => fileType.ext) + setAllowedFileTypes(allowedFileTypes) if (dialogProps.chatflow) { if (dialogProps.chatflow.chatbotConfig) { try { @@ -90,6 +116,10 @@ const FileUpload = ({ dialogProps }) => { if (chatbotConfig.fullFileUpload) { setFullFileUpload(chatbotConfig.fullFileUpload.status) } + if (chatbotConfig.fullFileUpload?.allowedUploadFileTypes) { + const allowedFileTypes = chatbotConfig.fullFileUpload.allowedUploadFileTypes.split(',') + setAllowedFileTypes(allowedFileTypes) + } } catch (e) { setChatbotConfig({}) } @@ -135,8 +165,44 @@ const FileUpload = ({ dialogProps }) => { - {/* TODO: Allow selection of allowed file types*/} - + + Allow Uploads of Type +
+ {availableFileTypes.map((fileType) => ( +
+ + +
+ ))} +
+ Save diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index b6739a96005..f4a271b2c32 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -200,6 +200,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview // full file upload const [fullFileUpload, setFullFileUpload] = useState(false) + const [fullFileUploadAllowedTypes, setFullFileUploadAllowedTypes] = useState("*") // feedback const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false) @@ -985,7 +986,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview } const getFileUploadAllowedTypes = () => { - if (fullFileUpload) return '*' + if (fullFileUpload) { + console.log(fullFileUploadAllowedTypes === '' ? '*' : fullFileUploadAllowedTypes) + console.log(fullFileUploadAllowedTypes) + return fullFileUploadAllowedTypes === '' ? '*' : fullFileUploadAllowedTypes + } return fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*' } @@ -1122,6 +1127,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview if (config.fullFileUpload) { setFullFileUpload(config.fullFileUpload.status) + if (config.fullFileUpload?.allowedUploadFileTypes) { + setFullFileUploadAllowedTypes(config.fullFileUpload?.allowedUploadFileTypes) + } } } } From 7ab61742d83de4b048f28d65e1ec886dc708eae1 Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Fri, 18 Apr 2025 14:00:11 +0530 Subject: [PATCH 6/8] lint.. --- packages/ui/src/views/chatmessage/ChatMessage.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index f4a271b2c32..11355599b57 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -200,7 +200,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview // full file upload const [fullFileUpload, setFullFileUpload] = useState(false) - const [fullFileUploadAllowedTypes, setFullFileUploadAllowedTypes] = useState("*") + const [fullFileUploadAllowedTypes, setFullFileUploadAllowedTypes] = useState('*') // feedback const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false) @@ -987,8 +987,6 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview const getFileUploadAllowedTypes = () => { if (fullFileUpload) { - console.log(fullFileUploadAllowedTypes === '' ? '*' : fullFileUploadAllowedTypes) - console.log(fullFileUploadAllowedTypes) return fullFileUploadAllowedTypes === '' ? '*' : fullFileUploadAllowedTypes } return fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*' From ceda18d398b39350cfc57951ffcd1fafeb1b7b6a Mon Sep 17 00:00:00 2001 From: Vinod Paidimarry Date: Tue, 22 Apr 2025 22:35:31 +0530 Subject: [PATCH 7/8] fixing the issue with storing only filtered nodes.. --- .../ui/src/ui-component/extended/OverrideConfig.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/ui-component/extended/OverrideConfig.jsx b/packages/ui/src/ui-component/extended/OverrideConfig.jsx index c9d1c5a0af7..f3c6041dfc7 100644 --- a/packages/ui/src/ui-component/extended/OverrideConfig.jsx +++ b/packages/ui/src/ui-component/extended/OverrideConfig.jsx @@ -123,10 +123,15 @@ const OverrideConfig = ({ dialogProps }) => { let overrideConfig = { status: overrideConfigStatus } if (overrideConfigStatus) { + const filteredNodeOverrides = {} + for (const key in nodeOverrides) { + filteredNodeOverrides[key] = nodeOverrides[key].filter((node) => node.enabled) + } + overrideConfig = { ...overrideConfig, - nodes: nodeOverrides, - variables: variableOverrides + nodes: filteredNodeOverrides, + variables: variableOverrides.filter((node) => node.enabled) } } apiConfig.overrideConfig = overrideConfig @@ -203,7 +208,7 @@ const OverrideConfig = ({ dialogProps }) => { if (!overrideConfigStatus) { setNodeOverrides(newNodeOverrides) } else { - const updatedNodeOverrides = { ...nodeOverrides } + const updatedNodeOverrides = { ...newNodeOverrides } Object.keys(updatedNodeOverrides).forEach((node) => { if (!seenNodes.has(node)) { From 5de887730d379cfd24ff3ace94771e96d42e2cfe Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 23 Apr 2025 22:43:15 +0800 Subject: [PATCH 8/8] return doc store processing response without await when queue mode and request is from UI --- packages/server/src/controllers/documentstore/index.ts | 3 ++- packages/server/src/services/documentstore/index.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/src/controllers/documentstore/index.ts b/packages/server/src/controllers/documentstore/index.ts index a3385c177e9..36b1402e1d8 100644 --- a/packages/server/src/controllers/documentstore/index.ts +++ b/packages/server/src/controllers/documentstore/index.ts @@ -201,7 +201,8 @@ const processLoader = async (req: Request, res: Response, next: NextFunction) => } const docLoaderId = req.params.loaderId const body = req.body - const apiResponse = await documentStoreService.processLoaderMiddleware(body, docLoaderId) + const isInternalRequest = req.headers['x-request-from'] === 'internal' + const apiResponse = await documentStoreService.processLoaderMiddleware(body, docLoaderId, isInternalRequest) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts index 9826af68bb8..adea69baed6 100644 --- a/packages/server/src/services/documentstore/index.ts +++ b/packages/server/src/services/documentstore/index.ts @@ -740,7 +740,7 @@ export const processLoader = async ({ appDataSource, componentNodes, data, docLo return getDocumentStoreFileChunks(appDataSource, data.storeId as string, docLoaderId) } -const processLoaderMiddleware = async (data: IDocumentStoreLoaderForPreview, docLoaderId: string) => { +const processLoaderMiddleware = async (data: IDocumentStoreLoaderForPreview, docLoaderId: string, isInternalRequest = false) => { try { const appServer = getRunningExpressApp() const appDataSource = appServer.AppDataSource @@ -761,6 +761,12 @@ const processLoaderMiddleware = async (data: IDocumentStoreLoaderForPreview, doc const job = await upsertQueue.addJob(omit(executeData, OMIT_QUEUE_JOB_DATA)) logger.debug(`[server]: Job added to queue: ${job.id}`) + if (isInternalRequest) { + return { + jobId: job.id + } + } + const queueEvents = upsertQueue.getQueueEvents() const result = await job.waitUntilFinished(queueEvents)