mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 23:01:09 +03:00
Enable/disable variables in override configuration (#3467)
* Add ability to enable/disable which variables can be overriden during external predictions * Remove duplicated code * Remove rate limit and allowed domains tab from chatflow configuration * Show tooltip in api code dialog for override config properties * Fix server crash when override config is not available * update UI for chatflow config security, file upload * Fix UI issues in security tab of chatflow configuration dialog * Fix override config options not updating when nodes change * Fix crash in api code dialog when overrideConfig is not available for a chatflow/agentflow. Also fix input config in api code dialog not updating when nodes change. * Refactor override config and add override config for variables * Update api code dialog - update how override config is read and show variable overrides * Update how node and variable overrides are read and resolved * Prevent api code dialog mounting on page load and only mount when api code dialog button is clicked. this should fix loading incorrect data. * Fix variables list not showing when overrideConfig is not available * add overrideconfig to agentflow and upsert vector * temporarily removed enable overrideconfig on upsert, fix linting issues --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
@@ -1,7 +1,15 @@
|
|||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { getErrorMessage } from '../../errors/utils'
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
import { buildFlow, constructGraphs, databaseEntities, getEndingNodes, getStartingNodes, resolveVariables } from '../../utils'
|
import {
|
||||||
|
buildFlow,
|
||||||
|
constructGraphs,
|
||||||
|
databaseEntities,
|
||||||
|
getAPIOverrideConfig,
|
||||||
|
getEndingNodes,
|
||||||
|
getStartingNodes,
|
||||||
|
resolveVariables
|
||||||
|
} from '../../utils'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
import { ChatFlow } from '../../database/entities/ChatFlow'
|
import { ChatFlow } from '../../database/entities/ChatFlow'
|
||||||
import { IDepthQueue, IReactFlowNode } from '../../Interface'
|
import { IDepthQueue, IReactFlowNode } from '../../Interface'
|
||||||
@@ -51,6 +59,8 @@ const buildAndInitTool = async (chatflowid: string, _chatId?: string, _apiMessag
|
|||||||
}
|
}
|
||||||
startingNodeIds = [...new Set(startingNodeIds)]
|
startingNodeIds = [...new Set(startingNodeIds)]
|
||||||
|
|
||||||
|
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||||
|
|
||||||
const reactFlowNodes = await buildFlow({
|
const reactFlowNodes = await buildFlow({
|
||||||
startingNodeIds,
|
startingNodeIds,
|
||||||
reactFlowNodes: nodes,
|
reactFlowNodes: nodes,
|
||||||
@@ -64,7 +74,10 @@ const buildAndInitTool = async (chatflowid: string, _chatId?: string, _apiMessag
|
|||||||
sessionId: chatId,
|
sessionId: chatId,
|
||||||
chatflowid,
|
chatflowid,
|
||||||
apiMessageId,
|
apiMessageId,
|
||||||
appDataSource: appServer.AppDataSource
|
appDataSource: appServer.AppDataSource,
|
||||||
|
apiOverrideStatus,
|
||||||
|
nodeOverrides,
|
||||||
|
variableOverrides
|
||||||
})
|
})
|
||||||
|
|
||||||
const nodeToExecute =
|
const nodeToExecute =
|
||||||
@@ -77,13 +90,16 @@ const buildAndInitTool = async (chatflowid: string, _chatId?: string, _apiMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
const flowDataObj: ICommonObject = { chatflowid, chatId }
|
const flowDataObj: ICommonObject = { chatflowid, chatId }
|
||||||
|
|
||||||
const reactFlowNodeData: INodeData = await resolveVariables(
|
const reactFlowNodeData: INodeData = await resolveVariables(
|
||||||
appServer.AppDataSource,
|
appServer.AppDataSource,
|
||||||
nodeToExecute.data,
|
nodeToExecute.data,
|
||||||
reactFlowNodes,
|
reactFlowNodes,
|
||||||
'',
|
'',
|
||||||
[],
|
[],
|
||||||
flowDataObj
|
flowDataObj,
|
||||||
|
'',
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
let nodeToExecuteData = reactFlowNodeData
|
let nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ import {
|
|||||||
databaseEntities,
|
databaseEntities,
|
||||||
getSessionChatHistory,
|
getSessionChatHistory,
|
||||||
getMemorySessionId,
|
getMemorySessionId,
|
||||||
clearSessionMemory
|
clearSessionMemory,
|
||||||
|
getAPIOverrideConfig
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||||
import { replaceInputsWithConfig, resolveVariables } from '.'
|
import { replaceInputsWithConfig, resolveVariables } from '.'
|
||||||
@@ -111,6 +112,9 @@ export const buildAgentGraph = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** Get API Config ***/
|
||||||
|
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||||
|
|
||||||
// Initialize nodes like ChatModels, Tools, etc.
|
// Initialize nodes like ChatModels, Tools, etc.
|
||||||
const reactFlowNodes: IReactFlowNode[] = await buildFlow({
|
const reactFlowNodes: IReactFlowNode[] = await buildFlow({
|
||||||
startingNodeIds,
|
startingNodeIds,
|
||||||
@@ -121,17 +125,20 @@ export const buildAgentGraph = async (
|
|||||||
depthQueue,
|
depthQueue,
|
||||||
componentNodes: appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
question: incomingInput.question,
|
question: incomingInput.question,
|
||||||
|
uploadedFilesContent,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
chatId,
|
chatId,
|
||||||
sessionId,
|
sessionId,
|
||||||
chatflowid,
|
chatflowid,
|
||||||
appDataSource: appServer.AppDataSource,
|
appDataSource: appServer.AppDataSource,
|
||||||
overrideConfig: incomingInput?.overrideConfig,
|
overrideConfig: incomingInput?.overrideConfig,
|
||||||
|
apiOverrideStatus,
|
||||||
|
nodeOverrides,
|
||||||
|
variableOverrides,
|
||||||
cachePool: appServer.cachePool,
|
cachePool: appServer.cachePool,
|
||||||
isUpsert: false,
|
isUpsert: false,
|
||||||
uploads: incomingInput.uploads,
|
uploads: incomingInput.uploads,
|
||||||
baseURL,
|
baseURL
|
||||||
uploadedFilesContent
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@@ -177,39 +184,39 @@ export const buildAgentGraph = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!seqAgentNodes.length) {
|
if (!seqAgentNodes.length) {
|
||||||
streamResults = await compileMultiAgentsGraph(
|
streamResults = await compileMultiAgentsGraph({
|
||||||
chatflow,
|
chatflow,
|
||||||
mapNameToLabel,
|
mapNameToLabel,
|
||||||
reactFlowNodes,
|
reactFlowNodes,
|
||||||
endingNodeIds,
|
workerNodeIds: endingNodeIds,
|
||||||
appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
options,
|
options,
|
||||||
startingNodeIds,
|
startingNodeIds,
|
||||||
incomingInput.question,
|
question: incomingInput.question,
|
||||||
incomingInput.history,
|
prependHistoryMessages: incomingInput.history,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
incomingInput?.overrideConfig,
|
overrideConfig: incomingInput?.overrideConfig,
|
||||||
sessionId || chatId,
|
threadId: sessionId || chatId,
|
||||||
seqAgentNodes.some((node) => node.data.inputs?.summarization),
|
summarization: seqAgentNodes.some((node) => node.data.inputs?.summarization),
|
||||||
uploadedFilesContent
|
uploadedFilesContent
|
||||||
)
|
})
|
||||||
} else {
|
} else {
|
||||||
isSequential = true
|
isSequential = true
|
||||||
streamResults = await compileSeqAgentsGraph(
|
streamResults = await compileSeqAgentsGraph({
|
||||||
depthQueue,
|
depthQueue,
|
||||||
chatflow,
|
chatflow,
|
||||||
reactFlowNodes,
|
reactFlowNodes,
|
||||||
edges,
|
reactFlowEdges: edges,
|
||||||
appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
options,
|
options,
|
||||||
incomingInput.question,
|
question: incomingInput.question,
|
||||||
incomingInput.history,
|
prependHistoryMessages: incomingInput.history,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
incomingInput?.overrideConfig,
|
overrideConfig: incomingInput?.overrideConfig,
|
||||||
sessionId || chatId,
|
threadId: sessionId || chatId,
|
||||||
incomingInput.action,
|
action: incomingInput.action,
|
||||||
uploadedFilesContent
|
uploadedFilesContent
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamResults) {
|
if (streamResults) {
|
||||||
@@ -367,7 +374,11 @@ export const buildAgentGraph = async (
|
|||||||
|
|
||||||
// Map raw tool calls to used tools, to be shown on interrupted message
|
// Map raw tool calls to used tools, to be shown on interrupted message
|
||||||
const mappedToolCalls = lastMessageRaw.tool_calls.map((toolCall) => {
|
const mappedToolCalls = lastMessageRaw.tool_calls.map((toolCall) => {
|
||||||
return { tool: toolCall.name, toolInput: toolCall.args, toolOutput: '' }
|
return {
|
||||||
|
tool: toolCall.name,
|
||||||
|
toolInput: toolCall.args,
|
||||||
|
toolOutput: ''
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emit the interrupt message to the client
|
// Emit the interrupt message to the client
|
||||||
@@ -388,7 +399,11 @@ export const buildAgentGraph = async (
|
|||||||
}
|
}
|
||||||
finalAction = {
|
finalAction = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
mapping: { approve: approveButtonText, reject: rejectButtonText, toolCalls: lastMessageRaw.tool_calls },
|
mapping: {
|
||||||
|
approve: approveButtonText,
|
||||||
|
reject: rejectButtonText,
|
||||||
|
toolCalls: lastMessageRaw.tool_calls
|
||||||
|
},
|
||||||
elements: [
|
elements: [
|
||||||
{ type: 'approve-button', label: approveButtonText },
|
{ type: 'approve-button', label: approveButtonText },
|
||||||
{ type: 'reject-button', label: rejectButtonText }
|
{ type: 'reject-button', label: rejectButtonText }
|
||||||
@@ -446,37 +461,42 @@ export const buildAgentGraph = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type MultiAgentsGraphParams = {
|
||||||
* Compile Multi Agents Graph
|
chatflow: IChatFlow
|
||||||
* @param {IChatFlow} chatflow
|
mapNameToLabel: Record<string, { label: string; nodeName: string }>
|
||||||
* @param {Record<string, {label: string, nodeName: string }>} mapNameToLabel
|
reactFlowNodes: IReactFlowNode[]
|
||||||
* @param {IReactFlowNode[]} reactflowNodes
|
workerNodeIds: string[]
|
||||||
* @param {string[]} workerNodeIds
|
componentNodes: IComponentNodes
|
||||||
* @param {IComponentNodes} componentNodes
|
options: ICommonObject
|
||||||
* @param {ICommonObject} options
|
startingNodeIds: string[]
|
||||||
* @param {string[]} startingNodeIds
|
question: string
|
||||||
* @param {string} question
|
prependHistoryMessages?: IMessage[]
|
||||||
* @param {ICommonObject} overrideConfig
|
chatHistory?: IMessage[]
|
||||||
* @param {string} threadId
|
overrideConfig?: ICommonObject
|
||||||
* @param {boolean} summarization
|
threadId?: string
|
||||||
* @param {string} uploadedFilesContent,
|
summarization?: boolean
|
||||||
*/
|
|
||||||
const compileMultiAgentsGraph = async (
|
|
||||||
chatflow: IChatFlow,
|
|
||||||
mapNameToLabel: Record<string, { label: string; nodeName: string }>,
|
|
||||||
reactflowNodes: IReactFlowNode[] = [],
|
|
||||||
workerNodeIds: string[],
|
|
||||||
componentNodes: IComponentNodes,
|
|
||||||
options: ICommonObject,
|
|
||||||
startingNodeIds: string[],
|
|
||||||
question: string,
|
|
||||||
prependHistoryMessages: IMessage[] = [],
|
|
||||||
chatHistory: IMessage[] = [],
|
|
||||||
overrideConfig?: ICommonObject,
|
|
||||||
threadId?: string,
|
|
||||||
summarization?: boolean,
|
|
||||||
uploadedFilesContent?: string
|
uploadedFilesContent?: string
|
||||||
) => {
|
}
|
||||||
|
|
||||||
|
const compileMultiAgentsGraph = async (params: MultiAgentsGraphParams) => {
|
||||||
|
const {
|
||||||
|
chatflow,
|
||||||
|
mapNameToLabel,
|
||||||
|
reactFlowNodes,
|
||||||
|
workerNodeIds,
|
||||||
|
componentNodes,
|
||||||
|
options,
|
||||||
|
startingNodeIds,
|
||||||
|
prependHistoryMessages = [],
|
||||||
|
chatHistory = [],
|
||||||
|
overrideConfig = {},
|
||||||
|
threadId,
|
||||||
|
summarization = false,
|
||||||
|
uploadedFilesContent
|
||||||
|
} = params
|
||||||
|
|
||||||
|
let question = params.question
|
||||||
|
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const channels: ITeamState = {
|
const channels: ITeamState = {
|
||||||
messages: {
|
messages: {
|
||||||
@@ -495,7 +515,10 @@ const compileMultiAgentsGraph = async (
|
|||||||
channels
|
channels
|
||||||
})
|
})
|
||||||
|
|
||||||
const workerNodes = reactflowNodes.filter((node) => workerNodeIds.includes(node.data.id))
|
const workerNodes = reactFlowNodes.filter((node) => workerNodeIds.includes(node.data.id))
|
||||||
|
|
||||||
|
/*** Get API Config ***/
|
||||||
|
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||||
|
|
||||||
let supervisorWorkers: { [key: string]: IMultiAgentNode[] } = {}
|
let supervisorWorkers: { [key: string]: IMultiAgentNode[] } = {}
|
||||||
|
|
||||||
@@ -506,15 +529,16 @@ const compileMultiAgentsGraph = async (
|
|||||||
const newNodeInstance = new nodeModule.nodeClass()
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
let flowNodeData = cloneDeep(workerNode.data)
|
let flowNodeData = cloneDeep(workerNode.data)
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
if (overrideConfig && apiOverrideStatus) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig, nodeOverrides)
|
||||||
flowNodeData = await resolveVariables(
|
flowNodeData = await resolveVariables(
|
||||||
appServer.AppDataSource,
|
appServer.AppDataSource,
|
||||||
flowNodeData,
|
flowNodeData,
|
||||||
reactflowNodes,
|
reactFlowNodes,
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
overrideConfig,
|
overrideConfig,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -536,7 +560,7 @@ const compileMultiAgentsGraph = async (
|
|||||||
// Init supervisor nodes
|
// Init supervisor nodes
|
||||||
for (const supervisor in supervisorWorkers) {
|
for (const supervisor in supervisorWorkers) {
|
||||||
const supervisorInputLabel = mapNameToLabel[supervisor].label
|
const supervisorInputLabel = mapNameToLabel[supervisor].label
|
||||||
const supervisorNode = reactflowNodes.find((node) => supervisorInputLabel === node.data.inputs?.supervisorName)
|
const supervisorNode = reactFlowNodes.find((node) => supervisorInputLabel === node.data.inputs?.supervisorName)
|
||||||
if (!supervisorNode) continue
|
if (!supervisorNode) continue
|
||||||
|
|
||||||
const nodeInstanceFilePath = componentNodes[supervisorNode.data.name].filePath as string
|
const nodeInstanceFilePath = componentNodes[supervisorNode.data.name].filePath as string
|
||||||
@@ -545,15 +569,16 @@ const compileMultiAgentsGraph = async (
|
|||||||
|
|
||||||
let flowNodeData = cloneDeep(supervisorNode.data)
|
let flowNodeData = cloneDeep(supervisorNode.data)
|
||||||
|
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
if (overrideConfig && apiOverrideStatus) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig, nodeOverrides)
|
||||||
flowNodeData = await resolveVariables(
|
flowNodeData = await resolveVariables(
|
||||||
appServer.AppDataSource,
|
appServer.AppDataSource,
|
||||||
flowNodeData,
|
flowNodeData,
|
||||||
reactflowNodes,
|
reactFlowNodes,
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
overrideConfig,
|
overrideConfig,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor]
|
if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor]
|
||||||
@@ -598,7 +623,7 @@ const compileMultiAgentsGraph = async (
|
|||||||
appServer.chatflowPool.add(
|
appServer.chatflowPool.add(
|
||||||
`${chatflow.id}_${options.chatId}`,
|
`${chatflow.id}_${options.chatId}`,
|
||||||
workflowGraph as any,
|
workflowGraph as any,
|
||||||
reactflowNodes.filter((node) => startingNodeIds.includes(node.id)),
|
reactFlowNodes.filter((node) => startingNodeIds.includes(node.id)),
|
||||||
overrideConfig
|
overrideConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -616,9 +641,17 @@ const compileMultiAgentsGraph = async (
|
|||||||
if (prependHistoryMessages.length === chatHistory.length) {
|
if (prependHistoryMessages.length === chatHistory.length) {
|
||||||
for (const message of prependHistoryMessages) {
|
for (const message of prependHistoryMessages) {
|
||||||
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
||||||
prependMessages.push(new AIMessage({ content: message.message || message.content || '' }))
|
prependMessages.push(
|
||||||
|
new AIMessage({
|
||||||
|
content: message.message || message.content || ''
|
||||||
|
})
|
||||||
|
)
|
||||||
} else if (message.role === 'userMessage' || message.type === 'userMessage') {
|
} else if (message.role === 'userMessage' || message.type === 'userMessage') {
|
||||||
prependMessages.push(new HumanMessage({ content: message.message || message.content || '' }))
|
prependMessages.push(
|
||||||
|
new HumanMessage({
|
||||||
|
content: message.message || message.content || ''
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -629,7 +662,11 @@ const compileMultiAgentsGraph = async (
|
|||||||
{
|
{
|
||||||
messages: [...prependMessages, new HumanMessage({ content: finalQuestion })]
|
messages: [...prependMessages, new HumanMessage({ content: finalQuestion })]
|
||||||
},
|
},
|
||||||
{ recursionLimit: supervisorResult?.recursionLimit ?? 100, callbacks: [loggerHandler, ...callbacks], configurable: config }
|
{
|
||||||
|
recursionLimit: supervisorResult?.recursionLimit ?? 100,
|
||||||
|
callbacks: [loggerHandler, ...callbacks],
|
||||||
|
configurable: config
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error initialize supervisor nodes - ${getErrorMessage(e)}`)
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error initialize supervisor nodes - ${getErrorMessage(e)}`)
|
||||||
@@ -637,35 +674,40 @@ const compileMultiAgentsGraph = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type SeqAgentsGraphParams = {
|
||||||
* Compile Seq Agents Graph
|
depthQueue: IDepthQueue
|
||||||
* @param {IDepthQueue} depthQueue
|
chatflow: IChatFlow
|
||||||
* @param {IChatFlow} chatflow
|
reactFlowNodes: IReactFlowNode[]
|
||||||
* @param {IReactFlowNode[]} reactflowNodes
|
reactFlowEdges: IReactFlowEdge[]
|
||||||
* @param {IReactFlowEdge[]} reactflowEdges
|
componentNodes: IComponentNodes
|
||||||
* @param {IComponentNodes} componentNodes
|
options: ICommonObject
|
||||||
* @param {ICommonObject} options
|
question: string
|
||||||
* @param {string} question
|
prependHistoryMessages?: IMessage[]
|
||||||
* @param {IMessage[]} chatHistory
|
chatHistory?: IMessage[]
|
||||||
* @param {ICommonObject} overrideConfig
|
overrideConfig?: ICommonObject
|
||||||
* @param {string} threadId
|
threadId?: string
|
||||||
* @param {IAction} action
|
action?: IAction
|
||||||
*/
|
|
||||||
const compileSeqAgentsGraph = async (
|
|
||||||
depthQueue: IDepthQueue,
|
|
||||||
chatflow: IChatFlow,
|
|
||||||
reactflowNodes: IReactFlowNode[] = [],
|
|
||||||
reactflowEdges: IReactFlowEdge[] = [],
|
|
||||||
componentNodes: IComponentNodes,
|
|
||||||
options: ICommonObject,
|
|
||||||
question: string,
|
|
||||||
prependHistoryMessages: IMessage[] = [],
|
|
||||||
chatHistory: IMessage[] = [],
|
|
||||||
overrideConfig?: ICommonObject,
|
|
||||||
threadId?: string,
|
|
||||||
action?: IAction,
|
|
||||||
uploadedFilesContent?: string
|
uploadedFilesContent?: string
|
||||||
) => {
|
}
|
||||||
|
|
||||||
|
const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => {
|
||||||
|
const {
|
||||||
|
depthQueue,
|
||||||
|
chatflow,
|
||||||
|
reactFlowNodes,
|
||||||
|
reactFlowEdges,
|
||||||
|
componentNodes,
|
||||||
|
options,
|
||||||
|
prependHistoryMessages = [],
|
||||||
|
chatHistory = [],
|
||||||
|
overrideConfig = {},
|
||||||
|
threadId,
|
||||||
|
action,
|
||||||
|
uploadedFilesContent
|
||||||
|
} = params
|
||||||
|
|
||||||
|
let question = params.question
|
||||||
|
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
||||||
let channels: ISeqAgentsState = {
|
let channels: ISeqAgentsState = {
|
||||||
@@ -676,7 +718,7 @@ const compileSeqAgentsGraph = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get state
|
// Get state
|
||||||
const seqStateNode = reactflowNodes.find((node: IReactFlowNode) => node.data.name === 'seqState')
|
const seqStateNode = reactFlowNodes.find((node: IReactFlowNode) => node.data.name === 'seqState')
|
||||||
if (seqStateNode) {
|
if (seqStateNode) {
|
||||||
channels = {
|
channels = {
|
||||||
...seqStateNode.data.instance.node,
|
...seqStateNode.data.instance.node,
|
||||||
@@ -690,13 +732,13 @@ const compileSeqAgentsGraph = async (
|
|||||||
})
|
})
|
||||||
|
|
||||||
/*** Validate Graph ***/
|
/*** Validate Graph ***/
|
||||||
const startAgentNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqStart')
|
const startAgentNodes: IReactFlowNode[] = reactFlowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqStart')
|
||||||
if (!startAgentNodes.length) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Start node not found')
|
if (!startAgentNodes.length) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Start node not found')
|
||||||
if (startAgentNodes.length > 1)
|
if (startAgentNodes.length > 1)
|
||||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have only one start node')
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have only one start node')
|
||||||
|
|
||||||
const endAgentNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqEnd')
|
const endAgentNodes: IReactFlowNode[] = reactFlowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqEnd')
|
||||||
const loopNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqLoop')
|
const loopNodes: IReactFlowNode[] = reactFlowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqLoop')
|
||||||
if (!endAgentNodes.length && !loopNodes.length) {
|
if (!endAgentNodes.length && !loopNodes.length) {
|
||||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have at least one End/Loop node')
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have at least one End/Loop node')
|
||||||
}
|
}
|
||||||
@@ -708,6 +750,7 @@ const compileSeqAgentsGraph = async (
|
|||||||
let conditionalToolNodes: Record<string, { source: ISeqAgentNode; toolNodes: ISeqAgentNode[] }> = {}
|
let conditionalToolNodes: Record<string, { source: ISeqAgentNode; toolNodes: ISeqAgentNode[] }> = {}
|
||||||
let bindModel: Record<string, any> = {}
|
let bindModel: Record<string, any> = {}
|
||||||
let interruptToolNodeNames = []
|
let interruptToolNodeNames = []
|
||||||
|
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||||
|
|
||||||
const initiateNode = async (node: IReactFlowNode) => {
|
const initiateNode = async (node: IReactFlowNode) => {
|
||||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||||
@@ -715,15 +758,16 @@ const compileSeqAgentsGraph = async (
|
|||||||
const newNodeInstance = new nodeModule.nodeClass()
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
flowNodeData = cloneDeep(node.data)
|
flowNodeData = cloneDeep(node.data)
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
if (overrideConfig && apiOverrideStatus) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig, nodeOverrides)
|
||||||
flowNodeData = await resolveVariables(
|
flowNodeData = await resolveVariables(
|
||||||
appServer.AppDataSource,
|
appServer.AppDataSource,
|
||||||
flowNodeData,
|
flowNodeData,
|
||||||
reactflowNodes,
|
reactFlowNodes,
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
overrideConfig,
|
overrideConfig,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
||||||
@@ -740,16 +784,16 @@ const compileSeqAgentsGraph = async (
|
|||||||
* 2.) With the interruptedRouteMapping object, avoid adding conditional edges to the Interrupted Agent for the nodes that are already interrupted by tools. It will be separately added from the function - agentInterruptToolFunc
|
* 2.) With the interruptedRouteMapping object, avoid adding conditional edges to the Interrupted Agent for the nodes that are already interrupted by tools. It will be separately added from the function - agentInterruptToolFunc
|
||||||
*/
|
*/
|
||||||
const processInterruptedRouteMapping = (conditionNodeId: string) => {
|
const processInterruptedRouteMapping = (conditionNodeId: string) => {
|
||||||
const conditionEdges = reactflowEdges.filter((edge) => edge.source === conditionNodeId) ?? []
|
const conditionEdges = reactFlowEdges.filter((edge) => edge.source === conditionNodeId) ?? []
|
||||||
|
|
||||||
for (const conditionEdge of conditionEdges) {
|
for (const conditionEdge of conditionEdges) {
|
||||||
const nextNodeId = conditionEdge.target
|
const nextNodeId = conditionEdge.target
|
||||||
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
||||||
|
|
||||||
const nextNode = reactflowNodes.find((node) => node.id === nextNodeId)
|
const nextNode = reactFlowNodes.find((node) => node.id === nextNodeId)
|
||||||
if (!nextNode) continue
|
if (!nextNode) continue
|
||||||
|
|
||||||
const conditionNode = reactflowNodes.find((node) => node.id === conditionNodeId)
|
const conditionNode = reactFlowNodes.find((node) => node.id === conditionNodeId)
|
||||||
if (!conditionNode) continue
|
if (!conditionNode) continue
|
||||||
|
|
||||||
const outputAnchors = conditionNode?.data.outputAnchors
|
const outputAnchors = conditionNode?.data.outputAnchors
|
||||||
@@ -780,13 +824,13 @@ const compileSeqAgentsGraph = async (
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
const prepareConditionalEdges = (nodeId: string, nodeInstance: ISeqAgentNode) => {
|
const prepareConditionalEdges = (nodeId: string, nodeInstance: ISeqAgentNode) => {
|
||||||
const conditionEdges = reactflowEdges.filter((edge) => edge.target === nodeId && edge.source.includes('seqCondition')) ?? []
|
const conditionEdges = reactFlowEdges.filter((edge) => edge.target === nodeId && edge.source.includes('seqCondition')) ?? []
|
||||||
|
|
||||||
for (const conditionEdge of conditionEdges) {
|
for (const conditionEdge of conditionEdges) {
|
||||||
const conditionNodeId = conditionEdge.source
|
const conditionNodeId = conditionEdge.source
|
||||||
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
||||||
|
|
||||||
const conditionNode = reactflowNodes.find((node) => node.id === conditionNodeId)
|
const conditionNode = reactFlowNodes.find((node) => node.id === conditionNodeId)
|
||||||
const outputAnchors = conditionNode?.data.outputAnchors
|
const outputAnchors = conditionNode?.data.outputAnchors
|
||||||
|
|
||||||
if (!outputAnchors || !outputAnchors.length || !outputAnchors[0].options) continue
|
if (!outputAnchors || !outputAnchors.length || !outputAnchors[0].options) continue
|
||||||
@@ -799,7 +843,10 @@ const compileSeqAgentsGraph = async (
|
|||||||
if (Object.prototype.hasOwnProperty.call(conditionalEdges, conditionNodeId)) {
|
if (Object.prototype.hasOwnProperty.call(conditionalEdges, conditionNodeId)) {
|
||||||
conditionalEdges[conditionNodeId] = {
|
conditionalEdges[conditionNodeId] = {
|
||||||
...conditionalEdges[conditionNodeId],
|
...conditionalEdges[conditionNodeId],
|
||||||
nodes: { ...conditionalEdges[conditionNodeId].nodes, [conditionOutputAnchorLabel]: nodeInstance.name }
|
nodes: {
|
||||||
|
...conditionalEdges[conditionNodeId].nodes,
|
||||||
|
[conditionOutputAnchorLabel]: nodeInstance.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
conditionalEdges[conditionNodeId] = {
|
conditionalEdges[conditionNodeId] = {
|
||||||
@@ -820,7 +867,10 @@ const compileSeqAgentsGraph = async (
|
|||||||
if (Object.prototype.hasOwnProperty.call(conditionalToolNodes, predecessorAgent.id)) {
|
if (Object.prototype.hasOwnProperty.call(conditionalToolNodes, predecessorAgent.id)) {
|
||||||
const toolNodes = conditionalToolNodes[predecessorAgent.id].toolNodes
|
const toolNodes = conditionalToolNodes[predecessorAgent.id].toolNodes
|
||||||
toolNodes.push(toolNodeInstance)
|
toolNodes.push(toolNodeInstance)
|
||||||
conditionalToolNodes[predecessorAgent.id] = { source: predecessorAgent, toolNodes }
|
conditionalToolNodes[predecessorAgent.id] = {
|
||||||
|
source: predecessorAgent,
|
||||||
|
toolNodes
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
conditionalToolNodes[predecessorAgent.id] = {
|
conditionalToolNodes[predecessorAgent.id] = {
|
||||||
source: predecessorAgent,
|
source: predecessorAgent,
|
||||||
@@ -837,7 +887,7 @@ const compileSeqAgentsGraph = async (
|
|||||||
|
|
||||||
/*** Start processing every Agent nodes ***/
|
/*** Start processing every Agent nodes ***/
|
||||||
for (const agentNodeId of getSortedDepthNodes(depthQueue)) {
|
for (const agentNodeId of getSortedDepthNodes(depthQueue)) {
|
||||||
const agentNode = reactflowNodes.find((node) => node.id === agentNodeId)
|
const agentNode = reactFlowNodes.find((node) => node.id === agentNodeId)
|
||||||
if (!agentNode) continue
|
if (!agentNode) continue
|
||||||
|
|
||||||
const eligibleSeqNodes = ['seqAgent', 'seqEnd', 'seqLoop', 'seqToolNode', 'seqLLMNode']
|
const eligibleSeqNodes = ['seqAgent', 'seqEnd', 'seqLoop', 'seqToolNode', 'seqLLMNode']
|
||||||
@@ -859,8 +909,8 @@ const compileSeqAgentsGraph = async (
|
|||||||
if (agentInstance.type === 'agent' && agentNode.data.inputs?.interrupt) {
|
if (agentInstance.type === 'agent' && agentNode.data.inputs?.interrupt) {
|
||||||
interruptToolNodeNames.push(agentInstance.agentInterruptToolNode.name)
|
interruptToolNodeNames.push(agentInstance.agentInterruptToolNode.name)
|
||||||
|
|
||||||
const nextNodeId = reactflowEdges.find((edge) => edge.source === agentNode.id)?.target
|
const nextNodeId = reactFlowEdges.find((edge) => edge.source === agentNode.id)?.target
|
||||||
const nextNode = reactflowNodes.find((node) => node.id === nextNodeId)
|
const nextNode = reactFlowNodes.find((node) => node.id === nextNodeId)
|
||||||
|
|
||||||
let nextNodeSeqAgentName = ''
|
let nextNodeSeqAgentName = ''
|
||||||
if (nextNodeId && nextNode) {
|
if (nextNodeId && nextNode) {
|
||||||
@@ -950,11 +1000,11 @@ const compileSeqAgentsGraph = async (
|
|||||||
|
|
||||||
/*** Add conditional edges to graph for condition nodes ***/
|
/*** Add conditional edges to graph for condition nodes ***/
|
||||||
for (const conditionNodeId in conditionalEdges) {
|
for (const conditionNodeId in conditionalEdges) {
|
||||||
const startConditionEdges = reactflowEdges.filter((edge) => edge.target === conditionNodeId)
|
const startConditionEdges = reactFlowEdges.filter((edge) => edge.target === conditionNodeId)
|
||||||
if (!startConditionEdges.length) continue
|
if (!startConditionEdges.length) continue
|
||||||
|
|
||||||
for (const startConditionEdge of startConditionEdges) {
|
for (const startConditionEdge of startConditionEdges) {
|
||||||
const startConditionNode = reactflowNodes.find((node) => node.id === startConditionEdge.source)
|
const startConditionNode = reactFlowNodes.find((node) => node.id === startConditionEdge.source)
|
||||||
if (!startConditionNode) continue
|
if (!startConditionNode) continue
|
||||||
seqGraph.addConditionalEdges(
|
seqGraph.addConditionalEdges(
|
||||||
startConditionNode.data.instance.name,
|
startConditionNode.data.instance.name,
|
||||||
@@ -995,22 +1045,24 @@ const compileSeqAgentsGraph = async (
|
|||||||
routeMessage
|
routeMessage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Add agentflow to pool ***/
|
/*** Add agentflow to pool ***/
|
||||||
;(seqGraph as any).signal = options.signal
|
;(seqGraph as any).signal = options.signal
|
||||||
appServer.chatflowPool.add(
|
appServer.chatflowPool.add(
|
||||||
`${chatflow.id}_${options.chatId}`,
|
`${chatflow.id}_${options.chatId}`,
|
||||||
seqGraph as any,
|
seqGraph as any,
|
||||||
reactflowNodes.filter((node) => startAgentNodes.map((nd) => nd.id).includes(node.id)),
|
reactFlowNodes.filter((node) => startAgentNodes.map((nd) => nd.id).includes(node.id)),
|
||||||
overrideConfig
|
overrideConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
/*** Get memory ***/
|
/*** Get memory ***/
|
||||||
const startNode = reactflowNodes.find((node: IReactFlowNode) => node.data.name === 'seqStart')
|
const startNode = reactFlowNodes.find((node: IReactFlowNode) => node.data.name === 'seqStart')
|
||||||
let memory = startNode?.data.instance?.checkpointMemory
|
let memory = startNode?.data.instance?.checkpointMemory
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const graph = seqGraph.compile({ checkpointer: memory, interruptBefore: interruptToolNodeNames as any })
|
const graph = seqGraph.compile({
|
||||||
|
checkpointer: memory,
|
||||||
|
interruptBefore: interruptToolNodeNames as any
|
||||||
|
})
|
||||||
|
|
||||||
const loggerHandler = new ConsoleCallbackHandler(logger)
|
const loggerHandler = new ConsoleCallbackHandler(logger)
|
||||||
const callbacks = await additionalCallbacks(flowNodeData as any, options)
|
const callbacks = await additionalCallbacks(flowNodeData as any, options)
|
||||||
@@ -1021,9 +1073,17 @@ const compileSeqAgentsGraph = async (
|
|||||||
if (prependHistoryMessages.length === chatHistory.length) {
|
if (prependHistoryMessages.length === chatHistory.length) {
|
||||||
for (const message of prependHistoryMessages) {
|
for (const message of prependHistoryMessages) {
|
||||||
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
||||||
prependMessages.push(new AIMessage({ content: message.message || message.content || '' }))
|
prependMessages.push(
|
||||||
|
new AIMessage({
|
||||||
|
content: message.message || message.content || ''
|
||||||
|
})
|
||||||
|
)
|
||||||
} else if (message.role === 'userMessage' || message.type === 'userMessage') {
|
} else if (message.role === 'userMessage' || message.type === 'userMessage') {
|
||||||
prependMessages.push(new HumanMessage({ content: message.message || message.content || '' }))
|
prependMessages.push(
|
||||||
|
new HumanMessage({
|
||||||
|
content: message.message || message.content || ''
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1047,7 +1107,10 @@ const compileSeqAgentsGraph = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await graph.stream(humanMsg, { callbacks: [loggerHandler, ...callbacks], configurable: config })
|
return await graph.stream(humanMsg, {
|
||||||
|
callbacks: [loggerHandler, ...callbacks],
|
||||||
|
configurable: config
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error compile graph', e)
|
logger.error('Error compile graph', e)
|
||||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error compile graph - ${getErrorMessage(e)}`)
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error compile graph - ${getErrorMessage(e)}`)
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import {
|
|||||||
isSameOverrideConfig,
|
isSameOverrideConfig,
|
||||||
getEndingNodes,
|
getEndingNodes,
|
||||||
constructGraphs,
|
constructGraphs,
|
||||||
isSameChatId
|
isSameChatId,
|
||||||
|
getAPIOverrideConfig
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { validateChatflowAPIKey } from './validateKey'
|
import { validateChatflowAPIKey } from './validateKey'
|
||||||
import { databaseEntities } from '.'
|
import { databaseEntities } from '.'
|
||||||
@@ -346,15 +347,19 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
|||||||
|
|
||||||
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
||||||
|
|
||||||
|
/*** Get API Config ***/
|
||||||
|
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||||
|
|
||||||
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
|
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
|
||||||
|
|
||||||
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
||||||
const reactFlowNodes = await buildFlow({
|
const reactFlowNodes = await buildFlow({
|
||||||
startingNodeIds,
|
startingNodeIds,
|
||||||
reactFlowNodes: nodes,
|
reactFlowNodes: nodes,
|
||||||
reactFlowEdges: edges,
|
reactFlowEdges: edges,
|
||||||
|
apiMessageId,
|
||||||
graph,
|
graph,
|
||||||
depthQueue,
|
depthQueue,
|
||||||
apiMessageId,
|
|
||||||
componentNodes: appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
question: incomingInput.question,
|
question: incomingInput.question,
|
||||||
uploadedFilesContent,
|
uploadedFilesContent,
|
||||||
@@ -364,6 +369,9 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
|||||||
chatflowid,
|
chatflowid,
|
||||||
appDataSource: appServer.AppDataSource,
|
appDataSource: appServer.AppDataSource,
|
||||||
overrideConfig: incomingInput?.overrideConfig,
|
overrideConfig: incomingInput?.overrideConfig,
|
||||||
|
apiOverrideStatus,
|
||||||
|
nodeOverrides,
|
||||||
|
variableOverrides,
|
||||||
cachePool: appServer.cachePool,
|
cachePool: appServer.cachePool,
|
||||||
isUpsert: false,
|
isUpsert: false,
|
||||||
uploads: incomingInput.uploads,
|
uploads: incomingInput.uploads,
|
||||||
@@ -378,8 +386,9 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
|||||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Node not found`)
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Node not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incomingInput.overrideConfig) {
|
// Only override the config if its status is true
|
||||||
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig)
|
if (incomingInput.overrideConfig && apiOverrideStatus) {
|
||||||
|
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig, nodeOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
const flowData: ICommonObject = {
|
const flowData: ICommonObject = {
|
||||||
@@ -398,7 +407,8 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
|||||||
incomingInput.question,
|
incomingInput.question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
flowData,
|
flowData,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
nodeToExecuteData = reactFlowNodeData
|
nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import path from 'path'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import logger from './logger'
|
import logger from './logger'
|
||||||
import {
|
import {
|
||||||
|
IChatFlow,
|
||||||
IComponentCredentials,
|
IComponentCredentials,
|
||||||
IComponentNodes,
|
IComponentNodes,
|
||||||
ICredentialDataDecrypted,
|
ICredentialDataDecrypted,
|
||||||
@@ -434,6 +435,9 @@ type BuildFlowParams = {
|
|||||||
apiMessageId: string
|
apiMessageId: string
|
||||||
appDataSource: DataSource
|
appDataSource: DataSource
|
||||||
overrideConfig?: ICommonObject
|
overrideConfig?: ICommonObject
|
||||||
|
apiOverrideStatus?: boolean
|
||||||
|
nodeOverrides?: ICommonObject
|
||||||
|
variableOverrides?: ICommonObject[]
|
||||||
cachePool?: CachePool
|
cachePool?: CachePool
|
||||||
isUpsert?: boolean
|
isUpsert?: boolean
|
||||||
stopNodeId?: string
|
stopNodeId?: string
|
||||||
@@ -462,6 +466,9 @@ export const buildFlow = async ({
|
|||||||
chatflowid,
|
chatflowid,
|
||||||
appDataSource,
|
appDataSource,
|
||||||
overrideConfig,
|
overrideConfig,
|
||||||
|
apiOverrideStatus = false,
|
||||||
|
nodeOverrides = {},
|
||||||
|
variableOverrides = [],
|
||||||
cachePool,
|
cachePool,
|
||||||
isUpsert,
|
isUpsert,
|
||||||
stopNodeId,
|
stopNodeId,
|
||||||
@@ -509,7 +516,11 @@ export const buildFlow = async ({
|
|||||||
const newNodeInstance = new nodeModule.nodeClass()
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
let flowNodeData = cloneDeep(reactFlowNode.data)
|
let flowNodeData = cloneDeep(reactFlowNode.data)
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
|
||||||
|
// Only override the config if its status is true
|
||||||
|
if (overrideConfig && apiOverrideStatus) {
|
||||||
|
flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig, nodeOverrides)
|
||||||
|
}
|
||||||
|
|
||||||
if (isUpsert) upsertHistory['flowData'] = saveUpsertFlowData(flowNodeData, upsertHistory)
|
if (isUpsert) upsertHistory['flowData'] = saveUpsertFlowData(flowNodeData, upsertHistory)
|
||||||
|
|
||||||
@@ -520,7 +531,8 @@ export const buildFlow = async ({
|
|||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
flowData,
|
flowData,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
|
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
|
||||||
@@ -713,13 +725,19 @@ export const clearSessionMemory = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGlobalVariable = async (appDataSource: DataSource, overrideConfig?: ICommonObject) => {
|
const getGlobalVariable = async (appDataSource: DataSource, overrideConfig?: ICommonObject, variableOverrides?: ICommonObject[]) => {
|
||||||
const variables = await appDataSource.getRepository(Variable).find()
|
const variables = await appDataSource.getRepository(Variable).find()
|
||||||
|
|
||||||
// override variables defined in overrideConfig
|
// override variables defined in overrideConfig
|
||||||
// nodeData.inputs.vars is an Object, check each property and override the variable
|
// nodeData.inputs.vars is an Object, check each property and override the variable
|
||||||
if (overrideConfig?.vars) {
|
if (overrideConfig?.vars && variableOverrides) {
|
||||||
for (const propertyName of Object.getOwnPropertyNames(overrideConfig.vars)) {
|
for (const propertyName of Object.getOwnPropertyNames(overrideConfig.vars)) {
|
||||||
|
// Check if this variable is enabled for override
|
||||||
|
const override = variableOverrides.find((v) => v.name === propertyName)
|
||||||
|
if (!override?.enabled) {
|
||||||
|
continue // Skip this variable if it's not enabled for override
|
||||||
|
}
|
||||||
|
|
||||||
const foundVar = variables.find((v) => v.name === propertyName)
|
const foundVar = variables.find((v) => v.name === propertyName)
|
||||||
if (foundVar) {
|
if (foundVar) {
|
||||||
// even if the variable was defined as runtime, we override it with static value
|
// even if the variable was defined as runtime, we override it with static value
|
||||||
@@ -776,7 +794,8 @@ export const getVariableValue = async (
|
|||||||
chatHistory: IMessage[],
|
chatHistory: IMessage[],
|
||||||
isAcceptVariable = false,
|
isAcceptVariable = false,
|
||||||
flowData?: ICommonObject,
|
flowData?: ICommonObject,
|
||||||
uploadedFilesContent?: string
|
uploadedFilesContent?: string,
|
||||||
|
variableOverrides: ICommonObject[] = []
|
||||||
) => {
|
) => {
|
||||||
const isObject = typeof paramValue === 'object'
|
const isObject = typeof paramValue === 'object'
|
||||||
const initialValue = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
|
const initialValue = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
|
||||||
@@ -818,7 +837,7 @@ export const getVariableValue = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (variableFullPath.startsWith('$vars.')) {
|
if (variableFullPath.startsWith('$vars.')) {
|
||||||
const vars = await getGlobalVariable(appDataSource, flowData)
|
const vars = await getGlobalVariable(appDataSource, flowData, variableOverrides)
|
||||||
const variableValue = get(vars, variableFullPath.replace('$vars.', ''))
|
const variableValue = get(vars, variableFullPath.replace('$vars.', ''))
|
||||||
if (variableValue) {
|
if (variableValue) {
|
||||||
variableDict[`{{${variableFullPath}}}`] = variableValue
|
variableDict[`{{${variableFullPath}}}`] = variableValue
|
||||||
@@ -927,7 +946,8 @@ export const resolveVariables = async (
|
|||||||
question: string,
|
question: string,
|
||||||
chatHistory: IMessage[],
|
chatHistory: IMessage[],
|
||||||
flowData?: ICommonObject,
|
flowData?: ICommonObject,
|
||||||
uploadedFilesContent?: string
|
uploadedFilesContent?: string,
|
||||||
|
variableOverrides: ICommonObject[] = []
|
||||||
): Promise<INodeData> => {
|
): Promise<INodeData> => {
|
||||||
let flowNodeData = cloneDeep(reactFlowNodeData)
|
let flowNodeData = cloneDeep(reactFlowNodeData)
|
||||||
const types = 'inputs'
|
const types = 'inputs'
|
||||||
@@ -946,7 +966,8 @@ export const resolveVariables = async (
|
|||||||
chatHistory,
|
chatHistory,
|
||||||
undefined,
|
undefined,
|
||||||
flowData,
|
flowData,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
resolvedInstances.push(resolvedInstance)
|
resolvedInstances.push(resolvedInstance)
|
||||||
}
|
}
|
||||||
@@ -961,7 +982,8 @@ export const resolveVariables = async (
|
|||||||
chatHistory,
|
chatHistory,
|
||||||
isAcceptVariable,
|
isAcceptVariable,
|
||||||
flowData,
|
flowData,
|
||||||
uploadedFilesContent
|
uploadedFilesContent,
|
||||||
|
variableOverrides
|
||||||
)
|
)
|
||||||
paramsObj[key] = resolvedInstance
|
paramsObj[key] = resolvedInstance
|
||||||
}
|
}
|
||||||
@@ -978,18 +1000,28 @@ export const resolveVariables = async (
|
|||||||
* Loop through each inputs and replace their value with override config values
|
* Loop through each inputs and replace their value with override config values
|
||||||
* @param {INodeData} flowNodeData
|
* @param {INodeData} flowNodeData
|
||||||
* @param {ICommonObject} overrideConfig
|
* @param {ICommonObject} overrideConfig
|
||||||
|
* @param {ICommonObject} nodeOverrides
|
||||||
* @returns {INodeData}
|
* @returns {INodeData}
|
||||||
*/
|
*/
|
||||||
export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: ICommonObject) => {
|
export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: ICommonObject, nodeOverrides: ICommonObject) => {
|
||||||
const types = 'inputs'
|
const types = 'inputs'
|
||||||
|
|
||||||
|
const isParameterEnabled = (nodeType: string, paramName: string): boolean => {
|
||||||
|
if (!nodeOverrides[nodeType]) return false
|
||||||
|
const parameter = nodeOverrides[nodeType].find((param: any) => param.name === paramName)
|
||||||
|
return parameter?.enabled ?? false
|
||||||
|
}
|
||||||
|
|
||||||
const getParamValues = (inputsObj: ICommonObject) => {
|
const getParamValues = (inputsObj: ICommonObject) => {
|
||||||
for (const config in overrideConfig) {
|
for (const config in overrideConfig) {
|
||||||
// If overrideConfig[key] is object
|
// If overrideConfig[key] is object
|
||||||
if (overrideConfig[config] && typeof overrideConfig[config] === 'object') {
|
if (overrideConfig[config] && typeof overrideConfig[config] === 'object') {
|
||||||
const nodeIds = Object.keys(overrideConfig[config])
|
const nodeIds = Object.keys(overrideConfig[config])
|
||||||
if (nodeIds.includes(flowNodeData.id)) {
|
if (nodeIds.includes(flowNodeData.id)) {
|
||||||
inputsObj[config] = overrideConfig[config][flowNodeData.id]
|
// Check if this parameter is enabled for this node type
|
||||||
|
if (isParameterEnabled(flowNodeData.label, config)) {
|
||||||
|
inputsObj[config] = overrideConfig[config][flowNodeData.id]
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
} else if (nodeIds.some((nodeId) => nodeId.includes(flowNodeData.name))) {
|
} else if (nodeIds.some((nodeId) => nodeId.includes(flowNodeData.name))) {
|
||||||
/*
|
/*
|
||||||
@@ -1001,6 +1033,11 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only proceed if the parameter is enabled for this node type
|
||||||
|
if (!isParameterEnabled(flowNodeData.label, config)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let paramValue = inputsObj[config]
|
let paramValue = inputsObj[config]
|
||||||
const overrideConfigValue = overrideConfig[config]
|
const overrideConfigValue = overrideConfig[config]
|
||||||
if (overrideConfigValue) {
|
if (overrideConfigValue) {
|
||||||
@@ -1600,3 +1637,16 @@ export const aMonthAgo = () => {
|
|||||||
date.setMonth(new Date().getMonth() - 1)
|
date.setMonth(new Date().getMonth() - 1)
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAPIOverrideConfig = (chatflow: IChatFlow) => {
|
||||||
|
try {
|
||||||
|
const apiConfig = chatflow.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
|
||||||
|
const nodeOverrides = apiConfig.overrideConfig && apiConfig.overrideConfig.nodes ? apiConfig.overrideConfig.nodes : {}
|
||||||
|
const variableOverrides = apiConfig.overrideConfig && apiConfig.overrideConfig.variables ? apiConfig.overrideConfig.variables : []
|
||||||
|
const apiOverrideStatus = apiConfig.overrideConfig && apiConfig.overrideConfig.status ? apiConfig.overrideConfig.status : false
|
||||||
|
|
||||||
|
return { nodeOverrides, variableOverrides, apiOverrideStatus }
|
||||||
|
} catch (error) {
|
||||||
|
return { nodeOverrides: {}, variableOverrides: [], apiOverrideStatus: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
|||||||
startingNodeIds,
|
startingNodeIds,
|
||||||
reactFlowNodes: nodes,
|
reactFlowNodes: nodes,
|
||||||
reactFlowEdges: edges,
|
reactFlowEdges: edges,
|
||||||
|
apiMessageId,
|
||||||
graph: filteredGraph,
|
graph: filteredGraph,
|
||||||
depthQueue,
|
depthQueue,
|
||||||
componentNodes: appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
question: incomingInput.question,
|
question: incomingInput.question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
chatId,
|
chatId,
|
||||||
apiMessageId,
|
|
||||||
sessionId: sessionId ?? '',
|
sessionId: sessionId ?? '',
|
||||||
chatflowid,
|
chatflowid,
|
||||||
appDataSource: appServer.AppDataSource,
|
appDataSource: appServer.AppDataSource,
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { createPortal } from 'react-dom'
|
|||||||
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
||||||
import { tabsClasses } from '@mui/material/Tabs'
|
import { tabsClasses } from '@mui/material/Tabs'
|
||||||
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
import Security from '@/ui-component/extended/Security'
|
||||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
|
||||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||||
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||||
@@ -15,8 +14,8 @@ import FileUpload from '@/ui-component/extended/FileUpload'
|
|||||||
|
|
||||||
const CHATFLOW_CONFIGURATION_TABS = [
|
const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
label: 'Rate Limiting',
|
label: 'Security',
|
||||||
id: 'rateLimiting'
|
id: 'security'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Starter Prompts',
|
label: 'Starter Prompts',
|
||||||
@@ -34,10 +33,6 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
|||||||
label: 'Chat Feedback',
|
label: 'Chat Feedback',
|
||||||
id: 'chatFeedback'
|
id: 'chatFeedback'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Allowed Domains',
|
|
||||||
id: 'allowedDomains'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Analyse Chatflow',
|
label: 'Analyse Chatflow',
|
||||||
id: 'analyseChatflow'
|
id: 'analyseChatflow'
|
||||||
@@ -94,8 +89,8 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1.25rem' }} id='alert-dialog-title'>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>{dialogProps.title}</div>
|
{dialogProps.title}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -115,7 +110,13 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
>
|
>
|
||||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
sx={{ minHeight: '40px', height: '40px', textAlign: 'left', display: 'flex', alignItems: 'start', mb: 1 }}
|
sx={{
|
||||||
|
minHeight: '40px',
|
||||||
|
height: '40px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 1
|
||||||
|
}}
|
||||||
key={index}
|
key={index}
|
||||||
label={item.label}
|
label={item.label}
|
||||||
{...a11yProps(index)}
|
{...a11yProps(index)}
|
||||||
@@ -124,12 +125,11 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||||
<TabPanel key={index} value={tabValue} index={index}>
|
<TabPanel key={index} value={tabValue} index={index}>
|
||||||
{item.id === 'rateLimiting' && <RateLimit />}
|
{item.id === 'security' && <Security dialogProps={dialogProps} />}
|
||||||
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
|
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'followUpPrompts' ? <FollowUpPrompts dialogProps={dialogProps} /> : null}
|
{item.id === 'followUpPrompts' ? <FollowUpPrompts dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
|
||||||
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'fileUpload' ? <FileUpload dialogProps={dialogProps} /> : null}
|
{item.id === 'fileUpload' ? <FileUpload dialogProps={dialogProps} /> : null}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
|||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Button, IconButton, OutlinedInput, Box, List, InputAdornment, Typography } from '@mui/material'
|
import { Button, IconButton, OutlinedInput, Box, InputAdornment, Stack, Typography } from '@mui/material'
|
||||||
import { IconX, IconTrash, IconPlus } from '@tabler/icons-react'
|
import { IconX, IconTrash, IconPlus } from '@tabler/icons-react'
|
||||||
|
|
||||||
// Project import
|
// Project import
|
||||||
@@ -118,23 +118,17 @@ const AllowedDomains = ({ dialogProps }) => {
|
|||||||
}, [dialogProps])
|
}, [dialogProps])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||||
<Box>
|
<Typography variant='h3'>
|
||||||
<Box
|
Allowed Domains
|
||||||
sx={{
|
<TooltipWithParser
|
||||||
display: 'flex',
|
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||||
flexDirection: 'column'
|
title={'Your chatbot will only work when used from the following domains.'}
|
||||||
}}
|
/>
|
||||||
>
|
</Typography>
|
||||||
<Typography sx={{ mb: 1 }}>
|
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||||
Allowed Domains
|
<Stack direction='column' spacing={2}>
|
||||||
<TooltipWithParser
|
<Typography>Domains</Typography>
|
||||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
|
||||||
title={'Your chatbot will only work when used from the following domains.'}
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<List>
|
|
||||||
{inputFields.map((origin, index) => {
|
{inputFields.map((origin, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||||
@@ -176,11 +170,9 @@ const AllowedDomains = ({ dialogProps }) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</List>
|
</Stack>
|
||||||
</Box>
|
<Stack direction='column' spacing={1}>
|
||||||
<Box sx={{ pt: 2, pb: 2 }}>
|
<Typography>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
|
||||||
<Typography sx={{ mb: 1 }}>
|
|
||||||
Error Message
|
Error Message
|
||||||
<TooltipWithParser
|
<TooltipWithParser
|
||||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||||
@@ -198,12 +190,12 @@ const AllowedDomains = ({ dialogProps }) => {
|
|||||||
setErrorMessage(e.target.value)
|
setErrorMessage(e.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Stack>
|
||||||
</Box>
|
</Stack>
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
<StyledButton variant='contained' onClick={onSave}>
|
||||||
Save
|
Save
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { useDispatch } from 'react-redux'
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
|
import parser from 'html-react-parser'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Button, Box, Typography } from '@mui/material'
|
import { Button, Box } from '@mui/material'
|
||||||
import { IconX } from '@tabler/icons-react'
|
import { IconX, IconBulb } from '@tabler/icons-react'
|
||||||
|
|
||||||
// Project import
|
// Project import
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
|
||||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
|
||||||
// store
|
// store
|
||||||
@@ -18,7 +18,9 @@ import useNotifier from '@/utils/useNotifier'
|
|||||||
// API
|
// API
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import chatflowsApi from '@/api/chatflows'
|
||||||
|
|
||||||
const message = `Allow files to be uploaded from the chat. Uploaded files will be parsed as string and sent to LLM. If File Upload is enabled on Vector Store as well, this will override and takes precedence.`
|
const message = `Uploaded files will be parsed as strings and sent to the LLM. If file upload is enabled on the Vector Store as well, this will override and take precedence.
|
||||||
|
<br />
|
||||||
|
Refer <a href='https://docs.flowiseai.com/using-flowise/uploads#files' target='_blank'>docs</a> for more details.`
|
||||||
|
|
||||||
const FileUpload = ({ dialogProps }) => {
|
const FileUpload = ({ dialogProps }) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
@@ -99,15 +101,41 @@ const FileUpload = ({ dialogProps }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', mb: 2 }}>
|
<Box
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
sx={{
|
||||||
<Typography>
|
width: '100%',
|
||||||
Enable Full File Upload
|
display: 'flex',
|
||||||
<TooltipWithParser style={{ marginLeft: 10 }} title={message} />
|
flexDirection: 'column',
|
||||||
</Typography>
|
alignItems: 'start',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 3,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: '#d8f3dc',
|
||||||
|
width: '100%',
|
||||||
|
padding: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconBulb size={30} color='#2d6a4f' />
|
||||||
|
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>{parser(message)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SwitchInput onChange={handleChange} value={fullFileUpload} />
|
<SwitchInput label='Enable Full File Upload' onChange={handleChange} value={fullFileUpload} />
|
||||||
</Box>
|
</Box>
|
||||||
|
{/* TODO: Allow selection of allowed file types*/}
|
||||||
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||||
Save
|
Save
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|||||||
@@ -0,0 +1,430 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
Card
|
||||||
|
} from '@mui/material'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
|
import { IconX, IconBox, IconVariable } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import useApi from '@/hooks/useApi'
|
||||||
|
import chatflowsApi from '@/api/chatflows'
|
||||||
|
import configApi from '@/api/config'
|
||||||
|
import variablesApi from '@/api/variables'
|
||||||
|
|
||||||
|
// utils
|
||||||
|
|
||||||
|
const OverrideConfigTable = ({ columns, onToggle, rows, sx }) => {
|
||||||
|
const handleChange = (enabled, row) => {
|
||||||
|
onToggle(row, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table size='small' sx={{ minWidth: 650, ...sx }} aria-label='simple table'>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((col, index) => (
|
||||||
|
<TableCell key={index}>{col.charAt(0).toUpperCase() + col.slice(1)}</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
|
{Object.keys(row).map((key, index) => {
|
||||||
|
if (key !== 'id') {
|
||||||
|
return (
|
||||||
|
<TableCell key={index}>
|
||||||
|
{key === 'enabled' ? (
|
||||||
|
<SwitchInput onChange={(enabled) => handleChange(enabled, row)} value={row.enabled} />
|
||||||
|
) : (
|
||||||
|
row[key]
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideConfigTable.propTypes = {
|
||||||
|
rows: PropTypes.array,
|
||||||
|
columns: PropTypes.array,
|
||||||
|
sx: PropTypes.object,
|
||||||
|
onToggle: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
const OverrideConfig = ({ dialogProps }) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||||
|
const chatflowid = chatflow.id
|
||||||
|
const apiConfig = chatflow.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [nodeConfig, setNodeConfig] = useState(null)
|
||||||
|
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||||
|
const [overrideConfigStatus, setOverrideConfigStatus] = useState(
|
||||||
|
apiConfig?.overrideConfig?.status !== undefined ? apiConfig.overrideConfig.status : false
|
||||||
|
)
|
||||||
|
const [nodeOverrides, setNodeOverrides] = useState(apiConfig?.overrideConfig?.nodes !== undefined ? apiConfig.overrideConfig.nodes : {})
|
||||||
|
const [variableOverrides, setVariableOverrides] = useState(
|
||||||
|
apiConfig?.overrideConfig?.variables !== undefined ? apiConfig.overrideConfig.variables : []
|
||||||
|
)
|
||||||
|
|
||||||
|
const getConfigApi = useApi(configApi.getConfig)
|
||||||
|
const getAllVariablesApi = useApi(variablesApi.getAllVariables)
|
||||||
|
|
||||||
|
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||||
|
const accordianNodes = { ...nodeConfigExpanded }
|
||||||
|
accordianNodes[nodeLabel] = isExpanded
|
||||||
|
setNodeConfigExpanded(accordianNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatObj = () => {
|
||||||
|
const obj = {
|
||||||
|
overrideConfig: { status: overrideConfigStatus }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrideConfigStatus) {
|
||||||
|
obj.overrideConfig = {
|
||||||
|
...obj.overrideConfig,
|
||||||
|
nodes: nodeOverrides,
|
||||||
|
variables: variableOverrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNodeOverrideToggle = (node, property, status) => {
|
||||||
|
setNodeOverrides((prev) => {
|
||||||
|
const newConfig = { ...prev }
|
||||||
|
newConfig[node] = newConfig[node].map((item) => {
|
||||||
|
if (item.name === property) {
|
||||||
|
item.enabled = status
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
return newConfig
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onVariableOverrideToggle = (id, status) => {
|
||||||
|
setVariableOverrides((prev) => {
|
||||||
|
return prev.map((item) => {
|
||||||
|
if (item.id === id) {
|
||||||
|
item.enabled = status
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupByNodeLabel = (nodes) => {
|
||||||
|
const result = {}
|
||||||
|
const newNodeOverrides = {}
|
||||||
|
const seenNodes = new Set()
|
||||||
|
|
||||||
|
nodes.forEach((item) => {
|
||||||
|
const { node, nodeId, label, name, type } = item
|
||||||
|
seenNodes.add(node)
|
||||||
|
|
||||||
|
if (!result[node]) {
|
||||||
|
result[node] = {
|
||||||
|
nodeIds: [],
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newNodeOverrides[node]) {
|
||||||
|
// If overrideConfigStatus is true, copy existing config for this node
|
||||||
|
newNodeOverrides[node] = overrideConfigStatus ? [...(nodeOverrides[node] || [])] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
||||||
|
|
||||||
|
const param = { label, name, type }
|
||||||
|
|
||||||
|
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
||||||
|
result[node].params.push(param)
|
||||||
|
const paramExists = newNodeOverrides[node].some(
|
||||||
|
(existingParam) => existingParam.label === label && existingParam.name === name && existingParam.type === type
|
||||||
|
)
|
||||||
|
if (!paramExists) {
|
||||||
|
newNodeOverrides[node].push({ ...param, enabled: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort the nodeIds array
|
||||||
|
for (const node in result) {
|
||||||
|
result[node].nodeIds.sort()
|
||||||
|
}
|
||||||
|
setNodeConfig(result)
|
||||||
|
|
||||||
|
if (!overrideConfigStatus) {
|
||||||
|
setNodeOverrides(newNodeOverrides)
|
||||||
|
} else {
|
||||||
|
const updatedNodeOverrides = { ...nodeOverrides }
|
||||||
|
|
||||||
|
Object.keys(updatedNodeOverrides).forEach((node) => {
|
||||||
|
if (!seenNodes.has(node)) {
|
||||||
|
delete updatedNodeOverrides[node]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
seenNodes.forEach((node) => {
|
||||||
|
if (!updatedNodeOverrides[node]) {
|
||||||
|
updatedNodeOverrides[node] = newNodeOverrides[node]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setNodeOverrides(updatedNodeOverrides)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupByVariableLabel = (variables) => {
|
||||||
|
const newVariables = []
|
||||||
|
const seenVariables = new Set()
|
||||||
|
|
||||||
|
variables.forEach((item) => {
|
||||||
|
const { id, name, type } = item
|
||||||
|
seenVariables.add(id)
|
||||||
|
|
||||||
|
const param = { id, name, type }
|
||||||
|
const existingVariable = variableOverrides?.find((existingParam) => existingParam.id === id)
|
||||||
|
|
||||||
|
if (existingVariable) {
|
||||||
|
if (!newVariables.some((existingVariable) => existingVariable.id === id)) {
|
||||||
|
newVariables.push({ ...existingVariable })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!newVariables.some((existingVariable) => existingVariable.id === id)) {
|
||||||
|
newVariables.push({ ...param, enabled: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (variableOverrides) {
|
||||||
|
variableOverrides.forEach((existingVariable) => {
|
||||||
|
if (!seenVariables.has(existingVariable.id)) {
|
||||||
|
const index = newVariables.findIndex((newVariable) => newVariable.id === existingVariable.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
newVariables.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setVariableOverrides(newVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOverrideConfigSave = async () => {
|
||||||
|
try {
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(chatflowid, {
|
||||||
|
apiConfig: JSON.stringify(formatObj())
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Override Configuration Saved',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Override Configuration: ${
|
||||||
|
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) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
getConfigApi.request(dialogProps.chatflow.id)
|
||||||
|
getAllVariablesApi.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getConfigApi.data) {
|
||||||
|
groupByNodeLabel(getConfigApi.data)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getConfigApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllVariablesApi.data) {
|
||||||
|
groupByVariableLabel(getAllVariablesApi.data)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAllVariablesApi.data])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||||
|
<Typography variant='h3'>
|
||||||
|
Override Configuration
|
||||||
|
<TooltipWithParser
|
||||||
|
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||||
|
title={
|
||||||
|
'Enable or disable which properties of the flow configuration can be overridden. Refer to the <a href="https://docs.flowiseai.com/using-flowise/api#override-config" target="_blank">documentation</a> for more information.'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||||
|
<SwitchInput label='Enable Override Configuration' onChange={setOverrideConfigStatus} value={overrideConfigStatus} />
|
||||||
|
{overrideConfigStatus && (
|
||||||
|
<>
|
||||||
|
{nodeOverrides && nodeConfig && (
|
||||||
|
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
|
||||||
|
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
|
||||||
|
<IconBox />
|
||||||
|
<Typography variant='h4'>Nodes</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Stack direction='column'>
|
||||||
|
{Object.keys(nodeOverrides)
|
||||||
|
.sort()
|
||||||
|
.map((nodeLabel) => (
|
||||||
|
<Accordion
|
||||||
|
expanded={nodeConfigExpanded[nodeLabel] || false}
|
||||||
|
onChange={handleAccordionChange(nodeLabel)}
|
||||||
|
key={nodeLabel}
|
||||||
|
disableGutters
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls={`nodes-accordian-${nodeLabel}`}
|
||||||
|
id={`nodes-accordian-header-${nodeLabel}`}
|
||||||
|
>
|
||||||
|
<Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Typography variant='h5'>{nodeLabel}</Typography>
|
||||||
|
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
|
||||||
|
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: 'max-content',
|
||||||
|
borderRadius: 15,
|
||||||
|
background: 'rgb(254,252,191)',
|
||||||
|
padding: 5,
|
||||||
|
paddingLeft: 10,
|
||||||
|
paddingRight: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: 'rgb(116,66,16)',
|
||||||
|
fontSize: '0.825rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nodeId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ p: 0 }}>
|
||||||
|
<OverrideConfigTable
|
||||||
|
rows={nodeOverrides[nodeLabel]}
|
||||||
|
columns={
|
||||||
|
nodeOverrides[nodeLabel].length > 0
|
||||||
|
? Object.keys(nodeOverrides[nodeLabel][0])
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
onToggle={(property, status) =>
|
||||||
|
onNodeOverrideToggle(nodeLabel, property.name, status)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{variableOverrides && (
|
||||||
|
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
|
||||||
|
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
|
||||||
|
<IconVariable />
|
||||||
|
<Typography variant='h4'>Variables</Typography>
|
||||||
|
</Stack>
|
||||||
|
<OverrideConfigTable
|
||||||
|
rows={variableOverrides}
|
||||||
|
columns={['name', 'type', 'enabled']}
|
||||||
|
onToggle={(property, status) => onVariableOverrideToggle(property.id, status)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<StyledButton variant='contained' onClick={onOverrideConfigSave}>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideConfig.propTypes = {
|
||||||
|
dialogProps: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OverrideConfig
|
||||||
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { Box, Typography, Button, OutlinedInput } from '@mui/material'
|
import { Typography, Button, OutlinedInput, Stack } from '@mui/material'
|
||||||
|
|
||||||
// Project import
|
// Project import
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
@@ -126,55 +126,49 @@ const RateLimit = () => {
|
|||||||
|
|
||||||
const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => {
|
const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ pt: 2, pb: 2 }}>
|
<Stack direction='column' spacing={1}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
<Typography>{fieldLabel}</Typography>
|
||||||
<Typography sx={{ mb: 1 }}>{fieldLabel}</Typography>
|
<OutlinedInput
|
||||||
<OutlinedInput
|
id={fieldName}
|
||||||
id={fieldName}
|
type={fieldType}
|
||||||
type={fieldType}
|
fullWidth
|
||||||
fullWidth
|
value={message}
|
||||||
value={message}
|
placeholder={placeholder}
|
||||||
placeholder={placeholder}
|
name={fieldName}
|
||||||
name={fieldName}
|
size='small'
|
||||||
size='small'
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
onTextChanged(e.target.value, fieldName)
|
||||||
onTextChanged(e.target.value, fieldName)
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Stack>
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||||
{/*Rate Limit*/}
|
<Typography variant='h3'>
|
||||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
|
||||||
Rate Limit{' '}
|
Rate Limit{' '}
|
||||||
<TooltipWithParser
|
<TooltipWithParser
|
||||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
style={{ marginLeft: 10 }}
|
||||||
title={
|
title={
|
||||||
'Visit <a target="_blank" href="https://docs.flowiseai.com/rate-limit">Rate Limit Setup Guide</a> to set up Rate Limit correctly in your hosting environment.'
|
'Visit <a target="_blank" href="https://docs.flowiseai.com/rate-limit">Rate Limit Setup Guide</a> to set up Rate Limit correctly in your hosting environment.'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
<SwitchInput label='Enable Rate Limit' onChange={handleChange} value={rateLimitStatus} />
|
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||||
{rateLimitStatus && (
|
<SwitchInput label='Enable Rate Limit' onChange={handleChange} value={rateLimitStatus} />
|
||||||
<>
|
{rateLimitStatus && (
|
||||||
{textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number', '5')}
|
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||||
{textField(limitDuration, 'limitDuration', 'Duration in Second', 'number', '60')}
|
{textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number', '5')}
|
||||||
{textField(limitMsg, 'limitMsg', 'Limit Message', 'string', 'You have reached the quota')}
|
{textField(limitDuration, 'limitDuration', 'Duration in Second', 'number', '60')}
|
||||||
</>
|
{textField(limitMsg, 'limitMsg', 'Limit Message', 'string', 'You have reached the quota')}
|
||||||
)}
|
</Stack>
|
||||||
<StyledButton
|
)}
|
||||||
disabled={checkDisabled()}
|
</Stack>
|
||||||
style={{ marginBottom: 10, marginTop: 10 }}
|
<StyledButton disabled={checkDisabled()} variant='contained' onClick={() => onSave()} sx={{ width: 'auto' }}>
|
||||||
variant='contained'
|
Save
|
||||||
onClick={() => onSave()}
|
|
||||||
>
|
|
||||||
Save Changes
|
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Divider, Stack } from '@mui/material'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||||
|
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||||
|
import OverrideConfig from './OverrideConfig'
|
||||||
|
|
||||||
|
const Security = ({ dialogProps }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction='column' divider={<Divider sx={{ my: 0.5, borderColor: theme.palette.grey[900] + 25 }} />} spacing={4}>
|
||||||
|
<RateLimit />
|
||||||
|
<AllowedDomains dialogProps={dialogProps} />
|
||||||
|
<OverrideConfig dialogProps={dialogProps} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Security.propTypes = {
|
||||||
|
dialogProps: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Security
|
||||||
@@ -248,9 +248,7 @@ const SpeechToText = ({ dialogProps }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box fullWidth sx={{ mb: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box fullWidth sx={{ mb: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
<Typography>Providers</Typography>
|
||||||
Providers
|
|
||||||
</Typography>
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Select size='small' value={selectedProvider} onChange={handleProviderChange}>
|
<Select size='small' value={selectedProvider} onChange={handleProviderChange}>
|
||||||
<MenuItem value='none'>None</MenuItem>
|
<MenuItem value='none'>None</MenuItem>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper } from '@mui/material'
|
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper, Chip } from '@mui/material'
|
||||||
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
|
|
||||||
export const TableViewOnly = ({ columns, rows, sx }) => {
|
export const TableViewOnly = ({ columns, rows, sx }) => {
|
||||||
return (
|
return (
|
||||||
@@ -9,16 +10,44 @@ export const TableViewOnly = ({ columns, rows, sx }) => {
|
|||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
{columns.map((col, index) => (
|
{columns.map((col, index) => (
|
||||||
<TableCell key={index}>{col.charAt(0).toUpperCase() + col.slice(1)}</TableCell>
|
<TableCell key={index}>
|
||||||
|
{col === 'enabled' ? (
|
||||||
|
<>
|
||||||
|
Override
|
||||||
|
<TooltipWithParser
|
||||||
|
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||||
|
title={
|
||||||
|
'If enabled, this variable can be overridden in API calls and embeds. If disabled, any overrides will be ignored. To change this, go to Security settings in Chatflow Configuration.'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
col.charAt(0).toUpperCase() + col.slice(1)
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{rows.map((row, index) => (
|
{rows.map((row, index) => (
|
||||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
{Object.keys(row).map((key, index) => (
|
{Object.keys(row).map((key, index) => {
|
||||||
<TableCell key={index}>{row[key]}</TableCell>
|
if (key !== 'id') {
|
||||||
))}
|
return (
|
||||||
|
<TableCell key={index}>
|
||||||
|
{key === 'enabled' ? (
|
||||||
|
row[key] ? (
|
||||||
|
<Chip label='Enabled' color='primary' />
|
||||||
|
) : (
|
||||||
|
<Chip label='Disabled' />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
row[key]
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
|||||||
onCancel={() => setFlowDialogOpen(false)}
|
onCancel={() => setFlowDialogOpen(false)}
|
||||||
onConfirm={onConfirmSaveName}
|
onConfirm={onConfirmSaveName}
|
||||||
/>
|
/>
|
||||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
{apiDialogOpen && <APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />}
|
||||||
<ViewMessagesDialog
|
<ViewMessagesDialog
|
||||||
show={viewMessagesDialogOpen}
|
show={viewMessagesDialogOpen}
|
||||||
dialogProps={viewMessagesDialogProps}
|
dialogProps={viewMessagesDialogProps}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -15,10 +15,12 @@ import {
|
|||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
Typography,
|
Typography,
|
||||||
Stack
|
Stack,
|
||||||
|
Card
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
// Project import
|
// Project import
|
||||||
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||||
@@ -36,12 +38,13 @@ import cURLSVG from '@/assets/images/cURL.svg'
|
|||||||
import EmbedSVG from '@/assets/images/embed.svg'
|
import EmbedSVG from '@/assets/images/embed.svg'
|
||||||
import ShareChatbotSVG from '@/assets/images/sharing.png'
|
import ShareChatbotSVG from '@/assets/images/sharing.png'
|
||||||
import settingsSVG from '@/assets/images/settings.svg'
|
import settingsSVG from '@/assets/images/settings.svg'
|
||||||
import { IconBulb } from '@tabler/icons-react'
|
import { IconBulb, IconBox, IconVariable } from '@tabler/icons-react'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import apiKeyApi from '@/api/apikey'
|
import apiKeyApi from '@/api/apikey'
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import chatflowsApi from '@/api/chatflows'
|
||||||
import configApi from '@/api/config'
|
import configApi from '@/api/config'
|
||||||
|
import variablesApi from '@/api/variables'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
@@ -83,6 +86,10 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
const theme = useTheme()
|
||||||
|
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||||
|
const apiConfig = chatflow?.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
|
||||||
|
const overrideConfigStatus = apiConfig?.overrideConfig?.status !== undefined ? apiConfig.overrideConfig.status : false
|
||||||
|
|
||||||
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
|
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
|
||||||
const [value, setValue] = useState(0)
|
const [value, setValue] = useState(0)
|
||||||
@@ -93,16 +100,20 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const [checkboxVal, setCheckbox] = useState(false)
|
const [checkboxVal, setCheckbox] = useState(false)
|
||||||
const [nodeConfig, setNodeConfig] = useState({})
|
const [nodeConfig, setNodeConfig] = useState({})
|
||||||
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||||
|
const [nodeOverrides, setNodeOverrides] = useState(apiConfig?.overrideConfig?.nodes ?? null)
|
||||||
|
const [variableOverrides, setVariableOverrides] = useState(apiConfig?.overrideConfig?.variables ?? [])
|
||||||
|
|
||||||
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||||
const getConfigApi = useApi(configApi.getConfig)
|
const getConfigApi = useApi(configApi.getConfig)
|
||||||
|
const getAllVariablesApi = useApi(variablesApi.getAllVariables)
|
||||||
|
|
||||||
const onCheckBoxChanged = (newVal) => {
|
const onCheckBoxChanged = (newVal) => {
|
||||||
setCheckbox(newVal)
|
setCheckbox(newVal)
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
getConfigApi.request(dialogProps.chatflowid)
|
getConfigApi.request(dialogProps.chatflowid)
|
||||||
|
getAllVariablesApi.request()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +132,12 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
|
|
||||||
const groupByNodeLabel = (nodes) => {
|
const groupByNodeLabel = (nodes) => {
|
||||||
const result = {}
|
const result = {}
|
||||||
|
const newNodeOverrides = {}
|
||||||
|
const seenNodes = new Set()
|
||||||
|
|
||||||
nodes.forEach((item) => {
|
nodes.forEach((item) => {
|
||||||
const { node, nodeId, label, name, type } = item
|
const { node, nodeId, label, name, type } = item
|
||||||
|
seenNodes.add(node)
|
||||||
|
|
||||||
if (!result[node]) {
|
if (!result[node]) {
|
||||||
result[node] = {
|
result[node] = {
|
||||||
@@ -132,12 +146,23 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!newNodeOverrides[node]) {
|
||||||
|
// If overrideConfigStatus is true, copy existing config for this node
|
||||||
|
newNodeOverrides[node] = overrideConfigStatus ? [...(nodeOverrides[node] || [])] : []
|
||||||
|
}
|
||||||
|
|
||||||
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
||||||
|
|
||||||
const param = { label, name, type }
|
const param = { label, name, type }
|
||||||
|
|
||||||
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
||||||
result[node].params.push(param)
|
result[node].params.push(param)
|
||||||
|
const paramExists = newNodeOverrides[node].some(
|
||||||
|
(existingParam) => existingParam.label === label && existingParam.name === name && existingParam.type === type
|
||||||
|
)
|
||||||
|
if (!paramExists) {
|
||||||
|
newNodeOverrides[node].push({ ...param, enabled: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -145,8 +170,73 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
for (const node in result) {
|
for (const node in result) {
|
||||||
result[node].nodeIds.sort()
|
result[node].nodeIds.sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
setNodeConfig(result)
|
setNodeConfig(result)
|
||||||
|
|
||||||
|
if (!overrideConfigStatus) {
|
||||||
|
setNodeOverrides(newNodeOverrides)
|
||||||
|
} else {
|
||||||
|
const updatedNodeOverrides = { ...nodeOverrides }
|
||||||
|
|
||||||
|
Object.keys(updatedNodeOverrides).forEach((node) => {
|
||||||
|
if (!seenNodes.has(node)) {
|
||||||
|
delete updatedNodeOverrides[node]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
seenNodes.forEach((node) => {
|
||||||
|
if (!updatedNodeOverrides[node]) {
|
||||||
|
updatedNodeOverrides[node] = newNodeOverrides[node]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setNodeOverrides(updatedNodeOverrides)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupByVariableLabel = (variables) => {
|
||||||
|
const newVariables = []
|
||||||
|
const seenVariables = new Set()
|
||||||
|
|
||||||
|
variables.forEach((item) => {
|
||||||
|
const { id, name, type } = item
|
||||||
|
seenVariables.add(id)
|
||||||
|
|
||||||
|
const param = { id, name, type }
|
||||||
|
|
||||||
|
// If overrideConfigStatus is true, look for existing variable config
|
||||||
|
// Otherwise, create new default config
|
||||||
|
if (overrideConfigStatus) {
|
||||||
|
const existingVariable = variableOverrides?.find((existingParam) => existingParam.id === id)
|
||||||
|
if (existingVariable) {
|
||||||
|
if (!newVariables.some((variable) => variable.id === id)) {
|
||||||
|
newVariables.push({ ...existingVariable })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!newVariables.some((variable) => variable.id === id)) {
|
||||||
|
newVariables.push({ ...param, enabled: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When no override config exists, create default values
|
||||||
|
if (!newVariables.some((variable) => variable.id === id)) {
|
||||||
|
newVariables.push({ ...param, enabled: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// If overrideConfigStatus is true, clean up any variables that no longer exist
|
||||||
|
if (overrideConfigStatus && variableOverrides) {
|
||||||
|
variableOverrides.forEach((existingVariable) => {
|
||||||
|
if (!seenVariables.has(existingVariable.id)) {
|
||||||
|
const index = newVariables.findIndex((newVariable) => newVariable.id === existingVariable.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
newVariables.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setVariableOverrides(newVariables)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||||
@@ -165,8 +255,16 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
if (getConfigApi.data) {
|
if (getConfigApi.data) {
|
||||||
groupByNodeLabel(getConfigApi.data)
|
groupByNodeLabel(getConfigApi.data)
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getConfigApi.data])
|
}, [getConfigApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllVariablesApi.data) {
|
||||||
|
groupByVariableLabel(getAllVariablesApi.data)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAllVariablesApi.data])
|
||||||
|
|
||||||
const handleChange = (event, newValue) => {
|
const handleChange = (event, newValue) => {
|
||||||
setValue(newValue)
|
setValue(newValue)
|
||||||
}
|
}
|
||||||
@@ -625,55 +723,85 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
|||||||
showLineNumbers={false}
|
showLineNumbers={false}
|
||||||
wrapLines
|
wrapLines
|
||||||
/>
|
/>
|
||||||
<CheckboxInput label='Show Input Config' value={checkboxVal} onChange={onCheckBoxChanged} />
|
<CheckboxInput label='Show Override Config' value={checkboxVal} onChange={onCheckBoxChanged} />
|
||||||
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
|
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{Object.keys(nodeConfig)
|
<Typography sx={{ mt: 2, mb: 3 }}>
|
||||||
.sort()
|
You can override existing input configuration of the chatflow with overrideConfig property.
|
||||||
.map((nodeLabel) => (
|
</Typography>
|
||||||
<Accordion
|
<Stack direction='column' spacing={2} sx={{ width: '100%', my: 2 }}>
|
||||||
expanded={nodeConfigExpanded[nodeLabel] || false}
|
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
|
||||||
onChange={handleAccordionChange(nodeLabel)}
|
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
|
||||||
key={nodeLabel}
|
<IconBox />
|
||||||
disableGutters
|
<Typography variant='h4'>Nodes</Typography>
|
||||||
>
|
</Stack>
|
||||||
<AccordionSummary
|
{Object.keys(nodeConfig)
|
||||||
expandIcon={<ExpandMoreIcon />}
|
.sort()
|
||||||
aria-controls={`nodes-accordian-${nodeLabel}`}
|
.map((nodeLabel) => (
|
||||||
id={`nodes-accordian-header-${nodeLabel}`}
|
<Accordion
|
||||||
>
|
expanded={nodeConfigExpanded[nodeLabel] || false}
|
||||||
<Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
onChange={handleAccordionChange(nodeLabel)}
|
||||||
<Typography variant='h5'>{nodeLabel}</Typography>
|
key={nodeLabel}
|
||||||
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
|
disableGutters
|
||||||
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
|
>
|
||||||
<div
|
<AccordionSummary
|
||||||
key={index}
|
expandIcon={<ExpandMoreIcon />}
|
||||||
style={{
|
aria-controls={`nodes-accordian-${nodeLabel}`}
|
||||||
display: 'flex',
|
id={`nodes-accordian-header-${nodeLabel}`}
|
||||||
flexDirection: 'row',
|
>
|
||||||
width: 'max-content',
|
<Stack
|
||||||
borderRadius: 15,
|
flexDirection='row'
|
||||||
background: 'rgb(254,252,191)',
|
sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}
|
||||||
padding: 5,
|
>
|
||||||
paddingLeft: 10,
|
<Typography variant='h5'>{nodeLabel}</Typography>
|
||||||
paddingRight: 10
|
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
|
||||||
}}
|
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
|
||||||
>
|
<div
|
||||||
<span style={{ color: 'rgb(116,66,16)', fontSize: '0.825rem' }}>
|
key={index}
|
||||||
{nodeId}
|
style={{
|
||||||
</span>
|
display: 'flex',
|
||||||
</div>
|
flexDirection: 'row',
|
||||||
))}
|
width: 'max-content',
|
||||||
</Stack>
|
borderRadius: 15,
|
||||||
</AccordionSummary>
|
background: 'rgb(254,252,191)',
|
||||||
<AccordionDetails>
|
padding: 5,
|
||||||
<TableViewOnly
|
paddingLeft: 10,
|
||||||
rows={nodeConfig[nodeLabel].params}
|
paddingRight: 10
|
||||||
columns={Object.keys(nodeConfig[nodeLabel].params[0]).slice(-3)}
|
}}
|
||||||
/>
|
>
|
||||||
</AccordionDetails>
|
<span
|
||||||
</Accordion>
|
style={{
|
||||||
))}
|
color: 'rgb(116,66,16)',
|
||||||
|
fontSize: '0.825rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nodeId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<TableViewOnly
|
||||||
|
rows={nodeOverrides[nodeLabel]}
|
||||||
|
columns={
|
||||||
|
nodeOverrides[nodeLabel].length > 0
|
||||||
|
? Object.keys(nodeOverrides[nodeLabel][0])
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
|
||||||
|
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
|
||||||
|
<IconVariable />
|
||||||
|
<Typography variant='h4'>Variables</Typography>
|
||||||
|
</Stack>
|
||||||
|
<TableViewOnly rows={variableOverrides} columns={['name', 'type', 'enabled']} />
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
<CopyBlock
|
<CopyBlock
|
||||||
theme={atomOneDark}
|
theme={atomOneDark}
|
||||||
text={
|
text={
|
||||||
|
|||||||
@@ -140,9 +140,9 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => {
|
|||||||
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
|
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
|
||||||
|
|
||||||
if (renderHTML) {
|
if (renderHTML) {
|
||||||
obj.overrideConfig.renderHTML = true
|
obj.renderHTML = true
|
||||||
} else {
|
} else {
|
||||||
obj.overrideConfig.renderHTML = false
|
obj.renderHTML = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts
|
if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts
|
||||||
|
|||||||
Reference in New Issue
Block a user