Bugfix/config input for multiple same fields (#4548)

* fix config input for multiple same fields

* fix custom tool not selected
This commit is contained in:
Henry Heng 2025-05-31 17:03:03 +01:00 committed by GitHub
parent eb69b23d73
commit a88337cc83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 32 deletions

View File

@ -28,7 +28,7 @@ class Tool_Agentflow implements INode {
constructor() { constructor() {
this.label = 'Tool' this.label = 'Tool'
this.name = 'toolAgentflow' this.name = 'toolAgentflow'
this.version = 1.0 this.version = 1.1
this.type = 'Tool' this.type = 'Tool'
this.category = 'Agent Flows' this.category = 'Agent Flows'
this.description = 'Tools allow LLM to interact with external systems' this.description = 'Tools allow LLM to interact with external systems'
@ -37,7 +37,7 @@ class Tool_Agentflow implements INode {
this.inputs = [ this.inputs = [
{ {
label: 'Tool', label: 'Tool',
name: 'selectedTool', name: 'toolAgentflowSelectedTool',
type: 'asyncOptions', type: 'asyncOptions',
loadMethod: 'listTools', loadMethod: 'listTools',
loadConfig: true loadConfig: true
@ -64,7 +64,7 @@ class Tool_Agentflow implements INode {
} }
], ],
show: { show: {
selectedTool: '.+' toolAgentflowSelectedTool: '.+'
} }
}, },
{ {
@ -124,7 +124,7 @@ class Tool_Agentflow implements INode {
}, },
async listToolInputArgs(nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> { async listToolInputArgs(nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> {
const currentNode = options.currentNode as ICommonObject const currentNode = options.currentNode as ICommonObject
const selectedTool = currentNode?.inputs?.selectedTool as string const selectedTool = (currentNode?.inputs?.selectedTool as string) || (currentNode?.inputs?.toolAgentflowSelectedTool as string)
const selectedToolConfig = currentNode?.inputs?.selectedToolConfig as ICommonObject const selectedToolConfig = currentNode?.inputs?.selectedToolConfig as ICommonObject
const nodeInstanceFilePath = options.componentNodes[selectedTool].filePath as string const nodeInstanceFilePath = options.componentNodes[selectedTool].filePath as string
@ -183,7 +183,7 @@ class Tool_Agentflow implements INode {
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const selectedTool = nodeData.inputs?.selectedTool as string const selectedTool = (nodeData.inputs?.selectedTool as string) || (nodeData.inputs?.toolAgentflowSelectedTool as string)
const selectedToolConfig = nodeData.inputs?.selectedToolConfig as ICommonObject const selectedToolConfig = nodeData.inputs?.selectedToolConfig as ICommonObject
const toolInputArgs = nodeData.inputs?.toolInputArgs as IToolInputArgs[] const toolInputArgs = nodeData.inputs?.toolInputArgs as IToolInputArgs[]

View File

@ -526,7 +526,7 @@
"data": { "data": {
"id": "toolAgentflow_0", "id": "toolAgentflow_0",
"label": "Slack Reply", "label": "Slack Reply",
"version": 1, "version": 1.1,
"name": "toolAgentflow", "name": "toolAgentflow",
"type": "Tool", "type": "Tool",
"color": "#d4a373", "color": "#d4a373",
@ -536,11 +536,11 @@
"inputParams": [ "inputParams": [
{ {
"label": "Tool", "label": "Tool",
"name": "selectedTool", "name": "toolAgentflowSelectedTool",
"type": "asyncOptions", "type": "asyncOptions",
"loadMethod": "listTools", "loadMethod": "listTools",
"loadConfig": true, "loadConfig": true,
"id": "toolAgentflow_0-input-selectedTool-asyncOptions", "id": "toolAgentflow_0-input-toolAgentflowSelectedTool-asyncOptions",
"display": true "display": true
}, },
{ {
@ -565,7 +565,7 @@
} }
], ],
"show": { "show": {
"selectedTool": ".+" "toolAgentflowSelectedTool": ".+"
}, },
"id": "toolAgentflow_0-input-toolInputArgs-array", "id": "toolAgentflow_0-input-toolInputArgs-array",
"display": true "display": true
@ -599,7 +599,7 @@
], ],
"inputAnchors": [], "inputAnchors": [],
"inputs": { "inputs": {
"selectedTool": "slackMCP", "toolAgentflowSelectedTool": "slackMCP",
"toolInputArgs": [ "toolInputArgs": [
{ {
"inputArgName": "channel_id", "inputArgName": "channel_id",
@ -611,9 +611,9 @@
} }
], ],
"toolUpdateState": "", "toolUpdateState": "",
"selectedToolConfig": { "toolAgentflowSelectedToolConfig": {
"mcpActions": "[\"slack_post_message\"]", "mcpActions": "[\"slack_post_message\"]",
"selectedTool": "slackMCP" "toolAgentflowSelectedTool": "slackMCP"
} }
}, },
"outputAnchors": [ "outputAnchors": [

View File

@ -1,4 +1,4 @@
import { useContext, useState, useEffect, useRef } from 'react' import { useContext, useState, useEffect, useMemo } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
@ -26,8 +26,12 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
const [selectedComponentNodeData, setSelectedComponentNodeData] = useState({}) const [selectedComponentNodeData, setSelectedComponentNodeData] = useState({})
// Track the last processed input values to prevent infinite loops // Track the last processed input values to prevent infinite loops using useState
const lastProcessedInputsRef = useRef({}) const [lastProcessedInputs, setLastProcessedInputs] = useState({
mainValue: null,
configValue: null,
arrayValue: null
})
const handleAccordionChange = (event, isExpanded) => { const handleAccordionChange = (event, isExpanded) => {
setExpanded(isExpanded) setExpanded(isExpanded)
@ -64,6 +68,18 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n
setSelectedComponentNodeData(nodeData) setSelectedComponentNodeData(nodeData)
} }
// Memoize current input values for reliable comparison
const currentInputValues = useMemo(
() => ({
mainValue: data.inputs[inputParam.name],
configValue: data.inputs[`${inputParam.name}Config`],
arrayValue: parentParamForArray ? data.inputs[parentParamForArray.name] : null
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[data.inputs, inputParam.name, parentParamForArray?.name]
)
// Load initial component data when the component mounts // Load initial component data when the component mounts
useEffect(() => { useEffect(() => {
const loadComponentData = async () => { const loadComponentData = async () => {
@ -138,11 +154,11 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n
setSelectedComponentNodeData(componentNodeData) setSelectedComponentNodeData(componentNodeData)
// Store the processed inputs to track changes // Store the processed inputs to track changes
lastProcessedInputsRef.current = { setLastProcessedInputs({
mainValue: data.inputs[inputParam.name], mainValue: data.inputs[inputParam.name],
configValue: data.inputs[`${inputParam.name}Config`], configValue: data.inputs[`${inputParam.name}Config`],
arrayValue: parentParamForArray ? data.inputs[parentParamForArray.name] : null arrayValue: parentParamForArray ? data.inputs[parentParamForArray.name] : null
} })
} }
loadComponentData() loadComponentData()
@ -154,15 +170,10 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n
useEffect(() => { useEffect(() => {
if (!selectedComponentNodeData.inputParams) return if (!selectedComponentNodeData.inputParams) return
// Get current input values // Check if relevant inputs have changed using strict equality comparison
const currentMainValue = data.inputs[inputParam.name] const hasMainValueChanged = lastProcessedInputs.mainValue !== currentInputValues.mainValue
const currentConfigValue = data.inputs[`${inputParam.name}Config`] const hasConfigValueChanged = lastProcessedInputs.configValue !== currentInputValues.configValue
const currentArrayValue = parentParamForArray ? data.inputs[parentParamForArray.name] : null const hasArrayValueChanged = lastProcessedInputs.arrayValue !== currentInputValues.arrayValue
// Check if relevant inputs have changed
const hasMainValueChanged = lastProcessedInputsRef.current.mainValue !== currentMainValue
const hasConfigValueChanged = lastProcessedInputsRef.current.configValue !== currentConfigValue
const hasArrayValueChanged = lastProcessedInputsRef.current.arrayValue !== currentArrayValue
if (!hasMainValueChanged && !hasConfigValueChanged && !hasArrayValueChanged) { if (!hasMainValueChanged && !hasConfigValueChanged && !hasArrayValueChanged) {
return // No relevant changes return // No relevant changes
@ -224,17 +235,17 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n
setSelectedComponentNodeData(updatedComponentData) setSelectedComponentNodeData(updatedComponentData)
// Update the tracked values // Update the tracked values
lastProcessedInputsRef.current = { setLastProcessedInputs({
mainValue: currentMainValue, mainValue: currentInputValues.mainValue,
configValue: currentConfigValue, configValue: currentInputValues.configValue,
arrayValue: currentArrayValue arrayValue: currentInputValues.arrayValue
} })
} }
updateComponentData() updateComponentData()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data.inputs, inputParam.name, parentParamForArray?.name, arrayIndex]) }, [currentInputValues, selectedComponentNodeData.inputParams, inputParam.name, parentParamForArray?.name, arrayIndex])
// Update node configuration when selected component data changes // Update node configuration when selected component data changes
useEffect(() => { useEffect(() => {