mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +03:00
Feature/Custom Function to Seq Agent (#3612)
* add custom function to seq agent * add seqExecuteFlow node
This commit is contained in:
@@ -205,7 +205,7 @@ class Agent_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Agent'
|
this.label = 'Agent'
|
||||||
this.name = 'seqAgent'
|
this.name = 'seqAgent'
|
||||||
this.version = 4.0
|
this.version = 4.1
|
||||||
this.type = 'Agent'
|
this.type = 'Agent'
|
||||||
this.icon = 'seqAgent.png'
|
this.icon = 'seqAgent.png'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -291,9 +291,11 @@ class Agent_SeqAgents implements INode {
|
|||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Start | Agent | Condition | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Start | Agent | Condition | LLMNode | ToolNode',
|
type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
list: true
|
list: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class Condition_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Condition'
|
this.label = 'Condition'
|
||||||
this.name = 'seqCondition'
|
this.name = 'seqCondition'
|
||||||
this.version = 2.0
|
this.version = 2.1
|
||||||
this.type = 'Condition'
|
this.type = 'Condition'
|
||||||
this.icon = 'condition.svg'
|
this.icon = 'condition.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -112,9 +112,11 @@ class Condition_SeqAgents implements INode {
|
|||||||
placeholder: 'If X, then Y'
|
placeholder: 'If X, then Y'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Start | Agent | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Start | Agent | LLMNode | ToolNode',
|
type: 'Start | Agent | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
list: true
|
list: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class ConditionAgent_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Condition Agent'
|
this.label = 'Condition Agent'
|
||||||
this.name = 'seqConditionAgent'
|
this.name = 'seqConditionAgent'
|
||||||
this.version = 3.0
|
this.version = 3.1
|
||||||
this.type = 'ConditionAgent'
|
this.type = 'ConditionAgent'
|
||||||
this.icon = 'condition.svg'
|
this.icon = 'condition.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -166,9 +166,11 @@ class ConditionAgent_SeqAgents implements INode {
|
|||||||
placeholder: 'Condition Agent'
|
placeholder: 'Condition Agent'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Start | Agent | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Start | Agent | LLMNode | ToolNode',
|
type: 'Start | Agent | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
list: true
|
list: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import { NodeVM } from '@flowiseai/nodevm'
|
||||||
|
import { DataSource } from 'typeorm'
|
||||||
|
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
|
||||||
|
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams, ISeqAgentNode, ISeqAgentsState } from '../../../src/Interface'
|
||||||
|
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages'
|
||||||
|
import { customGet } from '../commonUtils'
|
||||||
|
|
||||||
|
const howToUseCode = `
|
||||||
|
1. Must return a string value at the end of function.
|
||||||
|
|
||||||
|
2. You can get default flow config, including the current "state":
|
||||||
|
- \`$flow.sessionId\`
|
||||||
|
- \`$flow.chatId\`
|
||||||
|
- \`$flow.chatflowId\`
|
||||||
|
- \`$flow.input\`
|
||||||
|
- \`$flow.state\`
|
||||||
|
|
||||||
|
3. You can get custom variables: \`$vars.<variable-name>\`
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
class CustomFunction_SeqAgents implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
version: number
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Custom JS Function'
|
||||||
|
this.name = 'seqCustomFunction'
|
||||||
|
this.version = 1.0
|
||||||
|
this.type = 'CustomFunction'
|
||||||
|
this.icon = 'customfunction.svg'
|
||||||
|
this.category = 'Sequential Agents'
|
||||||
|
this.description = `Execute custom javascript function`
|
||||||
|
this.baseClasses = [this.type]
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'Input Variables',
|
||||||
|
name: 'functionInputVariables',
|
||||||
|
description: 'Input variables can be used in the function with prefix $. For example: $var',
|
||||||
|
type: 'json',
|
||||||
|
optional: true,
|
||||||
|
acceptVariable: true,
|
||||||
|
list: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sequential Node',
|
||||||
|
name: 'sequentialNode',
|
||||||
|
type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
|
list: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Function Name',
|
||||||
|
name: 'functionName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'My Function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Javascript Function',
|
||||||
|
name: 'javascriptFunction',
|
||||||
|
type: 'code',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseCode
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Return Value As',
|
||||||
|
name: 'returnValueAs',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{ label: 'AI Message', name: 'aiMessage' },
|
||||||
|
{ label: 'Human Message', name: 'humanMessage' },
|
||||||
|
{
|
||||||
|
label: 'State Object',
|
||||||
|
name: 'stateObj',
|
||||||
|
description: "Return as state object, ex: { foo: bar }. This will update the custom state 'foo' to 'bar'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 'aiMessage'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||||
|
const functionName = nodeData.inputs?.functionName as string
|
||||||
|
const javascriptFunction = nodeData.inputs?.javascriptFunction as string
|
||||||
|
const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables
|
||||||
|
const appDataSource = options.appDataSource as DataSource
|
||||||
|
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||||
|
const sequentialNodes = nodeData.inputs?.sequentialNode as ISeqAgentNode[]
|
||||||
|
const returnValueAs = nodeData.inputs?.returnValueAs as string
|
||||||
|
|
||||||
|
if (!sequentialNodes || !sequentialNodes.length) throw new Error('Custom function must have a predecessor!')
|
||||||
|
|
||||||
|
const executeFunc = async (state: ISeqAgentsState) => {
|
||||||
|
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||||
|
const flow = {
|
||||||
|
chatflowId: options.chatflowid,
|
||||||
|
sessionId: options.sessionId,
|
||||||
|
chatId: options.chatId,
|
||||||
|
input,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputVars: ICommonObject = {}
|
||||||
|
if (functionInputVariablesRaw) {
|
||||||
|
try {
|
||||||
|
inputVars =
|
||||||
|
typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw)
|
||||||
|
} catch (exception) {
|
||||||
|
throw new Error('Invalid JSON in the Custom Function Input Variables: ' + exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some values might be a stringified JSON, parse it
|
||||||
|
for (const key in inputVars) {
|
||||||
|
let value = inputVars[key]
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = handleEscapeCharacters(value, true)
|
||||||
|
if (value.startsWith('{') && value.endsWith('}')) {
|
||||||
|
try {
|
||||||
|
value = JSON.parse(value)
|
||||||
|
const nodeId = value.id || ''
|
||||||
|
if (nodeId) {
|
||||||
|
const messages = state.messages as unknown as BaseMessage[]
|
||||||
|
const content = messages.find((msg) => msg.additional_kwargs?.nodeId === nodeId)?.content
|
||||||
|
if (content) {
|
||||||
|
value = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith('$flow.')) {
|
||||||
|
const variableValue = customGet(flow, value.replace('$flow.', ''))
|
||||||
|
if (variableValue) {
|
||||||
|
value = variableValue
|
||||||
|
}
|
||||||
|
} else if (value.startsWith('$vars')) {
|
||||||
|
value = customGet(flow, value.replace('$', ''))
|
||||||
|
}
|
||||||
|
inputVars[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sandbox: any = {
|
||||||
|
$input: input,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
|
if (Object.keys(inputVars).length) {
|
||||||
|
for (const item in inputVars) {
|
||||||
|
sandbox[`$${item}`] = inputVars[item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||||
|
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||||
|
: defaultAllowBuiltInDep
|
||||||
|
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
|
||||||
|
const deps = availableDependencies.concat(externalDeps)
|
||||||
|
|
||||||
|
const nodeVMOptions = {
|
||||||
|
console: 'inherit',
|
||||||
|
sandbox,
|
||||||
|
require: {
|
||||||
|
external: { modules: deps },
|
||||||
|
builtin: builtinDeps
|
||||||
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
try {
|
||||||
|
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)
|
||||||
|
|
||||||
|
if (returnValueAs === 'stateObj') {
|
||||||
|
if (typeof response !== 'object') {
|
||||||
|
throw new Error('Custom function must return an object!')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof response !== 'string') {
|
||||||
|
throw new Error('Custom function must return a string!')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnValueAs === 'humanMessage') {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
new HumanMessage({
|
||||||
|
content: response,
|
||||||
|
additional_kwargs: {
|
||||||
|
nodeId: nodeData.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
new AIMessage({
|
||||||
|
content: response,
|
||||||
|
additional_kwargs: {
|
||||||
|
nodeId: nodeData.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startLLM = sequentialNodes[0].startLLM
|
||||||
|
|
||||||
|
const returnOutput: ISeqAgentNode = {
|
||||||
|
id: nodeData.id,
|
||||||
|
node: executeFunc,
|
||||||
|
name: functionName.toLowerCase().replace(/\s/g, '_').trim(),
|
||||||
|
label: functionName,
|
||||||
|
type: 'utilities',
|
||||||
|
output: 'CustomFunction',
|
||||||
|
llm: startLLM,
|
||||||
|
startLLM,
|
||||||
|
multiModalMessageContent: sequentialNodes[0]?.multiModalMessageContent,
|
||||||
|
predecessorAgents: sequentialNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: CustomFunction_SeqAgents }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.5 27H7.23536C9.13552 27 10.7734 25.6632 11.1542 23.8016L14.3458 8.19842C14.7266 6.3368 16.3645 5 18.2646 5H20" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 11H18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17 17H17.8676C18.5701 17 19.2212 17.3686 19.5826 17.971L24.4174 26.029C24.7788 26.6314 25.4299 27 26.1324 27H27" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M27 17H26.1324C25.4299 17 24.7788 17.3686 24.4174 17.971L19.5826 26.029C19.2212 26.6314 18.5701 27 17.8676 27H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 816 B |
@@ -18,7 +18,7 @@ class End_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'End'
|
this.label = 'End'
|
||||||
this.name = 'seqEnd'
|
this.name = 'seqEnd'
|
||||||
this.version = 2.0
|
this.version = 2.1
|
||||||
this.type = 'End'
|
this.type = 'End'
|
||||||
this.icon = 'end.svg'
|
this.icon = 'end.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -27,9 +27,11 @@ class End_SeqAgents implements INode {
|
|||||||
this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-10.-end-node'
|
this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-10.-end-node'
|
||||||
this.inputs = [
|
this.inputs = [
|
||||||
{
|
{
|
||||||
label: 'Agent | Condition | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Agent | Condition | LLMNode | ToolNode'
|
type: 'Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
this.hideOutput = true
|
this.hideOutput = true
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
import { NodeVM } from '@flowiseai/nodevm'
|
||||||
|
import { DataSource } from 'typeorm'
|
||||||
|
import {
|
||||||
|
availableDependencies,
|
||||||
|
defaultAllowBuiltInDep,
|
||||||
|
getCredentialData,
|
||||||
|
getCredentialParam,
|
||||||
|
getVars,
|
||||||
|
prepareSandboxVars
|
||||||
|
} from '../../../src/utils'
|
||||||
|
import {
|
||||||
|
ICommonObject,
|
||||||
|
IDatabaseEntity,
|
||||||
|
INode,
|
||||||
|
INodeData,
|
||||||
|
INodeOptionsValue,
|
||||||
|
INodeParams,
|
||||||
|
ISeqAgentNode,
|
||||||
|
ISeqAgentsState
|
||||||
|
} from '../../../src/Interface'
|
||||||
|
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
class ExecuteFlow_SeqAgents implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
version: number
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
credential: INodeParams
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Execute Flow'
|
||||||
|
this.name = 'seqExecuteFlow'
|
||||||
|
this.version = 1.0
|
||||||
|
this.type = 'ExecuteFlow'
|
||||||
|
this.icon = 'executeflow.svg'
|
||||||
|
this.category = 'Sequential Agents'
|
||||||
|
this.description = `Execute chatflow/agentflow and return final response`
|
||||||
|
this.baseClasses = [this.type]
|
||||||
|
this.credential = {
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['chatflowApi'],
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'Sequential Node',
|
||||||
|
name: 'sequentialNode',
|
||||||
|
type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
|
list: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
name: 'seqExecuteFlowName',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Select Flow',
|
||||||
|
name: 'selectedFlow',
|
||||||
|
type: 'asyncOptions',
|
||||||
|
loadMethod: 'listFlows'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Input',
|
||||||
|
name: 'seqExecuteFlowInput',
|
||||||
|
type: 'options',
|
||||||
|
description: 'Select one of the following or enter custom input',
|
||||||
|
freeSolo: true,
|
||||||
|
loadPreviousNodes: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '{{ question }}',
|
||||||
|
name: 'userQuestion',
|
||||||
|
description: 'Use the user question from the chat as input.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Override Config',
|
||||||
|
name: 'overrideConfig',
|
||||||
|
description: 'Override the config passed to the flow.',
|
||||||
|
type: 'json',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Base URL',
|
||||||
|
name: 'baseURL',
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'Base URL to Flowise. By default, it is the URL of the incoming request. Useful when you need to execute flow through an alternative route.',
|
||||||
|
placeholder: 'http://localhost:3000',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Start new session per message',
|
||||||
|
name: 'startNewSession',
|
||||||
|
type: 'boolean',
|
||||||
|
description:
|
||||||
|
'Whether to continue the session or start a new one with each interaction. Useful for flows with memory if you want to avoid it.',
|
||||||
|
default: false,
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Return Value As',
|
||||||
|
name: 'returnValueAs',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{ label: 'AI Message', name: 'aiMessage' },
|
||||||
|
{ label: 'Human Message', name: 'humanMessage' },
|
||||||
|
{
|
||||||
|
label: 'State Object',
|
||||||
|
name: 'stateObj',
|
||||||
|
description: "Return as state object, ex: { foo: bar }. This will update the custom state 'foo' to 'bar'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 'aiMessage'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
loadMethods = {
|
||||||
|
async listFlows(_: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> {
|
||||||
|
const returnData: INodeOptionsValue[] = []
|
||||||
|
|
||||||
|
const appDataSource = options.appDataSource as DataSource
|
||||||
|
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||||
|
if (appDataSource === undefined || !appDataSource) {
|
||||||
|
return returnData
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatflows = await appDataSource.getRepository(databaseEntities['ChatFlow']).find()
|
||||||
|
|
||||||
|
for (let i = 0; i < chatflows.length; i += 1) {
|
||||||
|
const data = {
|
||||||
|
label: chatflows[i].name,
|
||||||
|
name: chatflows[i].id
|
||||||
|
} as INodeOptionsValue
|
||||||
|
returnData.push(data)
|
||||||
|
}
|
||||||
|
return returnData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||||
|
const selectedFlowId = nodeData.inputs?.selectedFlow as string
|
||||||
|
const _seqExecuteFlowName = nodeData.inputs?.seqExecuteFlowName as string
|
||||||
|
if (!_seqExecuteFlowName) throw new Error('Execute Flow node name is required!')
|
||||||
|
const seqExecuteFlowName = _seqExecuteFlowName.toLowerCase().replace(/\s/g, '_').trim()
|
||||||
|
const startNewSession = nodeData.inputs?.startNewSession as boolean
|
||||||
|
const appDataSource = options.appDataSource as DataSource
|
||||||
|
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||||
|
const sequentialNodes = nodeData.inputs?.sequentialNode as ISeqAgentNode[]
|
||||||
|
const seqExecuteFlowInput = nodeData.inputs?.seqExecuteFlowInput as string
|
||||||
|
const overrideConfig =
|
||||||
|
typeof nodeData.inputs?.overrideConfig === 'string' &&
|
||||||
|
nodeData.inputs.overrideConfig.startsWith('{') &&
|
||||||
|
nodeData.inputs.overrideConfig.endsWith('}')
|
||||||
|
? JSON.parse(nodeData.inputs.overrideConfig)
|
||||||
|
: nodeData.inputs?.overrideConfig
|
||||||
|
|
||||||
|
if (!sequentialNodes || !sequentialNodes.length) throw new Error('Execute Flow must have a predecessor!')
|
||||||
|
|
||||||
|
const baseURL = (nodeData.inputs?.baseURL as string) || (options.baseURL as string)
|
||||||
|
const returnValueAs = nodeData.inputs?.returnValueAs as string
|
||||||
|
|
||||||
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
|
const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData)
|
||||||
|
|
||||||
|
if (selectedFlowId === options.chatflowid) throw new Error('Cannot call the same agentflow!')
|
||||||
|
|
||||||
|
let headers = {}
|
||||||
|
if (chatflowApiKey) headers = { Authorization: `Bearer ${chatflowApiKey}` }
|
||||||
|
|
||||||
|
const chatflowId = options.chatflowid
|
||||||
|
const sessionId = options.sessionId
|
||||||
|
const chatId = options.chatId
|
||||||
|
|
||||||
|
const executeFunc = async (state: ISeqAgentsState) => {
|
||||||
|
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||||
|
|
||||||
|
let flowInput = ''
|
||||||
|
if (seqExecuteFlowInput === 'userQuestion') {
|
||||||
|
flowInput = input
|
||||||
|
} else if (seqExecuteFlowInput && seqExecuteFlowInput.startsWith('{{') && seqExecuteFlowInput.endsWith('}}')) {
|
||||||
|
const nodeId = seqExecuteFlowInput.replace('{{', '').replace('}}', '').replace('$', '').trim()
|
||||||
|
const messageOutputs = ((state.messages as unknown as BaseMessage[]) ?? []).filter(
|
||||||
|
(message) => message.additional_kwargs && message.additional_kwargs?.nodeId === nodeId
|
||||||
|
)
|
||||||
|
const messageOutput = messageOutputs[messageOutputs.length - 1]
|
||||||
|
|
||||||
|
if (messageOutput) {
|
||||||
|
flowInput = JSON.stringify(messageOutput.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flow = {
|
||||||
|
chatflowId,
|
||||||
|
sessionId,
|
||||||
|
chatId,
|
||||||
|
input: flowInput,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
question: flowInput,
|
||||||
|
chatId: startNewSession ? uuidv4() : chatId,
|
||||||
|
overrideConfig: {
|
||||||
|
sessionId: startNewSession ? uuidv4() : sessionId,
|
||||||
|
...(overrideConfig ?? {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sandbox: ICommonObject = {
|
||||||
|
$input: flowInput,
|
||||||
|
$callOptions: options,
|
||||||
|
$callBody: body,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
|
const code = `
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const url = "${baseURL}/api/v1/prediction/${selectedFlowId}";
|
||||||
|
|
||||||
|
const body = $callBody;
|
||||||
|
|
||||||
|
const options = $callOptions;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
const resp = await response.json();
|
||||||
|
return resp.text;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||||
|
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||||
|
: defaultAllowBuiltInDep
|
||||||
|
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
|
||||||
|
const deps = availableDependencies.concat(externalDeps)
|
||||||
|
|
||||||
|
const nodeVMOptions = {
|
||||||
|
console: 'inherit',
|
||||||
|
sandbox,
|
||||||
|
require: {
|
||||||
|
external: { modules: deps },
|
||||||
|
builtin: builtinDeps
|
||||||
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
try {
|
||||||
|
let response = await vm.run(`module.exports = async function() {${code}}()`, __dirname)
|
||||||
|
|
||||||
|
if (typeof response === 'object') {
|
||||||
|
response = JSON.stringify(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnValueAs === 'humanMessage') {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
new HumanMessage({
|
||||||
|
content: response,
|
||||||
|
additional_kwargs: {
|
||||||
|
nodeId: nodeData.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
new AIMessage({
|
||||||
|
content: response,
|
||||||
|
additional_kwargs: {
|
||||||
|
nodeId: nodeData.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startLLM = sequentialNodes[0].startLLM
|
||||||
|
|
||||||
|
const returnOutput: ISeqAgentNode = {
|
||||||
|
id: nodeData.id,
|
||||||
|
node: executeFunc,
|
||||||
|
name: seqExecuteFlowName,
|
||||||
|
label: _seqExecuteFlowName,
|
||||||
|
type: 'utilities',
|
||||||
|
output: 'ExecuteFlow',
|
||||||
|
llm: startLLM,
|
||||||
|
startLLM,
|
||||||
|
multiModalMessageContent: sequentialNodes[0]?.multiModalMessageContent,
|
||||||
|
predecessorAgents: sequentialNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: ExecuteFlow_SeqAgents }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-schema"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 2h5v4h-5z" /><path d="M15 10h5v4h-5z" /><path d="M5 18h5v4h-5z" /><path d="M5 10h5v4h-5z" /><path d="M10 12h5" /><path d="M7.5 6v4" /><path d="M7.5 14v4" /></svg>
|
||||||
|
After Width: | Height: | Size: 481 B |
@@ -182,7 +182,7 @@ class LLMNode_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'LLM Node'
|
this.label = 'LLM Node'
|
||||||
this.name = 'seqLLMNode'
|
this.name = 'seqLLMNode'
|
||||||
this.version = 4.0
|
this.version = 4.1
|
||||||
this.type = 'LLMNode'
|
this.type = 'LLMNode'
|
||||||
this.icon = 'llmNode.svg'
|
this.icon = 'llmNode.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -261,9 +261,11 @@ class LLMNode_SeqAgents implements INode {
|
|||||||
additionalParams: true
|
additionalParams: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Start | Agent | Condition | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Start | Agent | Condition | LLMNode | ToolNode',
|
type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Start, Agent, Condition, LLM, Tool Node, Custom Function, Execute Flow',
|
||||||
list: true
|
list: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Loop_SeqAgents implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Loop'
|
this.label = 'Loop'
|
||||||
this.name = 'seqLoop'
|
this.name = 'seqLoop'
|
||||||
this.version = 2.0
|
this.version = 2.1
|
||||||
this.type = 'Loop'
|
this.type = 'Loop'
|
||||||
this.icon = 'loop.svg'
|
this.icon = 'loop.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
@@ -26,9 +26,11 @@ class Loop_SeqAgents implements INode {
|
|||||||
this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-9.-loop-node'
|
this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-9.-loop-node'
|
||||||
this.inputs = [
|
this.inputs = [
|
||||||
{
|
{
|
||||||
label: 'Agent | Condition | LLM | Tool Node',
|
label: 'Sequential Node',
|
||||||
name: 'sequentialNode',
|
name: 'sequentialNode',
|
||||||
type: 'Agent | Condition | LLMNode | ToolNode',
|
type: 'Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow',
|
||||||
|
description:
|
||||||
|
'Can be connected to one of the following nodes: Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow',
|
||||||
list: true
|
list: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ export interface INodeParams {
|
|||||||
tabIdentifier?: string
|
tabIdentifier?: string
|
||||||
tabs?: Array<INodeParams>
|
tabs?: Array<INodeParams>
|
||||||
refresh?: boolean
|
refresh?: boolean
|
||||||
|
freeSolo?: boolean
|
||||||
|
loadPreviousNodes?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeExecutionData {
|
export interface INodeExecutionData {
|
||||||
@@ -183,7 +185,7 @@ export interface IMultiAgentNode {
|
|||||||
checkpointMemory?: any
|
checkpointMemory?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeqAgentType = 'agent' | 'condition' | 'end' | 'start' | 'tool' | 'state' | 'llm'
|
type SeqAgentType = 'agent' | 'condition' | 'end' | 'start' | 'tool' | 'state' | 'llm' | 'utilities'
|
||||||
export type ConversationHistorySelection = 'user_question' | 'last_message' | 'all_messages' | 'empty'
|
export type ConversationHistorySelection = 'user_question' | 'last_message' | 'all_messages' | 'empty'
|
||||||
|
|
||||||
export interface ISeqAgentNode {
|
export interface ISeqAgentNode {
|
||||||
|
|||||||
@@ -904,8 +904,8 @@ const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => {
|
|||||||
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', 'seqCustomFunction', 'seqExecuteFlow']
|
||||||
const nodesToAdd = ['seqAgent', 'seqToolNode', 'seqLLMNode']
|
const nodesToAdd = ['seqAgent', 'seqToolNode', 'seqLLMNode', 'seqCustomFunction', 'seqExecuteFlow']
|
||||||
|
|
||||||
if (eligibleSeqNodes.includes(agentNode.data.name)) {
|
if (eligibleSeqNodes.includes(agentNode.data.name)) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const AsyncDropdown = ({
|
|||||||
onCreateNew,
|
onCreateNew,
|
||||||
credentialNames = [],
|
credentialNames = [],
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
freeSolo = false,
|
||||||
disableClearable = false
|
disableClearable = false
|
||||||
}) => {
|
}) => {
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
@@ -114,6 +115,7 @@ export const AsyncDropdown = ({
|
|||||||
<>
|
<>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={name}
|
id={name}
|
||||||
|
freeSolo={freeSolo}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
disableClearable={disableClearable}
|
disableClearable={disableClearable}
|
||||||
size='small'
|
size='small'
|
||||||
@@ -176,6 +178,7 @@ AsyncDropdown.propTypes = {
|
|||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
onCreateNew: PropTypes.func,
|
onCreateNew: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
freeSolo: PropTypes.bool,
|
||||||
credentialNames: PropTypes.array,
|
credentialNames: PropTypes.array,
|
||||||
disableClearable: PropTypes.bool,
|
disableClearable: PropTypes.bool,
|
||||||
isCreateNewOption: PropTypes.bool
|
isCreateNewOption: PropTypes.bool
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, disableClearable = false }) => {
|
export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, freeSolo = false, disableClearable = false }) => {
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
|
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
|
||||||
const getDefaultOptionValue = () => ''
|
const getDefaultOptionValue = () => ''
|
||||||
@@ -29,6 +29,7 @@ export const Dropdown = ({ name, value, loading, options, onSelect, disabled = f
|
|||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={name}
|
id={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
freeSolo={freeSolo}
|
||||||
disableClearable={disableClearable}
|
disableClearable={disableClearable}
|
||||||
size='small'
|
size='small'
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -101,6 +102,7 @@ Dropdown.propTypes = {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
|
freeSolo: PropTypes.bool,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
disableClearable: PropTypes.bool
|
disableClearable: PropTypes.bool
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle)
|
|||||||
collectParentNodes(parentNode.id, nodes, edges)
|
collectParentNodes(parentNode.id, nodes, edges)
|
||||||
|
|
||||||
// Check and add the parent node to the list if it does not include specific names
|
// Check and add the parent node to the list if it does not include specific names
|
||||||
const excludeNodeNames = ['seqAgent', 'seqLLMNode', 'seqToolNode']
|
const excludeNodeNames = ['seqAgent', 'seqLLMNode', 'seqToolNode', 'seqCustomFunction', 'seqExecuteFlow']
|
||||||
if (excludeNodeNames.includes(parentNode.data.name)) {
|
if (excludeNodeNames.includes(parentNode.data.name)) {
|
||||||
parentNodes.push(parentNode)
|
parentNodes.push(parentNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,6 +319,26 @@ const NodeInputHandler = ({
|
|||||||
return colDef
|
return colDef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDropdownOptions = (inputParam) => {
|
||||||
|
const preLoadOptions = []
|
||||||
|
if (inputParam.loadPreviousNodes) {
|
||||||
|
const nodes = getAvailableNodesForVariable(
|
||||||
|
reactFlowInstance?.getNodes() || [],
|
||||||
|
reactFlowInstance?.getEdges() || [],
|
||||||
|
data.id,
|
||||||
|
inputParam.id
|
||||||
|
)
|
||||||
|
for (const node of nodes) {
|
||||||
|
preLoadOptions.push({
|
||||||
|
name: `{{ ${node.data.id} }}`,
|
||||||
|
label: `{{ ${node.data.id} }}`,
|
||||||
|
description: `Output from ${node.data.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...preLoadOptions, ...inputParam.options]
|
||||||
|
}
|
||||||
|
|
||||||
const getTabValue = (inputParam) => {
|
const getTabValue = (inputParam) => {
|
||||||
return inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`]) >= 0
|
return inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`]) >= 0
|
||||||
? inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`])
|
? inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`])
|
||||||
@@ -515,6 +535,20 @@ const NodeInputHandler = ({
|
|||||||
{inputParam.description && <TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />}
|
{inputParam.description && <TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{ flexGrow: 1 }}></div>
|
<div style={{ flexGrow: 1 }}></div>
|
||||||
|
{inputParam.hint && !isAdditionalParams && (
|
||||||
|
<IconButton
|
||||||
|
size='small'
|
||||||
|
sx={{
|
||||||
|
height: 25,
|
||||||
|
width: 25
|
||||||
|
}}
|
||||||
|
title={inputParam.hint.label}
|
||||||
|
color='secondary'
|
||||||
|
onClick={() => onInputHintDialogClicked(inputParam.hint)}
|
||||||
|
>
|
||||||
|
<IconBulb />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
{inputParam.hint && isAdditionalParams && (
|
{inputParam.hint && isAdditionalParams && (
|
||||||
<Button
|
<Button
|
||||||
sx={{ p: 0, px: 2 }}
|
sx={{ p: 0, px: 2 }}
|
||||||
@@ -721,7 +755,8 @@ const NodeInputHandler = ({
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
name={inputParam.name}
|
name={inputParam.name}
|
||||||
options={inputParam.options}
|
options={getDropdownOptions(inputParam)}
|
||||||
|
freeSolo={inputParam.freeSolo}
|
||||||
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
||||||
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
||||||
/>
|
/>
|
||||||
@@ -730,7 +765,7 @@ const NodeInputHandler = ({
|
|||||||
<MultiDropdown
|
<MultiDropdown
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
name={inputParam.name}
|
name={inputParam.name}
|
||||||
options={inputParam.options}
|
options={getDropdownOptions(inputParam)}
|
||||||
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
||||||
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
||||||
/>
|
/>
|
||||||
@@ -744,6 +779,7 @@ const NodeInputHandler = ({
|
|||||||
name={inputParam.name}
|
name={inputParam.name}
|
||||||
nodeData={data}
|
nodeData={data}
|
||||||
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
||||||
|
freeSolo={inputParam.freeSolo}
|
||||||
isCreateNewOption={EDITABLE_OPTIONS.includes(inputParam.name)}
|
isCreateNewOption={EDITABLE_OPTIONS.includes(inputParam.name)}
|
||||||
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
||||||
onCreateNew={() => addAsyncOption(inputParam.name)}
|
onCreateNew={() => addAsyncOption(inputParam.name)}
|
||||||
|
|||||||
Generated
+5868
-5871
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user