sync nodes

This commit is contained in:
hjlarry 2025-09-25 16:31:46 +08:00
parent 2398ed6fe8
commit e240175116
6 changed files with 74 additions and 127 deletions

View File

@ -4,7 +4,6 @@ import {
import produce from 'immer'
import {
useReactFlow,
useStoreApi,
useViewport,
} from 'reactflow'
import { useEventListener } from 'ahooks'
@ -19,9 +18,9 @@ import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
import { BlockEnum } from './types'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
const CandidateNode = () => {
const store = useStoreApi()
const reactflow = useReactFlow()
const workflowStore = useWorkflowStore()
const candidateNode = useStore(s => s.candidateNode)
@ -29,18 +28,15 @@ const CandidateNode = () => {
const { zoom } = useViewport()
const { handleNodeSelect } = useNodesInteractions()
const { saveStateToHistory } = useWorkflowHistory()
const collaborativeWorkflow = useCollaborativeWorkflow()
useEventListener('click', (e) => {
const { candidateNode, mousePosition } = workflowStore.getState()
if (candidateNode) {
e.preventDefault()
const {
getNodes,
setNodes,
} = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const { screenToFlowPosition } = reactflow
const nodes = getNodes()
const { x, y } = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY })
const newNodes = produce(nodes, (draft) => {
draft.push({

View File

@ -1,7 +1,7 @@
import {
useCallback,
} from 'react'
import { useReactFlow, useStoreApi } from 'reactflow'
import { useReactFlow } from 'reactflow'
import produce from 'immer'
import { useStore, useWorkflowStore } from '../store'
import {
@ -28,6 +28,7 @@ import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-withou
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
export const useWorkflowInteractions = () => {
const workflowStore = useWorkflowStore()
@ -87,23 +88,22 @@ export const useWorkflowMoveMode = () => {
export const useWorkflowOrganize = () => {
const workflowStore = useWorkflowStore()
const store = useStoreApi()
const reactflow = useReactFlow()
const { getNodesReadOnly } = useNodesReadOnly()
const { saveStateToHistory } = useWorkflowHistory()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const collaborativeWorkflow = useCollaborativeWorkflow()
const handleLayout = useCallback(async () => {
if (getNodesReadOnly())
return
workflowStore.setState({ nodeAnimation: true })
const {
getNodes,
nodes,
edges,
setNodes,
} = store.getState()
} = collaborativeWorkflow.getState()
const { setViewport } = reactflow
const nodes = getNodes()
const loopAndIterationNodes = nodes.filter(
node => (node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration)
@ -248,7 +248,7 @@ export const useWorkflowOrganize = () => {
setTimeout(() => {
handleSyncWorkflowDraft()
})
}, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
}, [getNodesReadOnly, collaborativeWorkflow, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
return {
handleLayout,

View File

@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next'
import {
getIncomers,
getOutgoers,
useStoreApi,
} from 'reactflow'
import type {
Connection,
@ -35,6 +34,7 @@ import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
import { useAvailableBlocks } from './use-available-blocks'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
import {
fetchAllBuiltInTools,
fetchAllCustomTools,
@ -55,26 +55,19 @@ export const useIsChatMode = () => {
export const useWorkflow = () => {
const { t } = useTranslation()
const store = useStoreApi()
const collaborativeWorkflow = useCollaborativeWorkflow()
const workflowStore = useWorkflowStore()
const { getAvailableBlocks } = useAvailableBlocks()
const { nodesMap } = useNodesMetaData()
const getNodeById = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes } = collaborativeWorkflow.getState()
const currentNode = nodes.find(node => node.id === nodeId)
return currentNode
}, [store])
}, [collaborativeWorkflow])
const getTreeLeafNodes = useCallback((nodeId: string) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const { nodes, edges } = collaborativeWorkflow.getState()
const currentNode = nodes.find(node => node.id === nodeId)
let startNodes = nodes.filter(node => nodesMap?.[node.data.type as BlockEnum]?.metaData.isStart) || []
@ -117,14 +110,11 @@ export const useWorkflow = () => {
return uniqBy(list, 'id').filter((item: Node) => {
return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type)
})
}, [store, nodesMap])
}, [collaborativeWorkflow, nodesMap])
const getBeforeNodesInSameBranch = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = newNodes || getNodes()
const { nodes: oldNodes, edges } = collaborativeWorkflow.getState()
const nodes = newNodes || oldNodes
const currentNode = nodes.find(node => node.id === nodeId)
const list: Node[] = []
@ -167,14 +157,11 @@ export const useWorkflow = () => {
}
return []
}, [store])
}, [collaborativeWorkflow])
const getBeforeNodesInSameBranchIncludeParent = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
const nodes = getBeforeNodesInSameBranch(nodeId, newNodes, newEdges)
const {
getNodes,
} = store.getState()
const allNodes = getNodes()
const { nodes: allNodes } = collaborativeWorkflow.getState()
const node = allNodes.find(n => n.id === nodeId)
const parentNodeId = node?.parentId
const parentNode = allNodes.find(n => n.id === parentNodeId)
@ -182,14 +169,10 @@ export const useWorkflow = () => {
nodes.push(parentNode)
return nodes
}, [getBeforeNodesInSameBranch, store])
}, [getBeforeNodesInSameBranch, collaborativeWorkflow])
const getAfterNodesInSameBranch = useCallback((nodeId: string) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const { nodes, edges } = collaborativeWorkflow.getState()
const currentNode = nodes.find(node => node.id === nodeId)!
if (!currentNode)
@ -213,40 +196,29 @@ export const useWorkflow = () => {
})
return uniqBy(list, 'id')
}, [store])
}, [collaborativeWorkflow])
const getBeforeNodeById = useCallback((nodeId: string) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const { nodes, edges } = collaborativeWorkflow.getState()
const node = nodes.find(node => node.id === nodeId)!
return getIncomers(node, nodes, edges)
}, [store])
}, [collaborativeWorkflow])
const getIterationNodeChildren = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes } = collaborativeWorkflow.getState()
return nodes.filter(node => node.parentId === nodeId)
}, [store])
}, [collaborativeWorkflow])
const getLoopNodeChildren = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes } = collaborativeWorkflow.getState()
return nodes.filter(node => node.parentId === nodeId)
}, [store])
}, [collaborativeWorkflow])
const handleOutVarRenameChange = useCallback((nodeId: string, oldValeSelector: ValueSelector, newVarSelector: ValueSelector) => {
const { getNodes, setNodes } = store.getState()
const allNodes = getNodes()
const { nodes: allNodes, setNodes } = collaborativeWorkflow.getState()
const affectedNodes = findUsedVarNodes(oldValeSelector, allNodes)
if (affectedNodes.length > 0) {
const newNodes = allNodes.map((node) => {
@ -257,7 +229,7 @@ export const useWorkflow = () => {
})
setNodes(newNodes)
}
}, [store])
}, [collaborativeWorkflow])
const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => {
const nodeId = varSelector[0]
@ -268,11 +240,11 @@ export const useWorkflow = () => {
const removeUsedVarInNodes = useCallback((varSelector: ValueSelector) => {
const nodeId = varSelector[0]
const { getNodes, setNodes } = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const afterNodes = getAfterNodesInSameBranch(nodeId)
const effectNodes = findUsedVarNodes(varSelector, afterNodes)
if (effectNodes.length > 0) {
const newNodes = getNodes().map((node) => {
const newNodes = nodes.map((node) => {
if (effectNodes.find(n => n.id === node.id))
return updateNodeVars(node, varSelector, [])
@ -280,7 +252,7 @@ export const useWorkflow = () => {
})
setNodes(newNodes)
}
}, [getAfterNodesInSameBranch, store])
}, [getAfterNodesInSameBranch, collaborativeWorkflow])
const isNodeVarsUsedInNodes = useCallback((node: Node, isChatMode: boolean) => {
const outputVars = getNodeOutputVars(node, isChatMode)
@ -291,9 +263,7 @@ export const useWorkflow = () => {
}, [isVarUsedInNodes])
const checkParallelLimit = useCallback((nodeId: string, nodeHandle = 'source') => {
const {
edges,
} = store.getState()
const { edges } = collaborativeWorkflow.getState()
const connectedEdges = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === nodeHandle)
if (connectedEdges.length > MAX_PARALLEL_LIMIT - 1) {
const { setShowTips } = workflowStore.getState()
@ -302,14 +272,10 @@ export const useWorkflow = () => {
}
return true
}, [store, workflowStore, t])
}, [collaborativeWorkflow, workflowStore, t])
const getRootNodesById = useCallback((nodeId: string) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const { nodes, edges } = collaborativeWorkflow.getState()
const currentNode = nodes.find(node => node.id === nodeId)
const rootNodes: Node[] = []
@ -349,7 +315,7 @@ export const useWorkflow = () => {
return uniqBy(rootNodes, 'id')
return []
}, [store])
}, [collaborativeWorkflow])
const getStartNodes = useCallback((nodes: Node[], currentNode?: Node) => {
const { id, parentId } = currentNode || {}
@ -402,11 +368,7 @@ export const useWorkflow = () => {
}, [t, workflowStore, getStartNodes])
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
const {
edges,
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes, edges } = collaborativeWorkflow.getState()
const sourceNode: Node = nodes.find(node => node.id === source)!
const targetNode: Node = nodes.find(node => node.id === target)!
@ -445,7 +407,7 @@ export const useWorkflow = () => {
}
return !hasCycle(targetNode)
}, [store, checkParallelLimit, getAvailableBlocks])
}, [collaborativeWorkflow, checkParallelLimit, getAvailableBlocks])
return {
getNodeById,
@ -550,13 +512,10 @@ export const useNodesReadOnly = () => {
}
export const useIsNodeInIteration = (iterationId: string) => {
const store = useStoreApi()
const collaborativeWorkflow = useCollaborativeWorkflow()
const isNodeInIteration = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes } = collaborativeWorkflow.getState()
const node = nodes.find(node => node.id === nodeId)
if (!node)
@ -566,20 +525,17 @@ export const useIsNodeInIteration = (iterationId: string) => {
return true
return false
}, [iterationId, store])
}, [iterationId, collaborativeWorkflow])
return {
isNodeInIteration,
}
}
export const useIsNodeInLoop = (loopId: string) => {
const store = useStoreApi()
const collaborativeWorkflow = useCollaborativeWorkflow()
const isNodeInLoop = useCallback((nodeId: string) => {
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const { nodes } = collaborativeWorkflow.getState()
const node = nodes.find(node => node.id === nodeId)
if (!node)
@ -589,7 +545,7 @@ export const useIsNodeInLoop = (loopId: string) => {
return true
return false
}, [loopId, store])
}, [loopId, collaborativeWorkflow])
return {
isNodeInLoop,
}

