Skip to content

Commit

Permalink
Feature/Add auto sync nodes (#2307)
Browse files Browse the repository at this point in the history
add auto sync nodes
  • Loading branch information
HenryHengZJ authored May 2, 2024
1 parent d5a9706 commit c5e06bc
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 5 deletions.
66 changes: 66 additions & 0 deletions packages/ui/src/utils/genericHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/src/views/canvas/CanvasNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -241,8 +243,8 @@ const CanvasNode = ({ data }) => {
</Typography>
</Box>
<Divider />
{data.outputAnchors.map((outputAnchor, index) => (
<NodeOutputHandler key={index} outputAnchor={outputAnchor} data={data} />
{data.outputAnchors.map((outputAnchor) => (
<NodeOutputHandler key={JSON.stringify(data)} outputAnchor={outputAnchor} data={data} />
))}
</Box>
</NodeTooltip>
Expand Down
76 changes: 73 additions & 3 deletions packages/ui/src/views/canvas/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -532,6 +582,26 @@ const Canvas = () => {
/>
<Background color='#aaa' gap={16} />
<AddNodes nodesData={getNodesApi.data} node={selectedNode} />
{isSyncNodesButtonEnabled && (
<Fab
sx={{
left: 40,
top: 20,
color: 'white',
background: 'orange',
'&:hover': {
background: 'orange',
backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)`
}
}}
size='small'
aria-label='sync'
title='Sync Nodes'
onClick={() => syncNodes()}
>
<IconRefreshAlert />
</Fab>
)}
{isUpsertButtonEnabled && <VectorStorePopUp chatflowid={chatflowId} />}
<ChatPopUp chatflowid={chatflowId} />
</ReactFlow>
Expand Down

0 comments on commit c5e06bc

Please sign in to comment.