Files
dify/web/app/components/workflow/hooks/use-edges-interactions.ts
yyh 30b9295156 Merge remote-tracking branch 'origin/build/feat/hitl' into wip/hitl-merge-web-conflicts-20260206-175434
# Conflicts:
#	api/.env.example
#	api/core/app/apps/advanced_chat/app_generator.py
#	api/core/app/apps/advanced_chat/app_runner.py
#	api/core/app/apps/advanced_chat/generate_task_pipeline.py
#	api/core/app/apps/workflow/app_generator.py
#	api/core/app/apps/workflow/app_runner.py
#	api/core/app/entities/queue_entities.py
#	api/core/workflow/node_events/__init__.py
#	api/core/workflow/runtime/graph_runtime_state.py
#	api/fields/message_fields.py
#	api/services/workflow_service.py
#	web/app/components/app/app-publisher/index.tsx
#	web/app/components/base/chat/chat/answer/index.tsx
#	web/app/components/base/chat/chat/hooks.ts
#	web/app/components/base/chat/chat/type.ts
#	web/app/components/base/prompt-editor/index.tsx
#	web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts
#	web/app/components/share/text-generation/result/header.tsx
#	web/app/components/workflow-app/components/workflow-header/features-trigger.tsx
#	web/app/components/workflow-app/hooks/use-workflow-run.ts
#	web/app/components/workflow/hooks/use-checklist.ts
#	web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts
#	web/app/components/workflow/hooks/use-workflow.ts
#	web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
#	web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
#	web/app/components/workflow/nodes/_base/node.tsx
#	web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/components/placeholder.tsx
#	web/app/components/workflow/panel/debug-and-preview/hooks.ts
#	web/app/components/workflow/panel/workflow-preview.tsx
#	web/app/components/workflow/store/workflow/workflow-slice.ts
#	web/eslint-suppressions.json
#	web/i18n/en-US/common.json
#	web/i18n/zh-Hans/common.json
#	web/i18n/zh-Hant/common.json
#	web/service/workflow.ts
2026-02-06 19:13:51 +08:00

259 lines
8.3 KiB
TypeScript