View File

@ -3,9 +3,6 @@ import {
useCallback,
useState,
} from 'react'
import {
useStoreApi,
} from 'reactflow'
import { RiBookOpenLine, RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useStore } from '@/app/components/workflow/store'
@ -25,15 +22,15 @@ import { useDocLink } from '@/context/i18n'
import cn from '@/utils/classnames'
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
import { updateConversationVariables } from '@/service/workflow'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
const ChatVariablePanel = () => {
const { t } = useTranslation()
const docLink = useDocLink()
const store = useStoreApi()
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
const updateChatVarList = useStore(s => s.setConversationVariables)
const appId = useStore(s => s.appId)
const appId = useStore(s => s.appId) as string
const {
invalidateConversationVarValues,
} = useInspectVarsCrud()
@ -44,27 +41,27 @@ const ChatVariablePanel = () => {
const [showRemoveVarConfirm, setShowRemoveConfirm] = useState(false)
const [cacheForDelete, setCacheForDelete] = useState<ConversationVariable>()
const collaborativeWorkflow = useCollaborativeWorkflow()
const getEffectedNodes = useCallback((chatVar: ConversationVariable) => {
const { getNodes } = store.getState()
const allNodes = getNodes()
const { nodes: allNodes } = collaborativeWorkflow.getState()
return findUsedVarNodes(
['conversation', chatVar.name],
allNodes,
)
}, [store])
}, [collaborativeWorkflow])
const removeUsedVarInNodes = useCallback((chatVar: ConversationVariable) => {
const { getNodes, setNodes } = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const effectedNodes = getEffectedNodes(chatVar)
const newNodes = getNodes().map((node) => {
const newNodes = nodes.map((node) => {
if (effectedNodes.find(n => n.id === node.id))
return updateNodeVars(node, ['conversation', chatVar.name], [])
return node
})
setNodes(newNodes)
}, [getEffectedNodes, store])
}, [getEffectedNodes, collaborativeWorkflow])
const handleEdit = (chatVar: ConversationVariable) => {
setCurrentVar(chatVar)
@ -151,9 +148,9 @@ const ChatVariablePanel = () => {
// side effects of rename conversation variable
if (currentVar.name !== chatVar.name) {
const { getNodes, setNodes } = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const effectedNodes = getEffectedNodes(currentVar)
const newNodes = getNodes().map((node) => {
const newNodes = nodes.map((node) => {
if (effectedNodes.find(n => n.id === node.id))
return updateNodeVars(node, ['conversation', currentVar.name], ['conversation', chatVar.name])
@ -183,7 +180,7 @@ const ChatVariablePanel = () => {
// Revert local state on error
updateChatVarList(varList)
}
}, [currentVar, getEffectedNodes, store, updateChatVarList, varList, appId, invalidateConversationVarValues])
}, [currentVar, getEffectedNodes, collaborativeWorkflow, updateChatVarList, varList, appId, invalidateConversationVarValues])
return (
<div

View File

@ -3,9 +3,6 @@ import {
useCallback,
useState,
} from 'react'
import {
useStoreApi,
} from 'reactflow'
import { RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useStore } from '@/app/components/workflow/store'
@ -20,16 +17,17 @@ import cn from '@/utils/classnames'
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
import { updateEnvironmentVariables } from '@/service/workflow'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
const EnvPanel = () => {
const { t } = useTranslation()
const store = useStoreApi()
const collaborativeWorkflow = useCollaborativeWorkflow()
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
const envList = useStore(s => s.environmentVariables) as EnvironmentVariable[]
const envSecrets = useStore(s => s.envSecrets)
const updateEnvList = useStore(s => s.setEnvironmentVariables)
const setEnvSecrets = useStore(s => s.setEnvSecrets)
const appId = useWorkflowStore(s => s.appId)
const appId = useWorkflowStore(s => s.appId) as string
const [showVariableModal, setShowVariableModal] = useState(false)
const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
@ -42,25 +40,24 @@ const EnvPanel = () => {
}
const getEffectedNodes = useCallback((env: EnvironmentVariable) => {
const { getNodes } = store.getState()
const allNodes = getNodes()
const { nodes: allNodes } = collaborativeWorkflow.getState()
return findUsedVarNodes(
['env', env.name],
allNodes,
)
}, [store])
}, [collaborativeWorkflow])
const removeUsedVarInNodes = useCallback((env: EnvironmentVariable) => {
const { getNodes, setNodes } = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const effectedNodes = getEffectedNodes(env)
const newNodes = getNodes().map((node) => {
const newNodes = nodes.map((node) => {
if (effectedNodes.find(n => n.id === node.id))
return updateNodeVars(node, ['env', env.name], [])
return node
})
setNodes(newNodes)
}, [getEffectedNodes, store])
}, [getEffectedNodes, collaborativeWorkflow])
const handleEdit = (env: EnvironmentVariable) => {
setCurrentVar(env)
@ -185,9 +182,9 @@ const EnvPanel = () => {
// side effects of rename env
if (currentVar.name !== env.name) {
const { getNodes, setNodes } = store.getState()
const { nodes, setNodes } = collaborativeWorkflow.getState()
const effectedNodes = getEffectedNodes(currentVar)
const newNodes = getNodes().map((node) => {
const newNodes = nodes.map((node) => {
if (effectedNodes.find(n => n.id === node.id))
return updateNodeVars(node, ['env', currentVar.name], ['env', env.name])
@ -218,7 +215,7 @@ const EnvPanel = () => {
// Revert local state on error
updateEnvList(envList)
}
}, [currentVar, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList, appId])
}, [currentVar, envList, envSecrets, getEffectedNodes, setEnvSecrets, collaborativeWorkflow, updateEnvList, appId])
return (
<div

View File

@ -7,7 +7,7 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'ahooks'
import { useStore as useReactFlowStore, useStoreApi } from 'reactflow'
import { useStore as useReactFlowStore } from 'reactflow'
import {
RiAlignBottom,
RiAlignCenter,
@ -22,6 +22,7 @@ import { WorkflowHistoryEvent, useWorkflowHistory } from './hooks/use-workflow-h
import { useStore } from './store'
import { useSelectionInteractions } from './hooks/use-selection-interactions'
import { useWorkflowStore } from './store'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
enum AlignType {
Left = 'left',
@ -42,8 +43,8 @@ const SelectionContextmenu = () => {
const selectionMenu = useStore(s => s.selectionMenu)
// Access React Flow methods
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const collaborativeWorkflow = useCollaborativeWorkflow()
// Get selected nodes for alignment logic
const selectedNodes = useReactFlowStore(state =>
@ -256,7 +257,7 @@ const SelectionContextmenu = () => {
workflowStore.setState({ nodeAnimation: false })
// Get all current nodes
const nodes = store.getState().getNodes()
const { nodes, setNodes } = collaborativeWorkflow.getState()
// Get all selected nodes
const selectedNodeIds = selectedNodes.map(node => node.id)
@ -312,7 +313,7 @@ const SelectionContextmenu = () => {
const distributeNodes = handleDistributeNodes(nodesToAlign, nodes, alignType)
if (distributeNodes) {
// Apply node distribution updates
store.getState().setNodes(distributeNodes)
setNodes(distributeNodes)
handleSelectionContextmenuCancel()
// Clear guide lines
@ -347,7 +348,7 @@ const SelectionContextmenu = () => {
// Apply node position updates - consistent with handleNodeDrag and handleNodeDragStop
try {
// Directly use setNodes to update nodes - consistent with handleNodeDrag
store.getState().setNodes(newNodes)
setNodes(newNodes)
// Close popup
handleSelectionContextmenuCancel()
@ -366,7 +367,7 @@ const SelectionContextmenu = () => {
catch (err) {
console.error('Failed to update nodes:', err)
}
}, [store, workflowStore, selectedNodes, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory, handleSelectionContextmenuCancel, handleAlignNode, handleDistributeNodes])
}, [collaborativeWorkflow, workflowStore, selectedNodes, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory, handleSelectionContextmenuCancel, handleAlignNode, handleDistributeNodes])
if (!selectionMenu)
return null