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 && (