diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 49d61036c..8feb42727 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -157,6 +157,7 @@ export interface IActiveChatflows { export interface IOverrideConfig { node: string + nodeId: string label: string name: string type: string diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2a68be47b..245f0b9c5 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -445,6 +445,14 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: const getParamValues = (paramsObj: ICommonObject) => { for (const config in overrideConfig) { + // If overrideConfig[key] is object + if (overrideConfig[config] && typeof overrideConfig[config] === 'object') { + const nodeIds = Object.keys(overrideConfig[config]) + if (!nodeIds.includes(flowNodeData.id)) continue + else paramsObj[config] = overrideConfig[config][flowNodeData.id] + continue + } + let paramValue = overrideConfig[config] ?? paramsObj[config] // Check if boolean if (paramValue === 'true') paramValue = true @@ -695,6 +703,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component if (inputParam.type === 'file') { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: 'files', type: inputParam.fileType ?? inputParam.type @@ -702,6 +711,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component } else if (inputParam.type === 'options') { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: inputParam.name, type: inputParam.options @@ -720,6 +730,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component for (const input of inputs) { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: input.label, name: input.name, type: input.type === 'password' ? 'string' : input.type @@ -732,6 +743,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component } else { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: inputParam.name, type: inputParam.type === 'password' ? 'string' : inputParam.type diff --git a/packages/ui/src/ui-component/table/Table.js b/packages/ui/src/ui-component/table/Table.js index a6ab312e1..2cf391827 100644 --- a/packages/ui/src/ui-component/table/Table.js +++ b/packages/ui/src/ui-component/table/Table.js @@ -16,9 +16,11 @@ export const TableViewOnly = ({ columns, rows }) => { {rows.map((row, index) => ( - {Object.keys(row).map((key, index) => ( - {row[key]} - ))} + {Object.keys(row) + .slice(-3) + .map((key, index) => ( + {row[key]} + ))} ))} diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 6ca605cd3..0ae8a8f4e 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -4,8 +4,20 @@ import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import PropTypes from 'prop-types' -import { Tabs, Tab, Dialog, DialogContent, DialogTitle, Box } from '@mui/material' +import { + Tabs, + Tab, + Dialog, + DialogContent, + DialogTitle, + Box, + Accordion, + AccordionSummary, + AccordionDetails, + Typography +} from '@mui/material' import { CopyBlock, atomOneDark } from 'react-code-blocks' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // Project import import { Dropdown } from 'ui-component/dropdown/Dropdown' @@ -33,6 +45,8 @@ import useApi from 'hooks/useApi' import { CheckboxInput } from 'ui-component/checkbox/Checkbox' import { TableViewOnly } from 'ui-component/table/Table' +import { IconBulb } from '@tabler/icons' + function TabPanel(props) { const { children, value, index, ...other } = props return ( @@ -82,7 +96,7 @@ const getConfigExamplesForJS = (configData, bodyType) => { else if (config.type === 'number') exampleVal = `1` else if (config.name === 'files') exampleVal = `input.files[0]` finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n` - if (i === loop - 1 && bodyType !== 'json') `formData.append("question", "Hey, how are you?")\n` + if (i === loop - 1 && bodyType !== 'json') finalStr += `formData.append("question", "Hey, how are you?")\n` } return finalStr } @@ -134,6 +148,8 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) + const [nodeConfig, setNodeConfig] = useState({}) + const [nodeConfigExpanded, setNodeConfigExpanded] = useState({}) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -160,12 +176,36 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { updateChatflowApi.request(dialogProps.chatflowid, updateBody) } + const groupByNodeLabel = (nodes, isFilter = false) => { + const accordianNodes = {} + const result = nodes.reduce(function (r, a) { + r[a.node] = r[a.node] || [] + r[a.node].push(a) + accordianNodes[a.node] = isFilter ? true : false + return r + }, Object.create(null)) + setNodeConfig(result) + setNodeConfigExpanded(accordianNodes) + } + + const handleAccordionChange = (nodeLabel) => (event, isExpanded) => { + const accordianNodes = { ...nodeConfigExpanded } + accordianNodes[nodeLabel] = isExpanded + setNodeConfigExpanded(accordianNodes) + } + useEffect(() => { if (updateChatflowApi.data) { dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) } }, [updateChatflowApi.data, dispatch]) + useEffect(() => { + if (getConfigApi.data) { + groupByNodeLabel(getConfigApi.data) + } + }, [getConfigApi.data]) + const handleChange = (event, newValue) => { setValue(newValue) } @@ -493,6 +533,32 @@ query({ return '' } + const getMultiConfigCodeWithFormData = (codeLang) => { + if (codeLang === 'Python') { + return `body_data = { + "openAIApiKey[chatOpenAI_0]": "sk-my-openai-1st-key", + "openAIApiKey[openAIEmbeddings_0]": "sk-my-openai-2nd-key" +}` + } else if (codeLang === 'JavaScript') { + return `formData.append("openAIApiKey[chatOpenAI_0]", "sk-my-openai-1st-key") +formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` + } else if (codeLang === 'cURL') { + return `-F "openAIApiKey[chatOpenAI_0]=sk-my-openai-1st-key" \\ +-F "openAIApiKey[openAIEmbeddings_0]=sk-my-openai-2nd-key" \\` + } + } + + const getMultiConfigCode = () => { + return `{ + "overrideConfig": { + "openAIApiKey": { + "chatOpenAI_0": "sk-my-openai-1st-key", + "openAIEmbeddings_0": "sk-my-openai-2nd-key" + } + } +}` + } + useEffect(() => { if (getAllAPIKeysApi.data) { const options = [ @@ -593,7 +659,49 @@ query({ {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( <> - + {Object.keys(nodeConfig) + .sort() + .map((nodeLabel) => ( + + } + aria-controls={`nodes-accordian-${nodeLabel}`} + id={`nodes-accordian-header-${nodeLabel}`} + > +
+ {nodeLabel} +
+ + {nodeConfig[nodeLabel][0].nodeId} + +
+
+
+ + + +
+ ))} +
+
+ + + You can also specify multiple values for a config parameter by specifying the node id + +
+
+ +
+
)} {getIsChatflowStreamingApi.data?.isStreaming && (