utils.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import {
  2. Position,
  3. getConnectedEdges,
  4. getOutgoers,
  5. } from 'reactflow'
  6. import dagre from 'dagre'
  7. import { v4 as uuid4 } from 'uuid'
  8. import {
  9. cloneDeep,
  10. uniqBy,
  11. } from 'lodash-es'
  12. import type {
  13. Edge,
  14. InputVar,
  15. Node,
  16. ToolWithProvider,
  17. } from './types'
  18. import { BlockEnum } from './types'
  19. import {
  20. NODE_WIDTH_X_OFFSET,
  21. START_INITIAL_POSITION,
  22. } from './constants'
  23. import type { QuestionClassifierNodeType } from './nodes/question-classifier/types'
  24. import type { ToolNodeType } from './nodes/tool/types'
  25. import { CollectionType } from '@/app/components/tools/types'
  26. import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
  27. const WHITE = 'WHITE'
  28. const GRAY = 'GRAY'
  29. const BLACK = 'BLACK'
  30. const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjaList: Record<string, string[]>, stack: string[]) => {
  31. color[nodeId] = GRAY
  32. stack.push(nodeId)
  33. for (let i = 0; i < adjaList[nodeId].length; ++i) {
  34. const childId = adjaList[nodeId][i]
  35. if (color[childId] === GRAY) {
  36. stack.push(childId)
  37. return true
  38. }
  39. if (color[childId] === WHITE && isCyclicUtil(childId, color, adjaList, stack))
  40. return true
  41. }
  42. color[nodeId] = BLACK
  43. if (stack.length > 0 && stack[stack.length - 1] === nodeId)
  44. stack.pop()
  45. return false
  46. }
  47. const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
  48. const adjaList: Record<string, string[]> = {}
  49. const color: Record<string, string> = {}
  50. const stack: string[] = []
  51. for (const node of nodes) {
  52. color[node.id] = WHITE
  53. adjaList[node.id] = []
  54. }
  55. for (const edge of edges)
  56. adjaList[edge.source]?.push(edge.target)
  57. for (let i = 0; i < nodes.length; i++) {
  58. if (color[nodes[i].id] === WHITE)
  59. isCyclicUtil(nodes[i].id, color, adjaList, stack)
  60. }
  61. const cycleEdges = []
  62. if (stack.length > 0) {
  63. const cycleNodes = new Set(stack)
  64. for (const edge of edges) {
  65. if (cycleNodes.has(edge.source) && cycleNodes.has(edge.target))
  66. cycleEdges.push(edge)
  67. }
  68. }
  69. return cycleEdges
  70. }
  71. export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
  72. const nodes = cloneDeep(originNodes)
  73. const edges = cloneDeep(originEdges)
  74. const firstNode = nodes[0]
  75. if (!firstNode?.position) {
  76. nodes.forEach((node, index) => {
  77. node.position = {
  78. x: START_INITIAL_POSITION.x + index * NODE_WIDTH_X_OFFSET,
  79. y: START_INITIAL_POSITION.y,
  80. }
  81. })
  82. }
  83. return nodes.map((node) => {
  84. node.type = 'custom'
  85. const connectedEdges = getConnectedEdges([node], edges)
  86. node.data._connectedSourceHandleIds = connectedEdges.filter(edge => edge.source === node.id).map(edge => edge.sourceHandle || 'source')
  87. node.data._connectedTargetHandleIds = connectedEdges.filter(edge => edge.target === node.id).map(edge => edge.targetHandle || 'target')
  88. if (node.data.type === BlockEnum.IfElse) {
  89. node.data._targetBranches = [
  90. {
  91. id: 'true',
  92. name: 'IS TRUE',
  93. },
  94. {
  95. id: 'false',
  96. name: 'IS FALSE',
  97. },
  98. ]
  99. }
  100. if (node.data.type === BlockEnum.QuestionClassifier) {
  101. node.data._targetBranches = (node.data as QuestionClassifierNodeType).classes.map((topic) => {
  102. return topic
  103. })
  104. }
  105. return node
  106. })
  107. }
  108. export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
  109. const nodes = cloneDeep(originNodes)
  110. const edges = cloneDeep(originEdges)
  111. let selectedNode: Node | null = null
  112. const nodesMap = nodes.reduce((acc, node) => {
  113. acc[node.id] = node
  114. if (node.data?.selected)
  115. selectedNode = node
  116. return acc
  117. }, {} as Record<string, Node>)
  118. const cycleEdges = getCycleEdges(nodes, edges)
  119. return edges.filter((edge) => {
  120. return !cycleEdges.find(cycEdge => cycEdge.source === edge.source && cycEdge.target === edge.target)
  121. }).map((edge) => {
  122. edge.type = 'custom'
  123. if (!edge.sourceHandle)
  124. edge.sourceHandle = 'source'
  125. if (!edge.targetHandle)
  126. edge.targetHandle = 'target'
  127. if (!edge.data?.sourceType && edge.source) {
  128. edge.data = {
  129. ...edge.data,
  130. sourceType: nodesMap[edge.source].data.type!,
  131. } as any
  132. }
  133. if (!edge.data?.targetType && edge.target) {
  134. edge.data = {
  135. ...edge.data,
  136. targetType: nodesMap[edge.target].data.type!,
  137. } as any
  138. }
  139. if (selectedNode) {
  140. edge.data = {
  141. ...edge.data,
  142. _connectedNodeIsSelected: edge.source === selectedNode.id || edge.target === selectedNode.id,
  143. } as any
  144. }
  145. return edge
  146. })
  147. }
  148. const dagreGraph = new dagre.graphlib.Graph()
  149. dagreGraph.setDefaultEdgeLabel(() => ({}))
  150. export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => {
  151. const nodes = cloneDeep(originNodes)
  152. const edges = cloneDeep(originEdges)
  153. dagreGraph.setGraph({
  154. rankdir: 'LR',
  155. align: 'UL',
  156. nodesep: 40,
  157. ranksep: 60,
  158. })
  159. nodes.forEach((node) => {
  160. dagreGraph.setNode(node.id, { width: node.width, height: node.height })
  161. })
  162. edges.forEach((edge) => {
  163. dagreGraph.setEdge(edge.source, edge.target)
  164. })
  165. dagre.layout(dagreGraph)
  166. return dagreGraph
  167. }
  168. export const canRunBySingle = (nodeType: BlockEnum) => {
  169. return nodeType === BlockEnum.LLM
  170. || nodeType === BlockEnum.KnowledgeRetrieval
  171. || nodeType === BlockEnum.Code
  172. || nodeType === BlockEnum.TemplateTransform
  173. || nodeType === BlockEnum.QuestionClassifier
  174. || nodeType === BlockEnum.HttpRequest
  175. || nodeType === BlockEnum.Tool
  176. }
  177. type ConnectedSourceOrTargetNodesChange = {
  178. type: string
  179. edge: Edge
  180. }[]
  181. export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSourceOrTargetNodesChange, nodes: Node[]) => {
  182. const nodesConnectedSourceOrTargetHandleIdsMap = {} as Record<string, any>
  183. changes.forEach((change) => {
  184. const {
  185. edge,
  186. type,
  187. } = change
  188. const sourceNode = nodes.find(node => node.id === edge.source)!
  189. if (sourceNode) {
  190. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id] = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id] || {
  191. _connectedSourceHandleIds: [...(sourceNode?.data._connectedSourceHandleIds || [])],
  192. _connectedTargetHandleIds: [...(sourceNode?.data._connectedTargetHandleIds || [])],
  193. }
  194. }
  195. const targetNode = nodes.find(node => node.id === edge.target)!
  196. if (targetNode) {
  197. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id] = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id] || {
  198. _connectedSourceHandleIds: [...(targetNode?.data._connectedSourceHandleIds || [])],
  199. _connectedTargetHandleIds: [...(targetNode?.data._connectedTargetHandleIds || [])],
  200. }
  201. }
  202. if (sourceNode) {
  203. if (type === 'remove')
  204. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.filter((handleId: string) => handleId !== edge.sourceHandle)
  205. if (type === 'add')
  206. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.push(edge.sourceHandle || 'source')
  207. }
  208. if (targetNode) {
  209. if (type === 'remove')
  210. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.filter((handleId: string) => handleId !== edge.targetHandle)
  211. if (type === 'add')
  212. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.push(edge.targetHandle || 'target')
  213. }
  214. })
  215. return nodesConnectedSourceOrTargetHandleIdsMap
  216. }
  217. export const generateNewNode = ({ data, position, id }: Pick<Node, 'data' | 'position'> & { id?: string }) => {
  218. return {
  219. id: id || `${Date.now()}`,
  220. type: 'custom',
  221. data,
  222. position,
  223. targetPosition: Position.Left,
  224. sourcePosition: Position.Right,
  225. } as Node
  226. }
  227. export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
  228. const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
  229. if (!startNode) {
  230. return {
  231. validNodes: [],
  232. maxDepth: 0,
  233. }
  234. }
  235. const list: Node[] = [startNode]
  236. let maxDepth = 1
  237. const traverse = (root: Node, depth: number) => {
  238. if (depth > maxDepth)
  239. maxDepth = depth
  240. const outgoers = getOutgoers(root, nodes, edges)
  241. if (outgoers.length) {
  242. outgoers.forEach((outgoer) => {
  243. list.push(outgoer)
  244. traverse(outgoer, depth + 1)
  245. })
  246. }
  247. else {
  248. list.push(root)
  249. }
  250. }
  251. traverse(startNode, maxDepth)
  252. return {
  253. validNodes: uniqBy(list, 'id'),
  254. maxDepth,
  255. }
  256. }
  257. export const getToolCheckParams = (
  258. toolData: ToolNodeType,
  259. buildInTools: ToolWithProvider[],
  260. customTools: ToolWithProvider[],
  261. language: string,
  262. ) => {
  263. const { provider_id, provider_type, tool_name } = toolData
  264. const isBuiltIn = provider_type === CollectionType.builtIn
  265. const currentTools = isBuiltIn ? buildInTools : customTools
  266. const currCollection = currentTools.find(item => item.id === provider_id)
  267. const currTool = currCollection?.tools.find(tool => tool.name === tool_name)
  268. const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : []
  269. const toolInputVarSchema = formSchemas.filter((item: any) => item.form === 'llm')
  270. const toolSettingSchema = formSchemas.filter((item: any) => item.form !== 'llm')
  271. return {
  272. toolInputsSchema: (() => {
  273. const formInputs: InputVar[] = []
  274. toolInputVarSchema.forEach((item: any) => {
  275. formInputs.push({
  276. label: item.label[language] || item.label.en_US,
  277. variable: item.variable,
  278. type: item.type,
  279. required: item.required,
  280. })
  281. })
  282. return formInputs
  283. })(),
  284. notAuthed: isBuiltIn && !!currCollection?.allow_delete && !currCollection?.is_team_authorization,
  285. toolSettingSchema,
  286. language,
  287. }
  288. }
  289. export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => {
  290. const idMap = nodes.reduce((acc, node) => {
  291. acc[node.id] = uuid4()
  292. return acc
  293. }, {} as Record<string, string>)
  294. const newNodes = nodes.map((node) => {
  295. return {
  296. ...node,
  297. id: idMap[node.id],
  298. }
  299. })
  300. const newEdges = edges.map((edge) => {
  301. return {
  302. ...edge,
  303. source: idMap[edge.source],
  304. target: idMap[edge.target],
  305. }
  306. })
  307. return [newNodes, newEdges] as [Node[], Edge[]]
  308. }