Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

workflow-init.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. import {
  2. getConnectedEdges,
  3. } from 'reactflow'
  4. import {
  5. cloneDeep,
  6. } from 'lodash-es'
  7. import type {
  8. Edge,
  9. Node,
  10. } from '../types'
  11. import {
  12. BlockEnum,
  13. ErrorHandleMode,
  14. } from '../types'
  15. import {
  16. CUSTOM_NODE,
  17. DEFAULT_RETRY_INTERVAL,
  18. DEFAULT_RETRY_MAX,
  19. ITERATION_CHILDREN_Z_INDEX,
  20. LOOP_CHILDREN_Z_INDEX,
  21. NODE_WIDTH_X_OFFSET,
  22. START_INITIAL_POSITION,
  23. } from '../constants'
  24. import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
  25. import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
  26. import type { QuestionClassifierNodeType } from '../nodes/question-classifier/types'
  27. import type { IfElseNodeType } from '../nodes/if-else/types'
  28. import { branchNameCorrect } from '../nodes/if-else/utils'
  29. import type { IterationNodeType } from '../nodes/iteration/types'
  30. import type { LoopNodeType } from '../nodes/loop/types'
  31. import type { ToolNodeType } from '../nodes/tool/types'
  32. import {
  33. getIterationStartNode,
  34. getLoopStartNode,
  35. } from '.'
  36. import { correctModelProvider } from '@/utils'
  37. const WHITE = 'WHITE'
  38. const GRAY = 'GRAY'
  39. const BLACK = 'BLACK'
  40. const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjList: Record<string, string[]>, stack: string[]) => {
  41. color[nodeId] = GRAY
  42. stack.push(nodeId)
  43. for (let i = 0; i < adjList[nodeId].length; ++i) {
  44. const childId = adjList[nodeId][i]
  45. if (color[childId] === GRAY) {
  46. stack.push(childId)
  47. return true
  48. }
  49. if (color[childId] === WHITE && isCyclicUtil(childId, color, adjList, stack))
  50. return true
  51. }
  52. color[nodeId] = BLACK
  53. if (stack.length > 0 && stack[stack.length - 1] === nodeId)
  54. stack.pop()
  55. return false
  56. }
  57. const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
  58. const adjList: Record<string, string[]> = {}
  59. const color: Record<string, string> = {}
  60. const stack: string[] = []
  61. for (const node of nodes) {
  62. color[node.id] = WHITE
  63. adjList[node.id] = []
  64. }
  65. for (const edge of edges)
  66. adjList[edge.source]?.push(edge.target)
  67. for (let i = 0; i < nodes.length; i++) {
  68. if (color[nodes[i].id] === WHITE)
  69. isCyclicUtil(nodes[i].id, color, adjList, stack)
  70. }
  71. const cycleEdges = []
  72. if (stack.length > 0) {
  73. const cycleNodes = new Set(stack)
  74. for (const edge of edges) {
  75. if (cycleNodes.has(edge.source) && cycleNodes.has(edge.target))
  76. cycleEdges.push(edge)
  77. }
  78. }
  79. return cycleEdges
  80. }
  81. export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
  82. const hasIterationNode = nodes.some(node => node.data.type === BlockEnum.Iteration)
  83. const hasLoopNode = nodes.some(node => node.data.type === BlockEnum.Loop)
  84. if (!hasIterationNode && !hasLoopNode) {
  85. return {
  86. nodes,
  87. edges,
  88. }
  89. }
  90. const nodesMap = nodes.reduce((prev, next) => {
  91. prev[next.id] = next
  92. return prev
  93. }, {} as Record<string, Node>)
  94. const iterationNodesWithStartNode = []
  95. const iterationNodesWithoutStartNode = []
  96. const loopNodesWithStartNode = []
  97. const loopNodesWithoutStartNode = []
  98. for (let i = 0; i < nodes.length; i++) {
  99. const currentNode = nodes[i] as Node<IterationNodeType | LoopNodeType>
  100. if (currentNode.data.type === BlockEnum.Iteration) {
  101. if (currentNode.data.start_node_id) {
  102. if (nodesMap[currentNode.data.start_node_id]?.type !== CUSTOM_ITERATION_START_NODE)
  103. iterationNodesWithStartNode.push(currentNode)
  104. }
  105. else {
  106. iterationNodesWithoutStartNode.push(currentNode)
  107. }
  108. }
  109. if (currentNode.data.type === BlockEnum.Loop) {
  110. if (currentNode.data.start_node_id) {
  111. if (nodesMap[currentNode.data.start_node_id]?.type !== CUSTOM_LOOP_START_NODE)
  112. loopNodesWithStartNode.push(currentNode)
  113. }
  114. else {
  115. loopNodesWithoutStartNode.push(currentNode)
  116. }
  117. }
  118. }
  119. const newIterationStartNodesMap = {} as Record<string, Node>
  120. const newIterationStartNodes = [...iterationNodesWithStartNode, ...iterationNodesWithoutStartNode].map((iterationNode, index) => {
  121. const newNode = getIterationStartNode(iterationNode.id)
  122. newNode.id = newNode.id + index
  123. newIterationStartNodesMap[iterationNode.id] = newNode
  124. return newNode
  125. })
  126. const newLoopStartNodesMap = {} as Record<string, Node>
  127. const newLoopStartNodes = [...loopNodesWithStartNode, ...loopNodesWithoutStartNode].map((loopNode, index) => {
  128. const newNode = getLoopStartNode(loopNode.id)
  129. newNode.id = newNode.id + index
  130. newLoopStartNodesMap[loopNode.id] = newNode
  131. return newNode
  132. })
  133. const newEdges = [...iterationNodesWithStartNode, ...loopNodesWithStartNode].map((nodeItem) => {
  134. const isIteration = nodeItem.data.type === BlockEnum.Iteration
  135. const newNode = (isIteration ? newIterationStartNodesMap : newLoopStartNodesMap)[nodeItem.id]
  136. const startNode = nodesMap[nodeItem.data.start_node_id]
  137. const source = newNode.id
  138. const sourceHandle = 'source'
  139. const target = startNode.id
  140. const targetHandle = 'target'
  141. const parentNode = nodes.find(node => node.id === startNode.parentId) || null
  142. const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  143. const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
  144. return {
  145. id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
  146. type: 'custom',
  147. source,
  148. sourceHandle,
  149. target,
  150. targetHandle,
  151. data: {
  152. sourceType: newNode.data.type,
  153. targetType: startNode.data.type,
  154. isInIteration,
  155. iteration_id: isInIteration ? startNode.parentId : undefined,
  156. isInLoop,
  157. loop_id: isInLoop ? startNode.parentId : undefined,
  158. _connectedNodeIsSelected: true,
  159. },
  160. zIndex: isIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX,
  161. }
  162. })
  163. nodes.forEach((node) => {
  164. if (node.data.type === BlockEnum.Iteration && newIterationStartNodesMap[node.id])
  165. (node.data as IterationNodeType).start_node_id = newIterationStartNodesMap[node.id].id
  166. if (node.data.type === BlockEnum.Loop && newLoopStartNodesMap[node.id])
  167. (node.data as LoopNodeType).start_node_id = newLoopStartNodesMap[node.id].id
  168. })
  169. return {
  170. nodes: [...nodes, ...newIterationStartNodes, ...newLoopStartNodes],
  171. edges: [...edges, ...newEdges],
  172. }
  173. }
  174. export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
  175. const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
  176. const firstNode = nodes[0]
  177. if (!firstNode?.position) {
  178. nodes.forEach((node, index) => {
  179. node.position = {
  180. x: START_INITIAL_POSITION.x + index * NODE_WIDTH_X_OFFSET,
  181. y: START_INITIAL_POSITION.y,
  182. }
  183. })
  184. }
  185. const iterationOrLoopNodeMap = nodes.reduce((acc, node) => {
  186. if (node.parentId) {
  187. if (acc[node.parentId])
  188. acc[node.parentId].push({ nodeId: node.id, nodeType: node.data.type })
  189. else
  190. acc[node.parentId] = [{ nodeId: node.id, nodeType: node.data.type }]
  191. }
  192. return acc
  193. }, {} as Record<string, { nodeId: string; nodeType: BlockEnum }[]>)
  194. return nodes.map((node) => {
  195. if (!node.type)
  196. node.type = CUSTOM_NODE
  197. const connectedEdges = getConnectedEdges([node], edges)
  198. node.data._connectedSourceHandleIds = connectedEdges.filter(edge => edge.source === node.id).map(edge => edge.sourceHandle || 'source')
  199. node.data._connectedTargetHandleIds = connectedEdges.filter(edge => edge.target === node.id).map(edge => edge.targetHandle || 'target')
  200. if (node.data.type === BlockEnum.IfElse) {
  201. const nodeData = node.data as IfElseNodeType
  202. if (!nodeData.cases && nodeData.logical_operator && nodeData.conditions) {
  203. (node.data as IfElseNodeType).cases = [
  204. {
  205. case_id: 'true',
  206. logical_operator: nodeData.logical_operator,
  207. conditions: nodeData.conditions,
  208. },
  209. ]
  210. }
  211. node.data._targetBranches = branchNameCorrect([
  212. ...(node.data as IfElseNodeType).cases.map(item => ({ id: item.case_id, name: '' })),
  213. { id: 'false', name: '' },
  214. ])
  215. }
  216. if (node.data.type === BlockEnum.QuestionClassifier) {
  217. node.data._targetBranches = (node.data as QuestionClassifierNodeType).classes.map((topic) => {
  218. return topic
  219. })
  220. }
  221. if (node.data.type === BlockEnum.Iteration) {
  222. const iterationNodeData = node.data as IterationNodeType
  223. iterationNodeData._children = iterationOrLoopNodeMap[node.id] || []
  224. iterationNodeData.is_parallel = iterationNodeData.is_parallel || false
  225. iterationNodeData.parallel_nums = iterationNodeData.parallel_nums || 10
  226. iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
  227. }
  228. // TODO: loop error handle mode
  229. if (node.data.type === BlockEnum.Loop) {
  230. const loopNodeData = node.data as LoopNodeType
  231. loopNodeData._children = iterationOrLoopNodeMap[node.id] || []
  232. loopNodeData.error_handle_mode = loopNodeData.error_handle_mode || ErrorHandleMode.Terminated
  233. }
  234. // legacy provider handle
  235. if (node.data.type === BlockEnum.LLM)
  236. (node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
  237. if (node.data.type === BlockEnum.KnowledgeRetrieval && (node as any).data.multiple_retrieval_config?.reranking_model)
  238. (node as any).data.multiple_retrieval_config.reranking_model.provider = correctModelProvider((node as any).data.multiple_retrieval_config?.reranking_model.provider)
  239. if (node.data.type === BlockEnum.QuestionClassifier)
  240. (node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
  241. if (node.data.type === BlockEnum.ParameterExtractor)
  242. (node as any).data.model.provider = correctModelProvider((node as any).data.model.provider)
  243. if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) {
  244. node.data.retry_config = {
  245. retry_enabled: true,
  246. max_retries: DEFAULT_RETRY_MAX,
  247. retry_interval: DEFAULT_RETRY_INTERVAL,
  248. }
  249. }
  250. if (node.data.type === BlockEnum.Tool && !(node as Node<ToolNodeType>).data.version && !(node as Node<ToolNodeType>).data.tool_node_version) {
  251. (node as Node<ToolNodeType>).data.tool_node_version = '2'
  252. const toolConfigurations = (node as Node<ToolNodeType>).data.tool_configurations
  253. if (toolConfigurations && Object.keys(toolConfigurations).length > 0) {
  254. const newValues = { ...toolConfigurations }
  255. Object.keys(toolConfigurations).forEach((key) => {
  256. if (typeof toolConfigurations[key] !== 'object' || toolConfigurations[key] === null) {
  257. newValues[key] = {
  258. type: 'constant',
  259. value: toolConfigurations[key],
  260. }
  261. }
  262. });
  263. (node as Node<ToolNodeType>).data.tool_configurations = newValues
  264. }
  265. }
  266. return node
  267. })
  268. }
  269. export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
  270. const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
  271. let selectedNode: Node | null = null
  272. const nodesMap = nodes.reduce((acc, node) => {
  273. acc[node.id] = node
  274. if (node.data?.selected)
  275. selectedNode = node
  276. return acc
  277. }, {} as Record<string, Node>)
  278. const cycleEdges = getCycleEdges(nodes, edges)
  279. return edges.filter((edge) => {
  280. return !cycleEdges.find(cycEdge => cycEdge.source === edge.source && cycEdge.target === edge.target)
  281. }).map((edge) => {
  282. edge.type = 'custom'
  283. if (!edge.sourceHandle)
  284. edge.sourceHandle = 'source'
  285. if (!edge.targetHandle)
  286. edge.targetHandle = 'target'
  287. if (!edge.data?.sourceType && edge.source && nodesMap[edge.source]) {
  288. edge.data = {
  289. ...edge.data,
  290. sourceType: nodesMap[edge.source].data.type!,
  291. } as any
  292. }
  293. if (!edge.data?.targetType && edge.target && nodesMap[edge.target]) {
  294. edge.data = {
  295. ...edge.data,
  296. targetType: nodesMap[edge.target].data.type!,
  297. } as any
  298. }
  299. if (selectedNode) {
  300. edge.data = {
  301. ...edge.data,
  302. _connectedNodeIsSelected: edge.source === selectedNode.id || edge.target === selectedNode.id,
  303. } as any
  304. }
  305. return edge
  306. })
  307. }