Files
dify/web/app/components/workflow/hooks/use-edges-interactions.ts
FFXN 0e320290e1 feat: evaluation (#35353)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: jyong <718720800@qq.com>
Co-authored-by: Yansong Zhang <916125788@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: hj24 <mambahj24@gmail.com>
Co-authored-by: hj24 <huangjian@dify.ai>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 非法操作 <hjlarry@163.com>
Co-authored-by: Ayush Baluni <73417844+aayushbaluni@users.noreply.github.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: jimcody1995 <jjimcody@gmail.com>
Co-authored-by: James <63717587+jamesrayammons@users.noreply.github.com>
Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai>
Co-authored-by: Stephen Zhou <hi@hyoban.cc>
Co-authored-by: Coding On Star <447357187@qq.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: jerryzai <jerryzh8710@protonmail.com>
Co-authored-by: NVIDIAN <speedy.hpc@hotmail.com>
Co-authored-by: ai-hpc <ai-hpc@users.noreply.github.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: Junghwan <70629228+shaun0927@users.noreply.github.com>
Co-authored-by: HeYinKazune <70251095+HeYin-OS@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Jingyi <jingyi.qi@dify.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: sxxtony <166789813+sxxtony@users.noreply.github.com>
2026-04-17 16:37:21 +08:00

213 lines
6.8 KiB
TypeScript

import type {
EdgeMouseHandler,
OnEdgesChange,
} from 'reactflow'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useWorkflowStore } from '../store'
import { useCollaborativeWorkflow } from './use-collaborative-workflow'
import {
applyConnectedHandleNodeData,
buildContextMenuEdges,
clearEdgeMenuIfNeeded,
clearNodeSelectionState,
updateEdgeHoverState,
updateEdgeSelectionState,
} from './use-edges-interactions.helpers'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useNodesReadOnly } from './use-workflow'
import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history'
export const useEdgesInteractions = () => {
const store = useStoreApi()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { getNodesReadOnly } = useNodesReadOnly()
const { saveStateToHistory } = useWorkflowHistory()
const workflowStore = useWorkflowStore()
const collaborativeWorkflow = useCollaborativeWorkflow()
const deleteEdgeById = useCallback((edgeId: string) => {
const {
nodes,
setNodes,
edges,
setEdges,
} = collaborativeWorkflow.getState()
const currentEdgeIndex = edges.findIndex(edge => edge.id === edgeId)
if (currentEdgeIndex < 0)
return
const currentEdge = edges[currentEdgeIndex]!
const newNodes = applyConnectedHandleNodeData(nodes, [{ type: 'remove', edge: currentEdge }])
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
draft.splice(currentEdgeIndex, 1)
})
setEdges(newEdges)
if (clearEdgeMenuIfNeeded({ edgeMenu: workflowStore.getState().edgeMenu, edgeIds: [currentEdge!.id] }))
workflowStore.setState({ edgeMenu: undefined })
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
}, [collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeEnter = useCallback<EdgeMouseHandler>((_, edge) => {
if (getNodesReadOnly())
return
const { edges, setEdges } = store.getState()
setEdges(updateEdgeHoverState(edges, edge.id, true))
}, [getNodesReadOnly, store])
const handleEdgeLeave = useCallback<EdgeMouseHandler>((_, edge) => {
if (getNodesReadOnly())
return
const { edges, setEdges } = store.getState()
setEdges(updateEdgeHoverState(edges, edge.id, false))
}, [getNodesReadOnly, store])
const handleEdgeDeleteByDeleteBranch = useCallback((nodeId: string, branchId: string) => {
if (getNodesReadOnly())
return
const {
nodes,
setNodes,
edges,
setEdges,
} = collaborativeWorkflow.getState()
const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId)
if (!edgeWillBeDeleted.length)
return
const newNodes = applyConnectedHandleNodeData(
nodes,
edgeWillBeDeleted.map(edge => ({ type: 'remove' as const, edge })),
)
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id))
})
setEdges(newEdges)
if (clearEdgeMenuIfNeeded({
edgeMenu: workflowStore.getState().edgeMenu,
edgeIds: edgeWillBeDeleted.map(edge => edge.id),
})) {
workflowStore.setState({ edgeMenu: undefined })
}
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch)
}, [getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeDelete = useCallback(() => {
if (getNodesReadOnly())
return
const { edges } = collaborativeWorkflow.getState()
const currentEdge = edges.find(edge => edge.selected)
if (!currentEdge)
return
deleteEdgeById(currentEdge.id)
}, [deleteEdgeById, getNodesReadOnly, collaborativeWorkflow])
const handleEdgeDeleteById = useCallback((edgeId: string) => {
if (getNodesReadOnly())
return
deleteEdgeById(edgeId)
}, [deleteEdgeById, getNodesReadOnly])
const handleEdgesChange = useCallback<OnEdgesChange>((changes) => {
if (getNodesReadOnly())
return
const {
edges,
setEdges,
} = collaborativeWorkflow.getState()
setEdges(updateEdgeSelectionState(edges, changes))
}, [collaborativeWorkflow, getNodesReadOnly])
const handleEdgeSourceHandleChange = useCallback((nodeId: string, oldHandleId: string, newHandleId: string) => {
if (getNodesReadOnly())
return
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
// Find edges connected to the old handle
const affectedEdges = edges.filter(
edge => edge.source === nodeId && edge.sourceHandle === oldHandleId,
)
if (affectedEdges.length === 0)
return
// Update node metadata: remove old handle, add new handle
const newNodes = applyConnectedHandleNodeData(nodes, [
...affectedEdges.map(edge => ({ type: 'remove' as const, edge })),
...affectedEdges.map(edge => ({
type: 'add' as const,
edge: { ...edge, sourceHandle: newHandleId },
})),
])
setNodes(newNodes)
// Update edges to use new sourceHandle and regenerate edge IDs
const newEdges = produce(edges, (draft) => {
draft.forEach((edge) => {
if (edge.source === nodeId && edge.sourceHandle === oldHandleId) {
edge.sourceHandle = newHandleId
edge.id = `${edge.source}-${newHandleId}-${edge.target}-${edge.targetHandle}`
}
})
})
setEdges(newEdges)
if (clearEdgeMenuIfNeeded({
edgeMenu: workflowStore.getState().edgeMenu,
edgeIds: affectedEdges.map(edge => edge.id),
})) {
workflowStore.setState({ edgeMenu: undefined })
}
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeSourceHandleChange)
}, [getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeContextMenu = useCallback<EdgeMouseHandler>((e, edge) => {
if (getNodesReadOnly())
return
e.preventDefault()
const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState()
setEdges(buildContextMenuEdges(edges, edge.id))
if (nodes.some(node => node.data.selected || node.selected || node.data._isBundled)) {
setNodes(clearNodeSelectionState(nodes))
}
workflowStore.setState({
nodeMenu: undefined,
panelMenu: undefined,
selectionMenu: undefined,
edgeMenu: {
clientX: e.clientX,
clientY: e.clientY,
edgeId: edge.id,
},
})
}, [collaborativeWorkflow, workflowStore, getNodesReadOnly])
return {
handleEdgeEnter,
handleEdgeLeave,
handleEdgeDeleteByDeleteBranch,
handleEdgeDelete,
handleEdgeDeleteById,
handleEdgesChange,
handleEdgeSourceHandleChange,
handleEdgeContextMenu,
}
}