您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

use-workflow.ts 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. import {
  2. useCallback,
  3. } from 'react'
  4. import { uniqBy } from 'lodash-es'
  5. import {
  6. getIncomers,
  7. getOutgoers,
  8. useStoreApi,
  9. } from 'reactflow'
  10. import type {
  11. Connection,
  12. } from 'reactflow'
  13. import type {
  14. BlockEnum,
  15. Edge,
  16. Node,
  17. ValueSelector,
  18. } from '../types'
  19. import {
  20. WorkflowRunningStatus,
  21. } from '../types'
  22. import {
  23. useStore,
  24. useWorkflowStore,
  25. } from '../store'
  26. import {
  27. SUPPORT_OUTPUT_VARS_NODE,
  28. } from '../constants'
  29. import type { IterationNodeType } from '../nodes/iteration/types'
  30. import type { LoopNodeType } from '../nodes/loop/types'
  31. import { CUSTOM_NOTE_NODE } from '../note-node/constants'
  32. import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
  33. import { useAvailableBlocks } from './use-available-blocks'
  34. import { useStore as useAppStore } from '@/app/components/app/store'
  35. import {
  36. fetchAllBuiltInTools,
  37. fetchAllCustomTools,
  38. fetchAllMCPTools,
  39. fetchAllWorkflowTools,
  40. } from '@/service/tools'
  41. import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
  42. import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
  43. import { basePath } from '@/utils/var'
  44. import { useNodesMetaData } from '.'
  45. export const useIsChatMode = () => {
  46. const appDetail = useAppStore(s => s.appDetail)
  47. return appDetail?.mode === 'advanced-chat'
  48. }
  49. export const useWorkflow = () => {
  50. const store = useStoreApi()
  51. const { getAvailableBlocks } = useAvailableBlocks()
  52. const { nodesMap } = useNodesMetaData()
  53. const getNodeById = useCallback((nodeId: string) => {
  54. const {
  55. getNodes,
  56. } = store.getState()
  57. const nodes = getNodes()
  58. const currentNode = nodes.find(node => node.id === nodeId)
  59. return currentNode
  60. }, [store])
  61. const getTreeLeafNodes = useCallback((nodeId: string) => {
  62. const {
  63. getNodes,
  64. edges,
  65. } = store.getState()
  66. const nodes = getNodes()
  67. const currentNode = nodes.find(node => node.id === nodeId)
  68. let startNodes = nodes.filter(node => nodesMap?.[node.data.type as BlockEnum]?.metaData.isStart) || []
  69. if (currentNode?.parentId) {
  70. const startNode = nodes.find(node => node.parentId === currentNode.parentId && (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_LOOP_START_NODE))
  71. if (startNode)
  72. startNodes = [startNode]
  73. }
  74. if (!startNodes.length)
  75. return []
  76. const list: Node[] = []
  77. const preOrder = (root: Node, callback: (node: Node) => void) => {
  78. if (root.id === nodeId)
  79. return
  80. const outgoers = getOutgoers(root, nodes, edges)
  81. if (outgoers.length) {
  82. outgoers.forEach((outgoer) => {
  83. preOrder(outgoer, callback)
  84. })
  85. }
  86. else {
  87. if (root.id !== nodeId)
  88. callback(root)
  89. }
  90. }
  91. startNodes.forEach((startNode) => {
  92. preOrder(startNode, (node) => {
  93. list.push(node)
  94. })
  95. })
  96. const incomers = getIncomers({ id: nodeId } as Node, nodes, edges)
  97. list.push(...incomers)
  98. return uniqBy(list, 'id').filter((item: Node) => {
  99. return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type)
  100. })
  101. }, [store, nodesMap])
  102. const getBeforeNodesInSameBranch = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
  103. const {
  104. getNodes,
  105. edges,
  106. } = store.getState()
  107. const nodes = newNodes || getNodes()
  108. const currentNode = nodes.find(node => node.id === nodeId)
  109. const list: Node[] = []
  110. if (!currentNode)
  111. return list
  112. if (currentNode.parentId) {
  113. const parentNode = nodes.find(node => node.id === currentNode.parentId)
  114. if (parentNode) {
  115. const parentList = getBeforeNodesInSameBranch(parentNode.id)
  116. list.push(...parentList)
  117. }
  118. }
  119. const traverse = (root: Node, callback: (node: Node) => void) => {
  120. if (root) {
  121. const incomers = getIncomers(root, nodes, newEdges || edges)
  122. if (incomers.length) {
  123. incomers.forEach((node) => {
  124. if (!list.find(n => node.id === n.id)) {
  125. callback(node)
  126. traverse(node, callback)
  127. }
  128. })
  129. }
  130. }
  131. }
  132. traverse(currentNode, (node) => {
  133. list.push(node)
  134. })
  135. const length = list.length
  136. if (length) {
  137. return uniqBy(list, 'id').reverse().filter((item: Node) => {
  138. return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type)
  139. })
  140. }
  141. return []
  142. }, [store])
  143. const getBeforeNodesInSameBranchIncludeParent = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
  144. const nodes = getBeforeNodesInSameBranch(nodeId, newNodes, newEdges)
  145. const {
  146. getNodes,
  147. } = store.getState()
  148. const allNodes = getNodes()
  149. const node = allNodes.find(n => n.id === nodeId)
  150. const parentNodeId = node?.parentId
  151. const parentNode = allNodes.find(n => n.id === parentNodeId)
  152. if (parentNode)
  153. nodes.push(parentNode)
  154. return nodes
  155. }, [getBeforeNodesInSameBranch, store])
  156. const getAfterNodesInSameBranch = useCallback((nodeId: string) => {
  157. const {
  158. getNodes,
  159. edges,
  160. } = store.getState()
  161. const nodes = getNodes()
  162. const currentNode = nodes.find(node => node.id === nodeId)!
  163. if (!currentNode)
  164. return []
  165. const list: Node[] = [currentNode]
  166. const traverse = (root: Node, callback: (node: Node) => void) => {
  167. if (root) {
  168. const outgoers = getOutgoers(root, nodes, edges)
  169. if (outgoers.length) {
  170. outgoers.forEach((node) => {
  171. callback(node)
  172. traverse(node, callback)
  173. })
  174. }
  175. }
  176. }
  177. traverse(currentNode, (node) => {
  178. list.push(node)
  179. })
  180. return uniqBy(list, 'id')
  181. }, [store])
  182. const getBeforeNodeById = useCallback((nodeId: string) => {
  183. const {
  184. getNodes,
  185. edges,
  186. } = store.getState()
  187. const nodes = getNodes()
  188. const node = nodes.find(node => node.id === nodeId)!
  189. return getIncomers(node, nodes, edges)
  190. }, [store])
  191. const getIterationNodeChildren = useCallback((nodeId: string) => {
  192. const {
  193. getNodes,
  194. } = store.getState()
  195. const nodes = getNodes()
  196. return nodes.filter(node => node.parentId === nodeId)
  197. }, [store])
  198. const getLoopNodeChildren = useCallback((nodeId: string) => {
  199. const {
  200. getNodes,
  201. } = store.getState()
  202. const nodes = getNodes()
  203. return nodes.filter(node => node.parentId === nodeId)
  204. }, [store])
  205. const handleOutVarRenameChange = useCallback((nodeId: string, oldValeSelector: ValueSelector, newVarSelector: ValueSelector) => {
  206. const { getNodes, setNodes } = store.getState()
  207. const allNodes = getNodes()
  208. const affectedNodes = findUsedVarNodes(oldValeSelector, allNodes)
  209. if (affectedNodes.length > 0) {
  210. const newNodes = allNodes.map((node) => {
  211. if (affectedNodes.find(n => n.id === node.id))
  212. return updateNodeVars(node, oldValeSelector, newVarSelector)
  213. return node
  214. })
  215. setNodes(newNodes)
  216. }
  217. }, [store])
  218. const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => {
  219. const nodeId = varSelector[0]
  220. const afterNodes = getAfterNodesInSameBranch(nodeId)
  221. const effectNodes = findUsedVarNodes(varSelector, afterNodes)
  222. return effectNodes.length > 0
  223. }, [getAfterNodesInSameBranch])
  224. const removeUsedVarInNodes = useCallback((varSelector: ValueSelector) => {
  225. const nodeId = varSelector[0]
  226. const { getNodes, setNodes } = store.getState()
  227. const afterNodes = getAfterNodesInSameBranch(nodeId)
  228. const effectNodes = findUsedVarNodes(varSelector, afterNodes)
  229. if (effectNodes.length > 0) {
  230. const newNodes = getNodes().map((node) => {
  231. if (effectNodes.find(n => n.id === node.id))
  232. return updateNodeVars(node, varSelector, [])
  233. return node
  234. })
  235. setNodes(newNodes)
  236. }
  237. }, [getAfterNodesInSameBranch, store])
  238. const isNodeVarsUsedInNodes = useCallback((node: Node, isChatMode: boolean) => {
  239. const outputVars = getNodeOutputVars(node, isChatMode)
  240. const isUsed = outputVars.some((varSelector) => {
  241. return isVarUsedInNodes(varSelector)
  242. })
  243. return isUsed
  244. }, [isVarUsedInNodes])
  245. const getRootNodesById = useCallback((nodeId: string) => {
  246. const {
  247. getNodes,
  248. edges,
  249. } = store.getState()
  250. const nodes = getNodes()
  251. const currentNode = nodes.find(node => node.id === nodeId)
  252. const rootNodes: Node[] = []
  253. if (!currentNode)
  254. return rootNodes
  255. if (currentNode.parentId) {
  256. const parentNode = nodes.find(node => node.id === currentNode.parentId)
  257. if (parentNode) {
  258. const parentList = getRootNodesById(parentNode.id)
  259. rootNodes.push(...parentList)
  260. }
  261. }
  262. const traverse = (root: Node, callback: (node: Node) => void) => {
  263. if (root) {
  264. const incomers = getIncomers(root, nodes, edges)
  265. if (incomers.length) {
  266. incomers.forEach((node) => {
  267. traverse(node, callback)
  268. })
  269. }
  270. else {
  271. callback(root)
  272. }
  273. }
  274. }
  275. traverse(currentNode, (node) => {
  276. rootNodes.push(node)
  277. })
  278. const length = rootNodes.length
  279. if (length)
  280. return uniqBy(rootNodes, 'id')
  281. return []
  282. }, [store])
  283. const getStartNodes = useCallback((nodes: Node[], currentNode?: Node) => {
  284. const { id, parentId } = currentNode || {}
  285. let startNodes: Node[] = []
  286. if (parentId) {
  287. const parentNode = nodes.find(node => node.id === parentId)
  288. if (!parentNode)
  289. throw new Error('Parent node not found')
  290. const startNode = nodes.find(node => node.id === (parentNode.data as (IterationNodeType | LoopNodeType)).start_node_id)
  291. if (startNode)
  292. startNodes = [startNode]
  293. }
  294. else {
  295. startNodes = nodes.filter(node => nodesMap?.[node.data.type as BlockEnum]?.metaData.isStart) || []
  296. }
  297. if (!startNodes.length)
  298. startNodes = getRootNodesById(id || '')
  299. return startNodes
  300. }, [nodesMap, getRootNodesById])
  301. const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
  302. const {
  303. edges,
  304. getNodes,
  305. } = store.getState()
  306. const nodes = getNodes()
  307. const sourceNode: Node = nodes.find(node => node.id === source)!
  308. const targetNode: Node = nodes.find(node => node.id === target)!
  309. if (sourceNode.type === CUSTOM_NOTE_NODE || targetNode.type === CUSTOM_NOTE_NODE)
  310. return false
  311. if (sourceNode.parentId !== targetNode.parentId)
  312. return false
  313. if (sourceNode && targetNode) {
  314. const sourceNodeAvailableNextNodes = getAvailableBlocks(sourceNode.data.type, !!sourceNode.parentId).availableNextBlocks
  315. const targetNodeAvailablePrevNodes = getAvailableBlocks(targetNode.data.type, !!targetNode.parentId).availablePrevBlocks
  316. if (!sourceNodeAvailableNextNodes.includes(targetNode.data.type))
  317. return false
  318. if (!targetNodeAvailablePrevNodes.includes(sourceNode.data.type))
  319. return false
  320. }
  321. const hasCycle = (node: Node, visited = new Set()) => {
  322. if (visited.has(node.id))
  323. return false
  324. visited.add(node.id)
  325. for (const outgoer of getOutgoers(node, nodes, edges)) {
  326. if (outgoer.id === source)
  327. return true
  328. if (hasCycle(outgoer, visited))
  329. return true
  330. }
  331. }
  332. return !hasCycle(targetNode)
  333. }, [store, getAvailableBlocks])
  334. return {
  335. getNodeById,
  336. getTreeLeafNodes,
  337. getBeforeNodesInSameBranch,
  338. getBeforeNodesInSameBranchIncludeParent,
  339. getAfterNodesInSameBranch,
  340. handleOutVarRenameChange,
  341. isVarUsedInNodes,
  342. removeUsedVarInNodes,
  343. isNodeVarsUsedInNodes,
  344. isValidConnection,
  345. getBeforeNodeById,
  346. getIterationNodeChildren,
  347. getLoopNodeChildren,
  348. getRootNodesById,
  349. getStartNodes,
  350. }
  351. }
  352. export const useFetchToolsData = () => {
  353. const workflowStore = useWorkflowStore()
  354. const handleFetchAllTools = useCallback(async (type: string) => {
  355. if (type === 'builtin') {
  356. const buildInTools = await fetchAllBuiltInTools()
  357. if (basePath) {
  358. buildInTools.forEach((item) => {
  359. if (typeof item.icon == 'string' && !item.icon.includes(basePath))
  360. item.icon = `${basePath}${item.icon}`
  361. })
  362. }
  363. workflowStore.setState({
  364. buildInTools: buildInTools || [],
  365. })
  366. }
  367. if (type === 'custom') {
  368. const customTools = await fetchAllCustomTools()
  369. workflowStore.setState({
  370. customTools: customTools || [],
  371. })
  372. }
  373. if (type === 'workflow') {
  374. const workflowTools = await fetchAllWorkflowTools()
  375. workflowStore.setState({
  376. workflowTools: workflowTools || [],
  377. })
  378. }
  379. if (type === 'mcp') {
  380. const mcpTools = await fetchAllMCPTools()
  381. workflowStore.setState({
  382. mcpTools: mcpTools || [],
  383. })
  384. }
  385. }, [workflowStore])
  386. return {
  387. handleFetchAllTools,
  388. }
  389. }
  390. export const useWorkflowReadOnly = () => {
  391. const workflowStore = useWorkflowStore()
  392. const workflowRunningData = useStore(s => s.workflowRunningData)
  393. const getWorkflowReadOnly = useCallback(() => {
  394. return workflowStore.getState().workflowRunningData?.result.status === WorkflowRunningStatus.Running
  395. }, [workflowStore])
  396. return {
  397. workflowReadOnly: workflowRunningData?.result.status === WorkflowRunningStatus.Running,
  398. getWorkflowReadOnly,
  399. }
  400. }
  401. export const useNodesReadOnly = () => {
  402. const workflowStore = useWorkflowStore()
  403. const workflowRunningData = useStore(s => s.workflowRunningData)
  404. const historyWorkflowData = useStore(s => s.historyWorkflowData)
  405. const isRestoring = useStore(s => s.isRestoring)
  406. const getNodesReadOnly = useCallback(() => {
  407. const {
  408. workflowRunningData,
  409. historyWorkflowData,
  410. isRestoring,
  411. } = workflowStore.getState()
  412. return workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring
  413. }, [workflowStore])
  414. return {
  415. nodesReadOnly: !!(workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring),
  416. getNodesReadOnly,
  417. }
  418. }
  419. export const useIsNodeInIteration = (iterationId: string) => {
  420. const store = useStoreApi()
  421. const isNodeInIteration = useCallback((nodeId: string) => {
  422. const {
  423. getNodes,
  424. } = store.getState()
  425. const nodes = getNodes()
  426. const node = nodes.find(node => node.id === nodeId)
  427. if (!node)
  428. return false
  429. if (node.parentId === iterationId)
  430. return true
  431. return false
  432. }, [iterationId, store])
  433. return {
  434. isNodeInIteration,
  435. }
  436. }
  437. export const useIsNodeInLoop = (loopId: string) => {
  438. const store = useStoreApi()
  439. const isNodeInLoop = useCallback((nodeId: string) => {
  440. const {
  441. getNodes,
  442. } = store.getState()
  443. const nodes = getNodes()
  444. const node = nodes.find(node => node.id === nodeId)
  445. if (!node)
  446. return false
  447. if (node.parentId === loopId)
  448. return true
  449. return false
  450. }, [loopId, store])
  451. return {
  452. isNodeInLoop,
  453. }
  454. }