mirror of
https://github.com/langgenius/dify.git
synced 2026-02-13 07:01:23 -05:00
- Updated `checkMakeGroupAvailability` to include a check for existing group nodes, preventing group creation if a group node is already selected. - Modified `useMakeGroupAvailability` and `useNodesInteractions` hooks to incorporate the new group node check, ensuring accurate group creation logic. - Adjusted UI rendering logic in the workflow panel to conditionally display elements based on node type, specifically for group nodes.
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import type { PredecessorHandle } from '../utils'
|
|
import { useMemo } from 'react'
|
|
import { useStore as useReactFlowStore } from 'reactflow'
|
|
import { shallow } from 'zustand/shallow'
|
|
import { BlockEnum } from '../types'
|
|
import { getCommonPredecessorHandles } from '../utils'
|
|
|
|
export type MakeGroupAvailability = {
|
|
canMakeGroup: boolean
|
|
branchEntryNodeIds: string[]
|
|
commonPredecessorHandle?: PredecessorHandle
|
|
}
|
|
|
|
type MinimalEdge = {
|
|
id: string
|
|
source: string
|
|
sourceHandle: string
|
|
target: string
|
|
}
|
|
|
|
/**
|
|
* Pure function to check if the selected nodes can be grouped.
|
|
* Can be called both from React hooks and imperatively.
|
|
*/
|
|
export const checkMakeGroupAvailability = (
|
|
selectedNodeIds: string[],
|
|
edges: MinimalEdge[],
|
|
hasGroupNode = false,
|
|
): MakeGroupAvailability => {
|
|
if (selectedNodeIds.length <= 1 || hasGroupNode) {
|
|
return {
|
|
canMakeGroup: false,
|
|
branchEntryNodeIds: [],
|
|
commonPredecessorHandle: undefined,
|
|
}
|
|
}
|
|
|
|
const selectedNodeIdSet = new Set(selectedNodeIds)
|
|
const inboundFromOutsideTargets = new Set<string>()
|
|
const incomingEdgeCounts = new Map<string, number>()
|
|
const incomingFromSelectedTargets = new Set<string>()
|
|
|
|
edges.forEach((edge) => {
|
|
// Only consider edges whose target is inside the selected subgraph.
|
|
if (!selectedNodeIdSet.has(edge.target))
|
|
return
|
|
|
|
incomingEdgeCounts.set(edge.target, (incomingEdgeCounts.get(edge.target) ?? 0) + 1)
|
|
|
|
if (selectedNodeIdSet.has(edge.source))
|
|
incomingFromSelectedTargets.add(edge.target)
|
|
else
|
|
inboundFromOutsideTargets.add(edge.target)
|
|
})
|
|
|
|
// Branch head (entry) definition:
|
|
// - has at least one incoming edge
|
|
// - and all its incoming edges come from outside the selected subgraph
|
|
const branchEntryNodeIds = selectedNodeIds.filter((nodeId) => {
|
|
const incomingEdgeCount = incomingEdgeCounts.get(nodeId) ?? 0
|
|
if (incomingEdgeCount === 0)
|
|
return false
|
|
|
|
return !incomingFromSelectedTargets.has(nodeId)
|
|
})
|
|
|
|
// No branch head means we cannot tell how many branches are represented by this selection.
|
|
if (branchEntryNodeIds.length === 0) {
|
|
return {
|
|
canMakeGroup: false,
|
|
branchEntryNodeIds,
|
|
commonPredecessorHandle: undefined,
|
|
}
|
|
}
|
|
|
|
// Guardrail: disallow side entrances into the selected subgraph.
|
|
// If an outside node connects to a non-entry node inside the selection, the grouping boundary is ambiguous.
|
|
const branchEntryNodeIdSet = new Set(branchEntryNodeIds)
|
|
const hasInboundToNonEntryNode = Array.from(inboundFromOutsideTargets).some(nodeId => !branchEntryNodeIdSet.has(nodeId))
|
|
|
|
if (hasInboundToNonEntryNode) {
|
|
return {
|
|
canMakeGroup: false,
|
|
branchEntryNodeIds,
|
|
commonPredecessorHandle: undefined,
|
|
}
|
|
}
|
|
|
|
// Compare the branch heads by their common predecessor "handler" (source node + sourceHandle).
|
|
// This is required for multi-handle nodes like If-Else / Classifier where different branches use different handles.
|
|
const commonPredecessorHandles = getCommonPredecessorHandles(
|
|
branchEntryNodeIds,
|
|
// Only look at edges coming from outside the selected subgraph when determining the "pre" handler.
|
|
edges.filter(edge => !selectedNodeIdSet.has(edge.source)),
|
|
)
|
|
|
|
if (commonPredecessorHandles.length !== 1) {
|
|
return {
|
|
canMakeGroup: false,
|
|
branchEntryNodeIds,
|
|
commonPredecessorHandle: undefined,
|
|
}
|
|
}
|
|
|
|
return {
|
|
canMakeGroup: true,
|
|
branchEntryNodeIds,
|
|
commonPredecessorHandle: commonPredecessorHandles[0],
|
|
}
|
|
}
|
|
|
|
export const useMakeGroupAvailability = (selectedNodeIds: string[]): MakeGroupAvailability => {
|
|
const edgeKeys = useReactFlowStore((state) => {
|
|
const delimiter = '\u0000'
|
|
const keys = state.edges.map(edge => `${edge.source}${delimiter}${edge.sourceHandle || 'source'}${delimiter}${edge.target}`)
|
|
keys.sort()
|
|
return keys
|
|
}, shallow)
|
|
|
|
const hasGroupNode = useReactFlowStore((state) => {
|
|
return state.getNodes().some(node => node.selected && node.data.type === BlockEnum.Group)
|
|
})
|
|
|
|
return useMemo(() => {
|
|
const delimiter = '\u0000'
|
|
const edges = edgeKeys.map((key) => {
|
|
const [source, handleId, target] = key.split(delimiter)
|
|
return {
|
|
id: key,
|
|
source,
|
|
sourceHandle: handleId || 'source',
|
|
target,
|
|
}
|
|
})
|
|
|
|
return checkMakeGroupAvailability(selectedNodeIds, edges, hasGroupNode)
|
|
}, [edgeKeys, selectedNodeIds, hasGroupNode])
|
|
}
|