mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 17:38:04 +08:00
Merge branch 'feat/pull-a-variable' into feat/support-agent-sandbox
This commit is contained in:
@ -13,6 +13,7 @@ import {
|
||||
} from '@/app/components/workflow/constants'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
|
||||
import { CUSTOM_SUB_GRAPH_START_NODE } from '@/app/components/workflow/nodes/sub-graph-start/constants'
|
||||
import {
|
||||
BlockEnum,
|
||||
} from '@/app/components/workflow/types'
|
||||
@ -442,6 +443,7 @@ const normaliseChildLayout = (
|
||||
const startNode = nodes.find(node =>
|
||||
node.type === CUSTOM_ITERATION_START_NODE
|
||||
|| node.type === CUSTOM_LOOP_START_NODE
|
||||
|| node.type === CUSTOM_SUB_GRAPH_START_NODE
|
||||
|| node.data?.type === BlockEnum.LoopStart
|
||||
|| node.data?.type === BlockEnum.IterationStart,
|
||||
)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { CodeNodeType, OutputVar } from '../nodes/code/types'
|
||||
import type { IterationNodeType } from '../nodes/iteration/types'
|
||||
import type { LoopNodeType } from '../nodes/loop/types'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '../types'
|
||||
import {
|
||||
@ -20,7 +22,61 @@ import {
|
||||
BlockEnum,
|
||||
} from '../types'
|
||||
|
||||
export function generateNewNode({ data, position, id, zIndex, type, ...rest }: Omit<Node, 'id'> & { id?: string }): {
|
||||
type MergeNodeDefaultDataParams<T extends CommonNodeType<Record<string, unknown>>> = {
|
||||
nodeType: BlockEnum
|
||||
metaDefault?: Partial<T>
|
||||
appDefault?: Partial<T>
|
||||
baseData?: Partial<T>
|
||||
overrideData?: Partial<T>
|
||||
}
|
||||
|
||||
const pickNonEmptyArray = <T>(value?: T[]) => {
|
||||
return Array.isArray(value) && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
export const mergeNodeDefaultData = <T extends CommonNodeType<Record<string, unknown>>>({
|
||||
nodeType,
|
||||
metaDefault,
|
||||
appDefault,
|
||||
baseData,
|
||||
overrideData,
|
||||
}: MergeNodeDefaultDataParams<T>) => {
|
||||
const merged = {
|
||||
...(metaDefault || {}),
|
||||
...(appDefault || {}),
|
||||
...(baseData || {}),
|
||||
...(overrideData || {}),
|
||||
} as Partial<T>
|
||||
|
||||
if (nodeType === BlockEnum.Code) {
|
||||
const codeMetaDefault = (metaDefault || {}) as Partial<CodeNodeType>
|
||||
const codeAppDefault = (appDefault || {}) as Partial<CodeNodeType>
|
||||
const codeBase = (baseData || {}) as Partial<CodeNodeType>
|
||||
const codeOverride = (overrideData || {}) as Partial<CodeNodeType>
|
||||
const codeDefaults = {
|
||||
...codeMetaDefault,
|
||||
...codeAppDefault,
|
||||
}
|
||||
|
||||
const outputs: OutputVar = {
|
||||
...(codeDefaults.outputs || {}),
|
||||
...(codeBase.outputs || {}),
|
||||
...(codeOverride.outputs || {}),
|
||||
}
|
||||
if (Object.keys(outputs).length > 0)
|
||||
(merged as Partial<CodeNodeType>).outputs = outputs
|
||||
|
||||
const resolvedVariables = pickNonEmptyArray(codeBase.variables)
|
||||
?? pickNonEmptyArray(codeOverride.variables)
|
||||
?? pickNonEmptyArray(codeDefaults.variables)
|
||||
if (resolvedVariables)
|
||||
(merged as Partial<CodeNodeType>).variables = resolvedVariables
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
export function generateNewNode<T = {}>({ data, position, id, zIndex, type, ...rest }: Omit<Node<T>, 'id'> & { id?: string }): {
|
||||
newNode: Node
|
||||
newIterationStartNode?: Node
|
||||
newLoopStartNode?: Node
|
||||
|
||||
@ -1,21 +1,15 @@
|
||||
import type { CustomGroupNodeData } from '../custom-group-node'
|
||||
import type { GroupNodeData } from '../nodes/group/types'
|
||||
import type { IfElseNodeType } from '../nodes/if-else/types'
|
||||
import type { IterationNodeType } from '../nodes/iteration/types'
|
||||
import type { LoopNodeType } from '../nodes/loop/types'
|
||||
import type { QuestionClassifierNodeType } from '../nodes/question-classifier/types'
|
||||
import type { ToolNodeType } from '../nodes/tool/types'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
} from '../types'
|
||||
import type { Edge, Node } from '../types'
|
||||
import { cloneDeep } from 'es-toolkit/object'
|
||||
import {
|
||||
getConnectedEdges,
|
||||
} from 'reactflow'
|
||||
import { getConnectedEdges } from 'reactflow'
|
||||
import { getIterationStartNode, getLoopStartNode } from '@/app/components/workflow/utils/node'
|
||||
import { correctModelProvider } from '@/utils'
|
||||
import {
|
||||
getIterationStartNode,
|
||||
getLoopStartNode,
|
||||
} from '.'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
DEFAULT_RETRY_INTERVAL,
|
||||
@ -25,18 +19,22 @@ import {
|
||||
NODE_WIDTH_X_OFFSET,
|
||||
START_INITIAL_POSITION,
|
||||
} from '../constants'
|
||||
import { CUSTOM_GROUP_NODE, GROUP_CHILDREN_Z_INDEX } from '../custom-group-node'
|
||||
import { branchNameCorrect } from '../nodes/if-else/utils'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
|
||||
import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
|
||||
import {
|
||||
BlockEnum,
|
||||
ErrorHandleMode,
|
||||
} from '../types'
|
||||
import { BlockEnum, ErrorHandleMode } from '../types'
|
||||
|
||||
const WHITE = 'WHITE'
|
||||
const GRAY = 'GRAY'
|
||||
const BLACK = 'BLACK'
|
||||
const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjList: Record<string, string[]>, stack: string[]) => {
|
||||
|
||||
const isCyclicUtil = (
|
||||
nodeId: string,
|
||||
color: Record<string, string>,
|
||||
adjList: Record<string, string[]>,
|
||||
stack: string[],
|
||||
) => {
|
||||
color[nodeId] = GRAY
|
||||
stack.push(nodeId)
|
||||
|
||||
@ -47,8 +45,12 @@ const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjList: Re
|
||||
stack.push(childId)
|
||||
return true
|
||||
}
|
||||
if (color[childId] === WHITE && isCyclicUtil(childId, color, adjList, stack))
|
||||
if (
|
||||
color[childId] === WHITE
|
||||
&& isCyclicUtil(childId, color, adjList, stack)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
color[nodeId] = BLACK
|
||||
if (stack.length > 0 && stack[stack.length - 1] === nodeId)
|
||||
@ -66,8 +68,7 @@ const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
adjList[node.id] = []
|
||||
}
|
||||
|
||||
for (const edge of edges)
|
||||
adjList[edge.source]?.push(edge.target)
|
||||
for (const edge of edges) adjList[edge.source]?.push(edge.target)
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (color[nodes[i].id] === WHITE)
|
||||
@ -87,20 +88,34 @@ const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
|
||||
export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
const hasIterationNode = nodes.some(node => node.data.type === BlockEnum.Iteration)
|
||||
const hasIterationNode = nodes.some(
|
||||
node => node.data.type === BlockEnum.Iteration,
|
||||
)
|
||||
const hasLoopNode = nodes.some(node => node.data.type === BlockEnum.Loop)
|
||||
const hasGroupNode = nodes.some(node => node.type === CUSTOM_GROUP_NODE)
|
||||
const hasBusinessGroupNode = nodes.some(
|
||||
node => node.data.type === BlockEnum.Group,
|
||||
)
|
||||
|
||||
if (!hasIterationNode && !hasLoopNode) {
|
||||
if (
|
||||
!hasIterationNode
|
||||
&& !hasLoopNode
|
||||
&& !hasGroupNode
|
||||
&& !hasBusinessGroupNode
|
||||
) {
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
}
|
||||
}
|
||||
|
||||
const nodesMap = nodes.reduce((prev, next) => {
|
||||
prev[next.id] = next
|
||||
return prev
|
||||
}, {} as Record<string, Node>)
|
||||
const nodesMap = nodes.reduce(
|
||||
(prev, next) => {
|
||||
prev[next.id] = next
|
||||
return prev
|
||||
},
|
||||
{} as Record<string, Node>,
|
||||
)
|
||||
|
||||
const iterationNodesWithStartNode = []
|
||||
const iterationNodesWithoutStartNode = []
|
||||
@ -112,8 +127,12 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Iteration) {
|
||||
if (currentNode.data.start_node_id) {
|
||||
if (nodesMap[currentNode.data.start_node_id]?.type !== CUSTOM_ITERATION_START_NODE)
|
||||
if (
|
||||
nodesMap[currentNode.data.start_node_id]?.type
|
||||
!== CUSTOM_ITERATION_START_NODE
|
||||
) {
|
||||
iterationNodesWithStartNode.push(currentNode)
|
||||
}
|
||||
}
|
||||
else {
|
||||
iterationNodesWithoutStartNode.push(currentNode)
|
||||
@ -122,8 +141,12 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Loop) {
|
||||
if (currentNode.data.start_node_id) {
|
||||
if (nodesMap[currentNode.data.start_node_id]?.type !== CUSTOM_LOOP_START_NODE)
|
||||
if (
|
||||
nodesMap[currentNode.data.start_node_id]?.type
|
||||
!== CUSTOM_LOOP_START_NODE
|
||||
) {
|
||||
loopNodesWithStartNode.push(currentNode)
|
||||
}
|
||||
}
|
||||
else {
|
||||
loopNodesWithoutStartNode.push(currentNode)
|
||||
@ -132,7 +155,10 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
|
||||
const newIterationStartNodesMap = {} as Record<string, Node>
|
||||
const newIterationStartNodes = [...iterationNodesWithStartNode, ...iterationNodesWithoutStartNode].map((iterationNode, index) => {
|
||||
const newIterationStartNodes = [
|
||||
...iterationNodesWithStartNode,
|
||||
...iterationNodesWithoutStartNode,
|
||||
].map((iterationNode, index) => {
|
||||
const newNode = getIterationStartNode(iterationNode.id)
|
||||
newNode.id = newNode.id + index
|
||||
newIterationStartNodesMap[iterationNode.id] = newNode
|
||||
@ -140,24 +166,34 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
})
|
||||
|
||||
const newLoopStartNodesMap = {} as Record<string, Node>
|
||||
const newLoopStartNodes = [...loopNodesWithStartNode, ...loopNodesWithoutStartNode].map((loopNode, index) => {
|
||||
const newLoopStartNodes = [
|
||||
...loopNodesWithStartNode,
|
||||
...loopNodesWithoutStartNode,
|
||||
].map((loopNode, index) => {
|
||||
const newNode = getLoopStartNode(loopNode.id)
|
||||
newNode.id = newNode.id + index
|
||||
newLoopStartNodesMap[loopNode.id] = newNode
|
||||
return newNode
|
||||
})
|
||||
|
||||
const newEdges = [...iterationNodesWithStartNode, ...loopNodesWithStartNode].map((nodeItem) => {
|
||||
const newEdges = [
|
||||
...iterationNodesWithStartNode,
|
||||
...loopNodesWithStartNode,
|
||||
].map((nodeItem) => {
|
||||
const isIteration = nodeItem.data.type === BlockEnum.Iteration
|
||||
const newNode = (isIteration ? newIterationStartNodesMap : newLoopStartNodesMap)[nodeItem.id]
|
||||
const newNode = (
|
||||
isIteration ? newIterationStartNodesMap : newLoopStartNodesMap
|
||||
)[nodeItem.id]
|
||||
const startNode = nodesMap[nodeItem.data.start_node_id]
|
||||
const source = newNode.id
|
||||
const sourceHandle = 'source'
|
||||
const target = startNode.id
|
||||
const targetHandle = 'target'
|
||||
|
||||
const parentNode = nodes.find(node => node.id === startNode.parentId) || null
|
||||
const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
|
||||
const parentNode
|
||||
= nodes.find(node => node.id === startNode.parentId) || null
|
||||
const isInIteration
|
||||
= !!parentNode && parentNode.data.type === BlockEnum.Iteration
|
||||
const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
|
||||
|
||||
return {
|
||||
@ -180,21 +216,159 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
})
|
||||
nodes.forEach((node) => {
|
||||
if (node.data.type === BlockEnum.Iteration && newIterationStartNodesMap[node.id])
|
||||
(node.data as IterationNodeType).start_node_id = newIterationStartNodesMap[node.id].id
|
||||
if (
|
||||
node.data.type === BlockEnum.Iteration
|
||||
&& newIterationStartNodesMap[node.id]
|
||||
) {
|
||||
(node.data as IterationNodeType).start_node_id
|
||||
= newIterationStartNodesMap[node.id].id
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Loop && newLoopStartNodesMap[node.id])
|
||||
(node.data as LoopNodeType).start_node_id = newLoopStartNodesMap[node.id].id
|
||||
if (node.data.type === BlockEnum.Loop && newLoopStartNodesMap[node.id]) {
|
||||
(node.data as LoopNodeType).start_node_id
|
||||
= newLoopStartNodesMap[node.id].id
|
||||
}
|
||||
})
|
||||
|
||||
// Derive Group internal edges (input → entries, leaves → exits)
|
||||
const groupInternalEdges: Edge[] = []
|
||||
const groupNodes = nodes.filter(node => node.type === CUSTOM_GROUP_NODE)
|
||||
|
||||
for (const groupNode of groupNodes) {
|
||||
const groupData = groupNode.data as unknown as CustomGroupNodeData
|
||||
const { group } = groupData
|
||||
|
||||
if (!group)
|
||||
continue
|
||||
|
||||
const { inputNodeId, entryNodeIds, exitPorts } = group
|
||||
|
||||
// Derive edges: input → each entry node
|
||||
for (const entryId of entryNodeIds) {
|
||||
const entryNode = nodesMap[entryId]
|
||||
if (entryNode) {
|
||||
groupInternalEdges.push({
|
||||
id: `group-internal-${inputNodeId}-source-${entryId}-target`,
|
||||
type: 'custom',
|
||||
source: inputNodeId,
|
||||
sourceHandle: 'source',
|
||||
target: entryId,
|
||||
targetHandle: 'target',
|
||||
data: {
|
||||
sourceType: '' as any, // Group input has empty type
|
||||
targetType: entryNode.data.type,
|
||||
_isGroupInternal: true,
|
||||
_groupId: groupNode.id,
|
||||
},
|
||||
zIndex: GROUP_CHILDREN_Z_INDEX,
|
||||
} as Edge)
|
||||
}
|
||||
}
|
||||
|
||||
// Derive edges: each leaf node → exit port
|
||||
for (const exitPort of exitPorts) {
|
||||
const leafNode = nodesMap[exitPort.leafNodeId]
|
||||
if (leafNode) {
|
||||
groupInternalEdges.push({
|
||||
id: `group-internal-${exitPort.leafNodeId}-${exitPort.sourceHandle}-${exitPort.portNodeId}-target`,
|
||||
type: 'custom',
|
||||
source: exitPort.leafNodeId,
|
||||
sourceHandle: exitPort.sourceHandle,
|
||||
target: exitPort.portNodeId,
|
||||
targetHandle: 'target',
|
||||
data: {
|
||||
sourceType: leafNode.data.type,
|
||||
targetType: '' as string, // Exit port has empty type
|
||||
_isGroupInternal: true,
|
||||
_groupId: groupNode.id,
|
||||
},
|
||||
zIndex: GROUP_CHILDREN_Z_INDEX,
|
||||
} as Edge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild isTemp edges for business Group nodes (BlockEnum.Group)
|
||||
// These edges connect the group node to external nodes for visual display
|
||||
const groupTempEdges: Edge[] = []
|
||||
const inboundEdgeIds = new Set<string>()
|
||||
|
||||
nodes.forEach((groupNode) => {
|
||||
if (groupNode.data.type !== BlockEnum.Group)
|
||||
return
|
||||
|
||||
const groupData = groupNode.data as GroupNodeData
|
||||
const {
|
||||
members = [],
|
||||
headNodeIds = [],
|
||||
leafNodeIds = [],
|
||||
handlers = [],
|
||||
} = groupData
|
||||
const memberSet = new Set(members.map(m => m.id))
|
||||
const headSet = new Set(headNodeIds)
|
||||
const leafSet = new Set(leafNodeIds)
|
||||
|
||||
edges.forEach((edge) => {
|
||||
// Inbound edge: source outside group, target is a head node
|
||||
// Use Set to dedupe since multiple head nodes may share same external source
|
||||
if (!memberSet.has(edge.source) && headSet.has(edge.target)) {
|
||||
const sourceHandle = edge.sourceHandle || 'source'
|
||||
const edgeId = `${edge.source}-${sourceHandle}-${groupNode.id}-target`
|
||||
if (!inboundEdgeIds.has(edgeId)) {
|
||||
inboundEdgeIds.add(edgeId)
|
||||
groupTempEdges.push({
|
||||
id: edgeId,
|
||||
type: 'custom',
|
||||
source: edge.source,
|
||||
sourceHandle,
|
||||
target: groupNode.id,
|
||||
targetHandle: 'target',
|
||||
data: {
|
||||
sourceType: edge.data?.sourceType,
|
||||
targetType: BlockEnum.Group,
|
||||
_isTemp: true,
|
||||
},
|
||||
} as Edge)
|
||||
}
|
||||
}
|
||||
|
||||
// Outbound edge: source is a leaf node, target outside group
|
||||
if (leafSet.has(edge.source) && !memberSet.has(edge.target)) {
|
||||
const edgeSourceHandle = edge.sourceHandle || 'source'
|
||||
const handler = handlers.find(
|
||||
h =>
|
||||
h.nodeId === edge.source && h.sourceHandle === edgeSourceHandle,
|
||||
)
|
||||
if (handler) {
|
||||
groupTempEdges.push({
|
||||
id: `${groupNode.id}-${handler.id}-${edge.target}-${edge.targetHandle}`,
|
||||
type: 'custom',
|
||||
source: groupNode.id,
|
||||
sourceHandle: handler.id,
|
||||
target: edge.target!,
|
||||
targetHandle: edge.targetHandle,
|
||||
data: {
|
||||
sourceType: BlockEnum.Group,
|
||||
targetType: edge.data?.targetType,
|
||||
_isTemp: true,
|
||||
},
|
||||
} as Edge)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
nodes: [...nodes, ...newIterationStartNodes, ...newLoopStartNodes],
|
||||
edges: [...edges, ...newEdges],
|
||||
edges: [...edges, ...newEdges, ...groupInternalEdges, ...groupTempEdges],
|
||||
}
|
||||
}
|
||||
|
||||
export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
|
||||
const { nodes, edges } = preprocessNodesAndEdges(
|
||||
cloneDeep(originNodes),
|
||||
cloneDeep(originEdges),
|
||||
)
|
||||
const firstNode = nodes[0]
|
||||
|
||||
if (!firstNode?.position) {
|
||||
@ -206,23 +380,35 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
const iterationOrLoopNodeMap = nodes.reduce((acc, node) => {
|
||||
if (node.parentId) {
|
||||
if (acc[node.parentId])
|
||||
acc[node.parentId].push({ nodeId: node.id, nodeType: node.data.type })
|
||||
else
|
||||
acc[node.parentId] = [{ nodeId: node.id, nodeType: node.data.type }]
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, { nodeId: string, nodeType: BlockEnum }[]>)
|
||||
const iterationOrLoopNodeMap = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.parentId) {
|
||||
if (acc[node.parentId]) {
|
||||
acc[node.parentId].push({
|
||||
nodeId: node.id,
|
||||
nodeType: node.data.type,
|
||||
})
|
||||
}
|
||||
else {
|
||||
acc[node.parentId] = [{ nodeId: node.id, nodeType: node.data.type }]
|
||||
}
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, { nodeId: string, nodeType: BlockEnum }[]>,
|
||||
)
|
||||
|
||||
return nodes.map((node) => {
|
||||
if (!node.type)
|
||||
node.type = CUSTOM_NODE
|
||||
|
||||
const connectedEdges = getConnectedEdges([node], edges)
|
||||
node.data._connectedSourceHandleIds = connectedEdges.filter(edge => edge.source === node.id).map(edge => edge.sourceHandle || 'source')
|
||||
node.data._connectedTargetHandleIds = connectedEdges.filter(edge => edge.target === node.id).map(edge => edge.targetHandle || 'target')
|
||||
node.data._connectedSourceHandleIds = connectedEdges
|
||||
.filter(edge => edge.source === node.id)
|
||||
.map(edge => edge.sourceHandle || 'source')
|
||||
node.data._connectedTargetHandleIds = connectedEdges
|
||||
.filter(edge => edge.target === node.id)
|
||||
.map(edge => edge.targetHandle || 'target')
|
||||
|
||||
if (node.data.type === BlockEnum.IfElse) {
|
||||
const nodeData = node.data as IfElseNodeType
|
||||
@ -237,49 +423,86 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
]
|
||||
}
|
||||
node.data._targetBranches = branchNameCorrect([
|
||||
...(node.data as IfElseNodeType).cases.map(item => ({ id: item.case_id, name: '' })),
|
||||
...(node.data as IfElseNodeType).cases.map(item => ({
|
||||
id: item.case_id,
|
||||
name: '',
|
||||
})),
|
||||
{ id: 'false', name: '' },
|
||||
])
|
||||
// delete conditions and logical_operator if cases is not empty
|
||||
if (nodeData.cases.length > 0 && nodeData.conditions && nodeData.logical_operator) {
|
||||
if (
|
||||
nodeData.cases.length > 0
|
||||
&& nodeData.conditions
|
||||
&& nodeData.logical_operator
|
||||
) {
|
||||
delete nodeData.conditions
|
||||
delete nodeData.logical_operator
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.QuestionClassifier) {
|
||||
node.data._targetBranches = (node.data as QuestionClassifierNodeType).classes.map((topic) => {
|
||||
node.data._targetBranches = (
|
||||
node.data as QuestionClassifierNodeType
|
||||
).classes.map((topic) => {
|
||||
return topic
|
||||
})
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Group) {
|
||||
const groupData = node.data as GroupNodeData
|
||||
if (groupData.handlers?.length) {
|
||||
node.data._targetBranches = groupData.handlers.map(handler => ({
|
||||
id: handler.id,
|
||||
name: handler.label || handler.id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration) {
|
||||
const iterationNodeData = node.data as IterationNodeType
|
||||
iterationNodeData._children = iterationOrLoopNodeMap[node.id] || []
|
||||
iterationNodeData.is_parallel = iterationNodeData.is_parallel || false
|
||||
iterationNodeData.parallel_nums = iterationNodeData.parallel_nums || 10
|
||||
iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||
iterationNodeData.error_handle_mode
|
||||
= iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||
}
|
||||
|
||||
// TODO: loop error handle mode
|
||||
if (node.data.type === BlockEnum.Loop) {
|
||||
const loopNodeData = node.data as LoopNodeType
|
||||
loopNodeData._children = iterationOrLoopNodeMap[node.id] || []
|
||||
loopNodeData.error_handle_mode = loopNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||
loopNodeData.error_handle_mode
|
||||
= loopNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||
}
|
||||
|
||||
// legacy provider handle
|
||||
if (node.data.type === BlockEnum.LLM)
|
||||
(node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
|
||||
if (node.data.type === BlockEnum.LLM) {
|
||||
(node as any).data.model.provider = correctModelProvider(
|
||||
(node as any).data.model.provider,
|
||||
)
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.KnowledgeRetrieval && (node as any).data.multiple_retrieval_config?.reranking_model)
|
||||
(node as any).data.multiple_retrieval_config.reranking_model.provider = correctModelProvider((node as any).data.multiple_retrieval_config?.reranking_model.provider)
|
||||
if (
|
||||
node.data.type === BlockEnum.KnowledgeRetrieval
|
||||
&& (node as any).data.multiple_retrieval_config?.reranking_model
|
||||
) {
|
||||
(node as any).data.multiple_retrieval_config.reranking_model.provider
|
||||
= correctModelProvider(
|
||||
(node as any).data.multiple_retrieval_config?.reranking_model.provider,
|
||||
)
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.QuestionClassifier)
|
||||
(node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
|
||||
if (node.data.type === BlockEnum.QuestionClassifier) {
|
||||
(node as any).data.model.provider = correctModelProvider(
|
||||
(node as any).data.model.provider,
|
||||
)
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.ParameterExtractor)
|
||||
(node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
|
||||
if (node.data.type === BlockEnum.ParameterExtractor) {
|
||||
(node as any).data.model.provider = correctModelProvider(
|
||||
(node as any).data.model.provider,
|
||||
)
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) {
|
||||
node.data.retry_config = {
|
||||
@ -289,14 +512,21 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Tool && !(node as Node<ToolNodeType>).data.version && !(node as Node<ToolNodeType>).data.tool_node_version) {
|
||||
if (
|
||||
node.data.type === BlockEnum.Tool
|
||||
&& !(node as Node<ToolNodeType>).data.version
|
||||
&& !(node as Node<ToolNodeType>).data.tool_node_version
|
||||
) {
|
||||
(node as Node<ToolNodeType>).data.tool_node_version = '2'
|
||||
|
||||
const toolConfigurations = (node as Node<ToolNodeType>).data.tool_configurations
|
||||
if (toolConfigurations && Object.keys(toolConfigurations).length > 0) {
|
||||
const newValues = { ...toolConfigurations }
|
||||
Object.keys(toolConfigurations).forEach((key) => {
|
||||
if (typeof toolConfigurations[key] !== 'object' || toolConfigurations[key] === null) {
|
||||
if (
|
||||
typeof toolConfigurations[key] !== 'object'
|
||||
|| toolConfigurations[key] === null
|
||||
) {
|
||||
newValues[key] = {
|
||||
type: 'constant',
|
||||
value: toolConfigurations[key],
|
||||
@ -312,50 +542,62 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
}
|
||||
|
||||
export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
|
||||
const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
|
||||
const { nodes, edges } = preprocessNodesAndEdges(
|
||||
cloneDeep(originNodes),
|
||||
cloneDeep(originEdges),
|
||||
)
|
||||
let selectedNode: Node | null = null
|
||||
const nodesMap = nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node
|
||||
const nodesMap = nodes.reduce(
|
||||
(acc, node) => {
|
||||
acc[node.id] = node
|
||||
|
||||
if (node.data?.selected)
|
||||
selectedNode = node
|
||||
if (node.data?.selected)
|
||||
selectedNode = node
|
||||
|
||||
return acc
|
||||
}, {} as Record<string, Node>)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Node>,
|
||||
)
|
||||
|
||||
const cycleEdges = getCycleEdges(nodes, edges)
|
||||
return edges.filter((edge) => {
|
||||
return !cycleEdges.find(cycEdge => cycEdge.source === edge.source && cycEdge.target === edge.target)
|
||||
}).map((edge) => {
|
||||
edge.type = 'custom'
|
||||
return edges
|
||||
.filter((edge) => {
|
||||
return !cycleEdges.find(
|
||||
cycEdge =>
|
||||
cycEdge.source === edge.source && cycEdge.target === edge.target,
|
||||
)
|
||||
})
|
||||
.map((edge) => {
|
||||
edge.type = 'custom'
|
||||
|
||||
if (!edge.sourceHandle)
|
||||
edge.sourceHandle = 'source'
|
||||
if (!edge.sourceHandle)
|
||||
edge.sourceHandle = 'source'
|
||||
|
||||
if (!edge.targetHandle)
|
||||
edge.targetHandle = 'target'
|
||||
if (!edge.targetHandle)
|
||||
edge.targetHandle = 'target'
|
||||
|
||||
if (!edge.data?.sourceType && edge.source && nodesMap[edge.source]) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
sourceType: nodesMap[edge.source].data.type!,
|
||||
} as any
|
||||
}
|
||||
if (!edge.data?.sourceType && edge.source && nodesMap[edge.source]) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
sourceType: nodesMap[edge.source].data.type!,
|
||||
} as any
|
||||
}
|
||||
|
||||
if (!edge.data?.targetType && edge.target && nodesMap[edge.target]) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
targetType: nodesMap[edge.target].data.type!,
|
||||
} as any
|
||||
}
|
||||
if (!edge.data?.targetType && edge.target && nodesMap[edge.target]) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
targetType: nodesMap[edge.target].data.type!,
|
||||
} as any
|
||||
}
|
||||
|
||||
if (selectedNode) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
_connectedNodeIsSelected: edge.source === selectedNode.id || edge.target === selectedNode.id,
|
||||
} as any
|
||||
}
|
||||
if (selectedNode) {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
_connectedNodeIsSelected:
|
||||
edge.source === selectedNode.id || edge.target === selectedNode.id,
|
||||
} as any
|
||||
}
|
||||
|
||||
return edge
|
||||
})
|
||||
return edge
|
||||
})
|
||||
}
|
||||
|
||||
@ -158,6 +158,95 @@ export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const getCommonPredecessorNodeIds = (selectedNodeIds: string[], edges: Edge[]) => {
|
||||
const uniqSelectedNodeIds = Array.from(new Set(selectedNodeIds))
|
||||
if (uniqSelectedNodeIds.length <= 1)
|
||||
return []
|
||||
|
||||
const selectedNodeIdSet = new Set(uniqSelectedNodeIds)
|
||||
const predecessorNodeIdsMap = new Map<string, Set<string>>()
|
||||
|
||||
edges.forEach((edge) => {
|
||||
if (!selectedNodeIdSet.has(edge.target))
|
||||
return
|
||||
|
||||
const predecessors = predecessorNodeIdsMap.get(edge.target) ?? new Set<string>()
|
||||
predecessors.add(edge.source)
|
||||
predecessorNodeIdsMap.set(edge.target, predecessors)
|
||||
})
|
||||
|
||||
let commonPredecessorNodeIds: Set<string> | null = null
|
||||
|
||||
uniqSelectedNodeIds.forEach((nodeId) => {
|
||||
const predecessors = predecessorNodeIdsMap.get(nodeId) ?? new Set<string>()
|
||||
|
||||
if (!commonPredecessorNodeIds) {
|
||||
commonPredecessorNodeIds = new Set(predecessors)
|
||||
return
|
||||
}
|
||||
|
||||
Array.from(commonPredecessorNodeIds).forEach((predecessorNodeId) => {
|
||||
if (!predecessors.has(predecessorNodeId))
|
||||
commonPredecessorNodeIds!.delete(predecessorNodeId)
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from(commonPredecessorNodeIds ?? []).sort()
|
||||
}
|
||||
|
||||
export type PredecessorHandle = {
|
||||
nodeId: string
|
||||
handleId: string
|
||||
}
|
||||
|
||||
export const getCommonPredecessorHandles = (targetNodeIds: string[], edges: Edge[]): PredecessorHandle[] => {
|
||||
const uniqTargetNodeIds = Array.from(new Set(targetNodeIds))
|
||||
if (uniqTargetNodeIds.length === 0)
|
||||
return []
|
||||
|
||||
// Get the "direct predecessor handler", which is:
|
||||
// - edge.source (predecessor node)
|
||||
// - edge.sourceHandle (the specific output handle of the predecessor; defaults to 'source' if not set)
|
||||
// Used to handle multi-handle branch scenarios like If-Else / Classifier.
|
||||
const targetNodeIdSet = new Set(uniqTargetNodeIds)
|
||||
const predecessorHandleMap = new Map<string, Set<string>>() // targetNodeId -> Set<`${source}\0${handleId}`>
|
||||
const delimiter = '\u0000'
|
||||
|
||||
edges.forEach((edge) => {
|
||||
if (!targetNodeIdSet.has(edge.target))
|
||||
return
|
||||
|
||||
const predecessors = predecessorHandleMap.get(edge.target) ?? new Set<string>()
|
||||
const handleId = edge.sourceHandle || 'source'
|
||||
predecessors.add(`${edge.source}${delimiter}${handleId}`)
|
||||
predecessorHandleMap.set(edge.target, predecessors)
|
||||
})
|
||||
|
||||
// Intersect predecessor handlers of all targets, keeping only handlers common to all targets.
|
||||
let commonKeys: Set<string> | null = null
|
||||
|
||||
uniqTargetNodeIds.forEach((nodeId) => {
|
||||
const keys = predecessorHandleMap.get(nodeId) ?? new Set<string>()
|
||||
|
||||
if (!commonKeys) {
|
||||
commonKeys = new Set(keys)
|
||||
return
|
||||
}
|
||||
|
||||
Array.from(commonKeys).forEach((key) => {
|
||||
if (!keys.has(key))
|
||||
commonKeys!.delete(key)
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from<string>(commonKeys ?? [])
|
||||
.map((key) => {
|
||||
const [nodeId, handleId] = key.split(delimiter)
|
||||
return { nodeId, handleId }
|
||||
})
|
||||
.sort((a, b) => a.nodeId.localeCompare(b.nodeId) || a.handleId.localeCompare(b.handleId))
|
||||
}
|
||||
|
||||
export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => {
|
||||
const idMap = nodes.reduce((acc, node) => {
|
||||
acc[node.id] = uuid4()
|
||||
|
||||
Reference in New Issue
Block a user