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

use-workflow.ts 17KB

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