diff --git a/packages/ui/src/ui-component/array/ArrayRenderer.jsx b/packages/ui/src/ui-component/array/ArrayRenderer.jsx index c3849954..fa90a247 100644 --- a/packages/ui/src/ui-component/array/ArrayRenderer.jsx +++ b/packages/ui/src/ui-component/array/ArrayRenderer.jsx @@ -54,7 +54,7 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => { // Initialize array items and parameters when component mounts or data changes useEffect(() => { - const initialArrayItems = data.inputs[inputParam.name] || [] + const initialArrayItems = cloneDeep(data.inputs[inputParam.name]) || [] setArrayItems(initialArrayItems) // Calculate initial display parameters for each array item diff --git a/packages/ui/src/views/agentflowsv2/ConfigInput.jsx b/packages/ui/src/views/agentflowsv2/ConfigInput.jsx index 2215528c..0c5ef713 100644 --- a/packages/ui/src/views/agentflowsv2/ConfigInput.jsx +++ b/packages/ui/src/views/agentflowsv2/ConfigInput.jsx @@ -1,4 +1,4 @@ -import { useContext, useState, useEffect } from 'react' +import { useContext, useState, useEffect, useRef } from 'react' import PropTypes from 'prop-types' import { cloneDeep } from 'lodash' @@ -26,6 +26,9 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n const [expanded, setExpanded] = useState(false) const [selectedComponentNodeData, setSelectedComponentNodeData] = useState({}) + // Track the last processed input values to prevent infinite loops + const lastProcessedInputsRef = useRef({}) + const handleAccordionChange = (event, isExpanded) => { setExpanded(isExpanded) } @@ -133,6 +136,13 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n componentNodeData.credential = credential ? credential : undefined setSelectedComponentNodeData(componentNodeData) + + // Store the processed inputs to track changes + lastProcessedInputsRef.current = { + mainValue: data.inputs[inputParam.name], + configValue: data.inputs[`${inputParam.name}Config`], + arrayValue: parentParamForArray ? data.inputs[parentParamForArray.name] : null + } } loadComponentData() @@ -140,6 +150,92 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + // Handle external changes to data.inputs + useEffect(() => { + if (!selectedComponentNodeData.inputParams) return + + // Get current input values + const currentMainValue = data.inputs[inputParam.name] + const currentConfigValue = data.inputs[`${inputParam.name}Config`] + const currentArrayValue = parentParamForArray ? data.inputs[parentParamForArray.name] : null + + // 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) { + return // No relevant changes + } + + // Update selectedComponentNodeData with new input values + const updateComponentData = () => { + const updatedComponentData = cloneDeep(selectedComponentNodeData) + + // Helper functions (same as in initial load) + const hasArrayConfig = () => { + return ( + parentParamForArray && + data.inputs[parentParamForArray.name] && + Array.isArray(data.inputs[parentParamForArray.name]) && + data.inputs[parentParamForArray.name][arrayIndex] && + data.inputs[parentParamForArray.name][arrayIndex][`${inputParam.name}Config`] + ) + } + + const getCurrentInputValue = () => { + return hasArrayConfig() ? data.inputs[parentParamForArray.name][arrayIndex][inputParam.name] : data.inputs[inputParam.name] + } + + const getConfigData = () => { + return hasArrayConfig() + ? data.inputs[parentParamForArray.name][arrayIndex][`${inputParam.name}Config`] + : data.inputs[`${inputParam.name}Config`] + } + + // Update the main input value in component data + const currentValue = getCurrentInputValue() + if (currentValue !== undefined) { + updatedComponentData.inputs[inputParam.name] = currentValue + } + + // If there's config data and it matches the current value, use it + if (hasArrayConfig() || data.inputs[`${inputParam.name}Config`]) { + const configData = getConfigData() + if (configData && configData[inputParam.name] === currentValue) { + // Config is still valid, merge it with current value + updatedComponentData.inputs = { ...configData, [inputParam.name]: currentValue } + } else if (hasMainValueChanged) { + // Main value changed but config doesn't match, reset to defaults with new value + const defaultInput = initializeDefaultNodeData(updatedComponentData.inputParams) + updatedComponentData.inputs = { ...defaultInput, [inputParam.name]: currentValue } + } + } + + // Update input parameters visibility + updatedComponentData.inputParams = showHideInputParams({ + ...updatedComponentData, + inputs: updatedComponentData.inputs + }) + + const credential = updatedComponentData.inputs.credential || updatedComponentData.inputs[FLOWISE_CREDENTIAL_ID] + updatedComponentData.credential = credential ? credential : undefined + + setSelectedComponentNodeData(updatedComponentData) + + // Update the tracked values + lastProcessedInputsRef.current = { + mainValue: currentMainValue, + configValue: currentConfigValue, + arrayValue: currentArrayValue + } + } + + updateComponentData() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data.inputs, inputParam.name, parentParamForArray?.name, arrayIndex]) + // Update node configuration when selected component data changes useEffect(() => { if (!selectedComponentNodeData.inputs) return @@ -169,7 +265,7 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n ) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data.inputs, arrayIndex, parentParamForArray, selectedComponentNodeData]) + }, [selectedComponentNodeData]) return ( <>