Merge branch 'main' into feat/grouping-branching

# Conflicts:
#	web/package.json
This commit is contained in:
zhsama
2026-01-06 22:00:01 +08:00
156 changed files with 5890 additions and 1553 deletions

View File

@ -550,6 +550,7 @@ export const useIsNodeInLoop = (loopId: string) => {
return false
if (node.parentId === loopId)
return true
return false

View File

@ -5,14 +5,9 @@ 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 {
@ -24,22 +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 { 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)
@ -50,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)
@ -69,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)
@ -90,22 +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)
const hasBusinessGroupNode = nodes.some(
node => node.data.type === BlockEnum.Group,
)
if (!hasIterationNode && !hasLoopNode && !hasGroupNode && !hasBusinessGroupNode) {
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 = []
@ -117,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)
@ -127,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)
@ -137,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
@ -145,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 {
@ -185,11 +216,18 @@ 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)
@ -240,7 +278,7 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
targetHandle: 'target',
data: {
sourceType: leafNode.data.type,
targetType: '' as any, // Exit port has empty type
targetType: '' as string, // Exit port has empty type
_isGroupInternal: true,
_groupId: groupNode.id,
},
@ -260,7 +298,12 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
return
const groupData = groupNode.data as GroupNodeData
const { members = [], headNodeIds = [], leafNodeIds = [], handlers = [] } = groupData
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)
@ -293,7 +336,8 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
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,
h =>
h.nodeId === edge.source && h.sourceHandle === edgeSourceHandle,
)
if (handler) {
groupTempEdges.push({
@ -321,7 +365,10 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
}
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) {
@ -333,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
@ -364,18 +423,27 @@ 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
})
}
@ -395,28 +463,46 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
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 = {
@ -426,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],
@ -449,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
})
}