From 9a92aa12f9464f86b19542ab43ccedb516c12d21 Mon Sep 17 00:00:00 2001 From: Vinod Kiran Date: Wed, 26 Feb 2025 23:10:03 +0530 Subject: [PATCH] New Feature: Add post postprocessing of response from LLM using custom JS Function (#4079) * New Feature: Add post postprocessing of response from LLM using custom Javascript functions * Disable Save when there is no content * add post processing ui changes, disable streaming --------- Co-authored-by: Henry --- .../CustomFunction/CustomFunction.ts | 1 + .../server/src/services/chatflows/index.ts | 11 +- packages/server/src/utils/buildChatflow.ts | 47 +++- .../dialog/ChatflowConfigurationDialog.jsx | 16 +- .../ui-component/extended/PostProcessing.jsx | 228 ++++++++++++++++++ packages/ui/src/views/canvas/CanvasHeader.jsx | 1 + 6 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 packages/ui/src/ui-component/extended/PostProcessing.jsx diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index 1bfddefca..079f186ba 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -88,6 +88,7 @@ class CustomFunction_Utilities implements INode { chatflowId: options.chatflowid, sessionId: options.sessionId, chatId: options.chatId, + rawOutput: options.rawOutput || '', input } diff --git a/packages/server/src/services/chatflows/index.ts b/packages/server/src/services/chatflows/index.ts index edabc3b29..2fc1adc18 100644 --- a/packages/server/src/services/chatflows/index.ts +++ b/packages/server/src/services/chatflows/index.ts @@ -1,4 +1,4 @@ -import { removeFolderFromStorage } from 'flowise-components' +import { ICommonObject, removeFolderFromStorage } from 'flowise-components' import { StatusCodes } from 'http-status-codes' import { ChatflowType, IReactFlowObject } from '../../Interface' import { ChatFlow } from '../../database/entities/ChatFlow' @@ -28,6 +28,15 @@ const checkIfChatflowIsValidForStreaming = async (chatflowId: string): Promise = { diff --git a/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.jsx b/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.jsx index 081f6c91e..cfa10cfbe 100644 --- a/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.jsx +++ b/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.jsx @@ -11,6 +11,7 @@ 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' +import PostProcessing from '@/ui-component/extended/PostProcessing' const CHATFLOW_CONFIGURATION_TABS = [ { @@ -44,6 +45,11 @@ const CHATFLOW_CONFIGURATION_TABS = [ { label: 'File Upload', id: 'fileUpload' + }, + { + label: 'Post Processing', + id: 'postProcessing', + hideInAgentFlow: true } ] @@ -76,10 +82,12 @@ function a11yProps(index) { } } -const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => { +const ChatflowConfigurationDialog = ({ show, isAgentCanvas, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const [tabValue, setTabValue] = useState(0) + const filteredTabs = CHATFLOW_CONFIGURATION_TABS.filter((tab) => !isAgentCanvas || !tab.hideInAgentFlow) + const component = show ? ( { variant='scrollable' scrollButtons='auto' > - {CHATFLOW_CONFIGURATION_TABS.map((item, index) => ( + {filteredTabs.map((item, index) => ( { > ))} - {CHATFLOW_CONFIGURATION_TABS.map((item, index) => ( + {filteredTabs.map((item, index) => ( {item.id === 'security' && } {item.id === 'conversationStarters' ? : null} @@ -133,6 +141,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => { {item.id === 'analyseChatflow' ? : null} {item.id === 'leads' ? : null} {item.id === 'fileUpload' ? : null} + {item.id === 'postProcessing' ? : null} ))} @@ -144,6 +153,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => { ChatflowConfigurationDialog.propTypes = { show: PropTypes.bool, + isAgentCanvas: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func } diff --git a/packages/ui/src/ui-component/extended/PostProcessing.jsx b/packages/ui/src/ui-component/extended/PostProcessing.jsx new file mode 100644 index 000000000..fd56a3eb6 --- /dev/null +++ b/packages/ui/src/ui-component/extended/PostProcessing.jsx @@ -0,0 +1,228 @@ +import { useDispatch } from 'react-redux' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' + +// material-ui +import { IconButton, Button, Box, Typography } from '@mui/material' +import { IconArrowsMaximize, IconBulb, IconX } from '@tabler/icons-react' +import { useTheme } from '@mui/material/styles' + +// Project import +import { StyledButton } from '@/ui-component/button/StyledButton' +import { SwitchInput } from '@/ui-component/switch/Switch' +import { CodeEditor } from '@/ui-component/editor/CodeEditor' +import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog' + +// store +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions' +import useNotifier from '@/utils/useNotifier' + +// API +import chatflowsApi from '@/api/chatflows' + +const sampleFunction = `return $flow.rawOutput + " This is a post processed response!";` + +const PostProcessing = ({ dialogProps }) => { + const dispatch = useDispatch() + + useNotifier() + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [postProcessingEnabled, setPostProcessingEnabled] = useState(false) + const [postProcessingFunction, setPostProcessingFunction] = useState('') + const [chatbotConfig, setChatbotConfig] = useState({}) + const [showExpandDialog, setShowExpandDialog] = useState(false) + const [expandDialogProps, setExpandDialogProps] = useState({}) + + const handleChange = (value) => { + setPostProcessingEnabled(value) + } + + const onExpandDialogClicked = (value) => { + const dialogProps = { + value, + inputParam: { + label: 'Post Processing Function', + name: 'postProcessingFunction', + type: 'code', + placeholder: sampleFunction, + hideCodeExecute: true + }, + languageType: 'js', + confirmButtonName: 'Save', + cancelButtonName: 'Cancel' + } + setExpandDialogProps(dialogProps) + setShowExpandDialog(true) + } + + const onSave = async () => { + try { + let value = { + postProcessing: { + enabled: postProcessingEnabled, + customFunction: JSON.stringify(postProcessingFunction) + } + } + chatbotConfig.postProcessing = value.postProcessing + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + chatbotConfig: JSON.stringify(chatbotConfig) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Post Processing Settings Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + } catch (error) { + enqueueSnackbar({ + message: `Failed to save Post Processing Settings: ${ + 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) => ( + + ) + } + }) + } + } + + useEffect(() => { + if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { + let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig) + setChatbotConfig(chatbotConfig || {}) + if (chatbotConfig.postProcessing) { + setPostProcessingEnabled(chatbotConfig.postProcessing.enabled) + if (chatbotConfig.postProcessing.customFunction) { + setPostProcessingFunction(JSON.parse(chatbotConfig.postProcessing.customFunction)) + } + } + } + + return () => {} + }, [dialogProps]) + + return ( + <> + + + + + + JS Function + +
+ onExpandDialogClicked(postProcessingFunction)} + > + + + + +
+ setPostProcessingFunction(code)} + basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} + /> +
+ +
+
+ + + The following variables are available to use in the custom function:{' '} +
$flow.rawOutput, $flow.input, $flow.chatflowId, $flow.sessionId, $flow.chatId
+
+
+
+ + Save + + setShowExpandDialog(false)} + onConfirm={(newValue) => { + setPostProcessingFunction(newValue) + setShowExpandDialog(false) + }} + > + + ) +} + +PostProcessing.propTypes = { + dialogProps: PropTypes.object +} + +export default PostProcessing diff --git a/packages/ui/src/views/canvas/CanvasHeader.jsx b/packages/ui/src/views/canvas/CanvasHeader.jsx index c89eb2829..9449e9753 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.jsx +++ b/packages/ui/src/views/canvas/CanvasHeader.jsx @@ -475,6 +475,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo show={chatflowConfigurationDialogOpen} dialogProps={chatflowConfigurationDialogProps} onCancel={() => setChatflowConfigurationDialogOpen(false)} + isAgentCanvas={isAgentCanvas} /> )