import type {
Edge,
EdgeMouseHandler,
OnEdgesChange,
} from 'reactflow'
import type {
Node,
} from '../types'
import { produce } from 'immer'
import { useCallback } from 'react'
import { BlockEnum } from '../types'
import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils'
import { useCollaborativeWorkflow } from './use-collaborative-workflow'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useNodesReadOnly } from './use-workflow'
import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history'
export const useEdgesInteractions = () => {
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { getNodesReadOnly } = useNodesReadOnly()
const { saveStateToHistory } = useWorkflowHistory()
const collaborativeWorkflow = useCollaborativeWorkflow()
const handleEdgeEnter = useCallback<EdgeMouseHandler>((_, edge) => {
if (getNodesReadOnly())
return
const { edges, setEdges } = collaborativeWorkflow.getState()
const newEdges = produce(edges, (draft) => {
const currentEdge = draft.find(e => e.id === edge.id)!
currentEdge.data._hovering = true
})
setEdges(newEdges, false)
}, [collaborativeWorkflow, getNodesReadOnly])
const handleEdgeLeave = useCallback<EdgeMouseHandler>((_, edge) => {
if (getNodesReadOnly())
return
const { edges, setEdges } = collaborativeWorkflow.getState()
const newEdges = produce(edges, (draft) => {
const currentEdge = draft.find(e => e.id === edge.id)!
currentEdge.data._hovering = false
})
setEdges(newEdges, false)
}, [collaborativeWorkflow, getNodesReadOnly])
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 nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })),
nodes,
)
const newNodes = produce(nodes, (draft: Node[]) => {
draft.forEach((node) => {
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
node.data = {
...node.data,
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
}
}
})
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id))
})
setEdges(newEdges)
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch)
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeDelete = useCallback(() => {
if (getNodesReadOnly())
return
const {
nodes,
setNodes,
edges,
setEdges,
} = collaborativeWorkflow.getState()
const currentEdgeIndex = edges.findIndex(edge => edge.selected)
if (currentEdgeIndex < 0)
return
const currentEdge = edges[currentEdgeIndex]
// collect edges to delete (including corresponding real edges for temp edges)
const edgesToDelete: Set<string> = new Set([currentEdge.id])
// if deleting a temp edge connected to a group, also delete the corresponding real hidden edge
if (currentEdge.data?._isTemp) {
const groupNode = nodes.find(n =>
n.data.type === BlockEnum.Group
&& (n.id === currentEdge.source || n.id === currentEdge.target),
)
if (groupNode) {
const memberIds = new Set((groupNode.data.members || []).map((m: { id: string }) => m.id))
if (currentEdge.target === groupNode.id) {
// inbound temp edge: find real edge with same source, target is a head node
edges.forEach((edge) => {
if (edge.source === currentEdge.source
&& memberIds.has(edge.target)
&& edge.sourceHandle === currentEdge.sourceHandle) {
edgesToDelete.add(edge.id)
}
})
}
else if (currentEdge.source === groupNode.id) {
// outbound temp edge: sourceHandle format is "leafNodeId-originalHandle"
const sourceHandle = currentEdge.sourceHandle || ''
const lastDashIndex = sourceHandle.lastIndexOf('-')
if (lastDashIndex > 0) {
const leafNodeId = sourceHandle.substring(0, lastDashIndex)
const originalHandle = sourceHandle.substring(lastDashIndex + 1)
edges.forEach((edge) => {
if (edge.source === leafNodeId
&& edge.target === currentEdge.target
&& (edge.sourceHandle || 'source') === originalHandle) {
edgesToDelete.add(edge.id)
}
})
}
}
}
}
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
[
{ type: 'remove', edge: currentEdge },
],
nodes,
)
const newNodes = produce(nodes, (draft: Node[]) => {
draft.forEach((node) => {
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
node.data = {
...node.data,
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
}
}
})
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
for (let i = draft.length - 1; i >= 0; i--) {
if (edgesToDelete.has(draft[i].id))
draft.splice(i, 1)
}
})
setEdges(newEdges)
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgesChange = useCallback<OnEdgesChange>((changes) => {
if (getNodesReadOnly())
return
const {
edges,
setEdges,
} = collaborativeWorkflow.getState()
const newEdges = produce(edges, (draft) => {
changes.forEach((change) => {
if (change.type === 'select')
draft.find(edge => edge.id === change.id)!.selected = change.selected
})
})
setEdges(newEdges)
}, [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) => edge.source === nodeId && edge.sourceHandle === oldHandleId,
)
if (affectedEdges.length === 0)
return
// Update node metadata: remove old handle, add new handle
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
[
...affectedEdges.map((edge: Edge) => ({ type: 'remove', edge })),
...affectedEdges.map((edge: Edge) => ({
type: 'add',
edge: { ...edge, sourceHandle: newHandleId },
})),
],
nodes,
)
const newNodes = produce(nodes, (draft: Node[]) => {
draft.forEach((node) => {
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
node.data = {
...node.data,
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
}
}
})
})
setNodes(newNodes)
// Update edges to use new sourceHandle and regenerate edge IDs
const newEdges = produce(edges, (draft: Edge[]) => {
draft.forEach((edge: Edge) => {
if (edge.source === nodeId && edge.sourceHandle === oldHandleId) {
edge.sourceHandle = newHandleId
edge.id = `${edge.source}-${newHandleId}-${edge.target}-${edge.targetHandle}`
}
})
})
setEdges(newEdges)
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeSourceHandleChange)
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
return {
handleEdgeEnter,
handleEdgeLeave,
handleEdgeDeleteByDeleteBranch,
handleEdgeDelete,
handleEdgesChange,
handleEdgeSourceHandleChange,
}
}