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 && }