diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 71e2d6a5d79..135807a0b6f 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -185,6 +185,72 @@ export const initNode = (nodeData, newNodeId) => { return nodeData } +export const updateOutdatedNodeData = (newComponentNodeData, existingComponentNodeData) => { + const initNewComponentNodeData = initNode(newComponentNodeData, existingComponentNodeData.id) + + // Update credentials with existing credentials + if (existingComponentNodeData.credential) { + initNewComponentNodeData.credential = existingComponentNodeData.credential + } + + // Update inputs with existing inputs + if (existingComponentNodeData.inputs) { + for (const key in existingComponentNodeData.inputs) { + if (key in initNewComponentNodeData.inputs) { + initNewComponentNodeData.inputs[key] = existingComponentNodeData.inputs[key] + } + } + } + + // Update outputs with existing outputs + if (existingComponentNodeData.outputs) { + for (const key in existingComponentNodeData.outputs) { + if (key in initNewComponentNodeData.outputs) { + initNewComponentNodeData.outputs[key] = existingComponentNodeData.outputs[key] + } + } + } + + return initNewComponentNodeData +} + +export const updateOutdatedNodeEdge = (newComponentNodeData, edges) => { + const removedEdges = [] + for (const edge of edges) { + const targetNodeId = edge.targetHandle.split('-')[0] + const sourceNodeId = edge.sourceHandle.split('-')[0] + + if (targetNodeId === newComponentNodeData.id) { + // Check if targetHandle is in inputParams or inputAnchors + const inputParam = newComponentNodeData.inputParams.find((param) => param.id === edge.targetHandle) + const inputAnchor = newComponentNodeData.inputAnchors.find((param) => param.id === edge.targetHandle) + + if (!inputParam && !inputAnchor) { + removedEdges.push(edge) + } + } + + if (sourceNodeId === newComponentNodeData.id) { + if (newComponentNodeData.outputAnchors?.length) { + for (const outputAnchor of newComponentNodeData.outputAnchors) { + const outputAnchorType = outputAnchor.type + if (outputAnchorType === 'options') { + if (!outputAnchor.options.find((outputOption) => outputOption.id === edge.sourceHandle)) { + removedEdges.push(edge) + } + } else { + if (outputAnchor.id !== edge.sourceHandle) { + removedEdges.push(edge) + } + } + } + } + } + } + + return removedEdges +} + export const isValidConnection = (connection, reactFlowInstance) => { const sourceHandle = connection.sourceHandle const targetHandle = connection.targetHandle diff --git a/packages/ui/src/views/canvas/CanvasNode.jsx b/packages/ui/src/views/canvas/CanvasNode.jsx index 3ba524431d0..3a86d66d79d 100644 --- a/packages/ui/src/views/canvas/CanvasNode.jsx +++ b/packages/ui/src/views/canvas/CanvasNode.jsx @@ -70,6 +70,8 @@ const CanvasNode = ({ data }) => { componentNode?.deprecateMessage ?? 'This node will be deprecated in the next release. Change to a new node tagged with NEW' ) + } else { + setWarningMessage('') } } }, [canvas.componentNodes, data.name, data.version]) @@ -241,8 +243,8 @@ const CanvasNode = ({ data }) => { - {data.outputAnchors.map((outputAnchor, index) => ( - + {data.outputAnchors.map((outputAnchor) => ( + ))} diff --git a/packages/ui/src/views/canvas/index.jsx b/packages/ui/src/views/canvas/index.jsx index d037c8350c4..94dc98b5e75 100644 --- a/packages/ui/src/views/canvas/index.jsx +++ b/packages/ui/src/views/canvas/index.jsx @@ -15,7 +15,7 @@ import { import { omit, cloneDeep } from 'lodash' // material-ui -import { Toolbar, Box, AppBar, Button } from '@mui/material' +import { Toolbar, Box, AppBar, Button, Fab } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports @@ -38,10 +38,17 @@ import useApi from '@/hooks/useApi' import useConfirm from '@/hooks/useConfirm' // icons -import { IconX } from '@tabler/icons' +import { IconX, IconRefreshAlert } from '@tabler/icons' // utils -import { getUniqueNodeId, initNode, rearrangeToolsOrdering, getUpsertDetails } from '@/utils/genericHelper' +import { + getUniqueNodeId, + initNode, + rearrangeToolsOrdering, + getUpsertDetails, + updateOutdatedNodeData, + updateOutdatedNodeEdge +} from '@/utils/genericHelper' import useNotifier from '@/utils/useNotifier' // const @@ -84,6 +91,7 @@ const Canvas = () => { const [selectedNode, setSelectedNode] = useState(null) const [isUpsertButtonEnabled, setIsUpsertButtonEnabled] = useState(false) + const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false) const reactFlowWrapper = useRef(null) @@ -305,6 +313,28 @@ const Canvas = () => { [reactFlowInstance] ) + const syncNodes = () => { + const componentNodes = canvas.componentNodes + + const cloneNodes = cloneDeep(nodes) + const cloneEdges = cloneDeep(edges) + let toBeRemovedEdges = [] + + for (let i = 0; i < cloneNodes.length; i++) { + const node = cloneNodes[i] + const componentNode = componentNodes.find((cn) => cn.name === node.data.name) + if (componentNode && componentNode.version > node.data.version) { + cloneNodes[i].data = updateOutdatedNodeData(componentNode, node.data) + toBeRemovedEdges.push(...updateOutdatedNodeEdge(cloneNodes[i].data, cloneEdges)) + } + } + + setNodes(cloneNodes) + setEdges(cloneEdges.filter((edge) => !toBeRemovedEdges.includes(edge))) + setDirty() + setIsSyncNodesButtonEnabled(false) + } + const saveChatflowSuccess = () => { dispatch({ type: REMOVE_DIRTY }) enqueueSnackbar({ @@ -347,6 +377,21 @@ const Canvas = () => { else setIsUpsertButtonEnabled(false) } + const checkIfSyncNodesAvailable = (nodes) => { + const componentNodes = canvas.componentNodes + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i] + const componentNode = componentNodes.find((cn) => cn.name === node.data.name) + if (componentNode && componentNode.version > node.data.version) { + setIsSyncNodesButtonEnabled(true) + return + } + } + + setIsSyncNodesButtonEnabled(false) + } + // ==============================|| useEffect ||============================== // // Get specific chatflow successful @@ -416,11 +461,16 @@ const Canvas = () => { if (canvasDataStore.chatflow) { const flowData = canvasDataStore.chatflow.flowData ? JSON.parse(canvasDataStore.chatflow.flowData) : [] checkIfUpsertAvailable(flowData.nodes || [], flowData.edges || []) + checkIfSyncNodesAvailable(flowData.nodes || []) } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasDataStore.chatflow]) // Initialization useEffect(() => { + setIsSyncNodesButtonEnabled(false) + setIsUpsertButtonEnabled(false) if (chatflowId) { getSpecificChatflowApi.request(chatflowId) } else { @@ -532,6 +582,26 @@ const Canvas = () => { /> + {isSyncNodesButtonEnabled && ( + syncNodes()} + > + + + )} {isUpsertButtonEnabled && }