use-one-step-run.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. import { useCallback, useEffect, useRef, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { unionBy } from 'lodash-es'
  4. import produce from 'immer'
  5. import {
  6. useIsChatMode,
  7. useNodeDataUpdate,
  8. useWorkflow,
  9. } from '@/app/components/workflow/hooks'
  10. import { getNodeInfoById, isConversationVar, isENV, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
  11. import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
  12. import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
  13. import { useStore as useAppStore } from '@/app/components/app/store'
  14. import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
  15. import { getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
  16. import Toast from '@/app/components/base/toast'
  17. import LLMDefault from '@/app/components/workflow/nodes/llm/default'
  18. import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
  19. import IfElseDefault from '@/app/components/workflow/nodes/if-else/default'
  20. import CodeDefault from '@/app/components/workflow/nodes/code/default'
  21. import TemplateTransformDefault from '@/app/components/workflow/nodes/template-transform/default'
  22. import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-classifier/default'
  23. import HTTPDefault from '@/app/components/workflow/nodes/http/default'
  24. import IntentReconTrainDefault from '@/app/components/workflow/nodes/intent-recon-train/default'
  25. import ToolDefault from '@/app/components/workflow/nodes/tool/default'
  26. import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default'
  27. import Assigner from '@/app/components/workflow/nodes/assigner/default'
  28. import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
  29. import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
  30. import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
  31. import LoopDefault from '@/app/components/workflow/nodes/loop/default'
  32. import { ssePost } from '@/service/base'
  33. import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
  34. import type { NodeTracing } from '@/types/workflow'
  35. const { checkValid: checkLLMValid } = LLMDefault
  36. const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
  37. const { checkValid: checkIfElseValid } = IfElseDefault
  38. const { checkValid: checkCodeValid } = CodeDefault
  39. const { checkValid: checkTemplateTransformValid } = TemplateTransformDefault
  40. const { checkValid: checkQuestionClassifyValid } = QuestionClassifyDefault
  41. const { checkValid: checkHttpValid } = HTTPDefault
  42. const { checkValid: checkIntentReconTrainValid } = IntentReconTrainDefault
  43. const { checkValid: checkToolValid } = ToolDefault
  44. const { checkValid: checkVariableAssignerValid } = VariableAssigner
  45. const { checkValid: checkAssignerValid } = Assigner
  46. const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
  47. const { checkValid: checkIterationValid } = IterationDefault
  48. const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
  49. const { checkValid: checkLoopValid } = LoopDefault
  50. // eslint-disable-next-line ts/no-unsafe-function-type
  51. const checkValidFns: Record<BlockEnum, Function> = {
  52. [BlockEnum.LLM]: checkLLMValid,
  53. [BlockEnum.KnowledgeRetrieval]: checkKnowledgeRetrievalValid,
  54. [BlockEnum.IfElse]: checkIfElseValid,
  55. [BlockEnum.Code]: checkCodeValid,
  56. [BlockEnum.TemplateTransform]: checkTemplateTransformValid,
  57. [BlockEnum.QuestionClassifier]: checkQuestionClassifyValid,
  58. [BlockEnum.HttpRequest]: checkHttpValid,
  59. [BlockEnum.IntentReconTrain]: checkIntentReconTrainValid,
  60. [BlockEnum.Tool]: checkToolValid,
  61. [BlockEnum.VariableAssigner]: checkAssignerValid,
  62. [BlockEnum.VariableAggregator]: checkVariableAssignerValid,
  63. [BlockEnum.ParameterExtractor]: checkParameterExtractorValid,
  64. [BlockEnum.Iteration]: checkIterationValid,
  65. [BlockEnum.DocExtractor]: checkDocumentExtractorValid,
  66. [BlockEnum.Loop]: checkLoopValid,
  67. } as any
  68. type Params<T> = {
  69. id: string
  70. data: CommonNodeType<T>
  71. defaultRunInputData: Record<string, any>
  72. moreDataForCheckValid?: any
  73. iteratorInputKey?: string
  74. loopInputKey?: string
  75. }
  76. const varTypeToInputVarType = (type: VarType, {
  77. isSelect,
  78. isParagraph,
  79. }: {
  80. isSelect: boolean
  81. isParagraph: boolean
  82. }) => {
  83. if (isSelect)
  84. return InputVarType.select
  85. if (isParagraph)
  86. return InputVarType.paragraph
  87. if (type === VarType.number)
  88. return InputVarType.number
  89. if ([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(type))
  90. return InputVarType.json
  91. if (type === VarType.file)
  92. return InputVarType.singleFile
  93. if (type === VarType.arrayFile)
  94. return InputVarType.multiFiles
  95. return InputVarType.textInput
  96. }
  97. const useOneStepRun = <T>({
  98. id,
  99. data,
  100. defaultRunInputData,
  101. moreDataForCheckValid,
  102. iteratorInputKey,
  103. loopInputKey,
  104. }: Params<T>) => {
  105. const { t } = useTranslation()
  106. const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
  107. const conversationVariables = useStore(s => s.conversationVariables)
  108. const isChatMode = useIsChatMode()
  109. const isIteration = data.type === BlockEnum.Iteration
  110. const isLoop = data.type === BlockEnum.Loop
  111. const availableNodes = getBeforeNodesInSameBranch(id)
  112. const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
  113. const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables)
  114. const getVar = (valueSelector: ValueSelector): Var | undefined => {
  115. const isSystem = valueSelector[0] === 'sys'
  116. const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
  117. if (!targetVar)
  118. return undefined
  119. if (isSystem)
  120. return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
  121. let curr: any = targetVar.vars
  122. for (let i = 1; i < valueSelector.length; i++) {
  123. const key = valueSelector[i]
  124. const isLast = i === valueSelector.length - 1
  125. if (Array.isArray(curr))
  126. curr = curr.find((v: any) => v.variable.replace('conversation.', '') === key)
  127. if (isLast)
  128. return curr
  129. else if (curr?.type === VarType.object || curr?.type === VarType.file)
  130. curr = curr.children
  131. }
  132. return undefined
  133. }
  134. const checkValid = checkValidFns[data.type]
  135. const appId = useAppStore.getState().appDetail?.id
  136. const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
  137. const runInputDataRef = useRef(runInputData)
  138. const handleSetRunInputData = useCallback((data: Record<string, any>) => {
  139. runInputDataRef.current = data
  140. setRunInputData(data)
  141. }, [])
  142. const iterationTimes = iteratorInputKey ? runInputData[iteratorInputKey].length : 0
  143. const loopTimes = loopInputKey ? runInputData[loopInputKey].length : 0
  144. const [runResult, setRunResult] = useState<any>(null)
  145. const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
  146. const [canShowSingleRun, setCanShowSingleRun] = useState(false)
  147. const isShowSingleRun = data._isSingleRun && canShowSingleRun
  148. const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[]>([])
  149. const [loopRunResult, setLoopRunResult] = useState<NodeTracing[]>([])
  150. useEffect(() => {
  151. if (!checkValid) {
  152. setCanShowSingleRun(true)
  153. return
  154. }
  155. if (data._isSingleRun) {
  156. const { isValid, errorMessage } = checkValid(data, t, moreDataForCheckValid)
  157. setCanShowSingleRun(isValid)
  158. if (!isValid) {
  159. handleNodeDataUpdate({
  160. id,
  161. data: {
  162. ...data,
  163. _isSingleRun: false,
  164. },
  165. })
  166. Toast.notify({
  167. type: 'error',
  168. message: errorMessage,
  169. })
  170. }
  171. }
  172. // eslint-disable-next-line react-hooks/exhaustive-deps
  173. }, [data._isSingleRun])
  174. const workflowStore = useWorkflowStore()
  175. useEffect(() => {
  176. workflowStore.getState().setShowSingleRunPanel(!!isShowSingleRun)
  177. }, [isShowSingleRun, workflowStore])
  178. const hideSingleRun = () => {
  179. handleNodeDataUpdate({
  180. id,
  181. data: {
  182. ...data,
  183. _isSingleRun: false,
  184. },
  185. })
  186. }
  187. const showSingleRun = () => {
  188. handleNodeDataUpdate({
  189. id,
  190. data: {
  191. ...data,
  192. _isSingleRun: true,
  193. },
  194. })
  195. }
  196. const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
  197. const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
  198. const handleRun = async (submitData: Record<string, any>) => {
  199. handleNodeDataUpdate({
  200. id,
  201. data: {
  202. ...data,
  203. _singleRunningStatus: NodeRunningStatus.Running,
  204. },
  205. })
  206. let res: any
  207. try {
  208. if (!isIteration && !isLoop) {
  209. res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
  210. }
  211. else if (isIteration) {
  212. setIterationRunResult([])
  213. let _iterationResult: NodeTracing[] = []
  214. let _runResult: any = null
  215. ssePost(
  216. getIterationSingleNodeRunUrl(isChatMode, appId!, id),
  217. { body: { inputs: submitData } },
  218. {
  219. onWorkflowStarted: () => {
  220. },
  221. onWorkflowFinished: (params) => {
  222. handleNodeDataUpdate({
  223. id,
  224. data: {
  225. ...data,
  226. _singleRunningStatus: NodeRunningStatus.Succeeded,
  227. },
  228. })
  229. const { data: iterationData } = params
  230. _runResult.created_by = iterationData.created_by.name
  231. setRunResult(_runResult)
  232. },
  233. onIterationStart: (params) => {
  234. const newIterationRunResult = produce(_iterationResult, (draft) => {
  235. draft.push({
  236. ...params.data,
  237. status: NodeRunningStatus.Running,
  238. })
  239. })
  240. _iterationResult = newIterationRunResult
  241. setIterationRunResult(newIterationRunResult)
  242. },
  243. onIterationNext: () => {
  244. // iteration next trigger time is triggered one more time than iterationTimes
  245. if (_iterationResult.length >= iterationTimes!)
  246. return _iterationResult.length >= iterationTimes!
  247. },
  248. onIterationFinish: (params) => {
  249. _runResult = params.data
  250. setRunResult(_runResult)
  251. const iterationRunResult = _iterationResult
  252. const currentIndex = iterationRunResult.findIndex(trace => trace.id === params.data.id)
  253. const newIterationRunResult = produce(iterationRunResult, (draft) => {
  254. if (currentIndex > -1) {
  255. draft[currentIndex] = {
  256. ...draft[currentIndex],
  257. ...data,
  258. }
  259. }
  260. })
  261. _iterationResult = newIterationRunResult
  262. setIterationRunResult(newIterationRunResult)
  263. },
  264. onNodeStarted: (params) => {
  265. const newIterationRunResult = produce(_iterationResult, (draft) => {
  266. draft.push({
  267. ...params.data,
  268. status: NodeRunningStatus.Running,
  269. })
  270. })
  271. _iterationResult = newIterationRunResult
  272. setIterationRunResult(newIterationRunResult)
  273. },
  274. onNodeFinished: (params) => {
  275. const iterationRunResult = _iterationResult
  276. const { data } = params
  277. const currentIndex = iterationRunResult.findIndex(trace => trace.id === data.id)
  278. const newIterationRunResult = produce(iterationRunResult, (draft) => {
  279. if (currentIndex > -1) {
  280. draft[currentIndex] = {
  281. ...draft[currentIndex],
  282. ...data,
  283. }
  284. }
  285. })
  286. _iterationResult = newIterationRunResult
  287. setIterationRunResult(newIterationRunResult)
  288. },
  289. onNodeRetry: (params) => {
  290. const newIterationRunResult = produce(_iterationResult, (draft) => {
  291. draft.push(params.data)
  292. })
  293. _iterationResult = newIterationRunResult
  294. setIterationRunResult(newIterationRunResult)
  295. },
  296. onError: () => {
  297. handleNodeDataUpdate({
  298. id,
  299. data: {
  300. ...data,
  301. _singleRunningStatus: NodeRunningStatus.Failed,
  302. },
  303. })
  304. },
  305. },
  306. )
  307. }
  308. else if (isLoop) {
  309. setLoopRunResult([])
  310. let _loopResult: NodeTracing[] = []
  311. let _runResult: any = null
  312. ssePost(
  313. getLoopSingleNodeRunUrl(isChatMode, appId!, id),
  314. { body: { inputs: submitData } },
  315. {
  316. onWorkflowStarted: () => {
  317. },
  318. onWorkflowFinished: (params) => {
  319. handleNodeDataUpdate({
  320. id,
  321. data: {
  322. ...data,
  323. _singleRunningStatus: NodeRunningStatus.Succeeded,
  324. },
  325. })
  326. const { data: loopData } = params
  327. _runResult.created_by = loopData.created_by.name
  328. setRunResult(_runResult)
  329. },
  330. onLoopStart: (params) => {
  331. const newLoopRunResult = produce(_loopResult, (draft) => {
  332. draft.push({
  333. ...params.data,
  334. status: NodeRunningStatus.Running,
  335. })
  336. })
  337. _loopResult = newLoopRunResult
  338. setLoopRunResult(newLoopRunResult)
  339. },
  340. onLoopNext: () => {
  341. // loop next trigger time is triggered one more time than loopTimes
  342. if (_loopResult.length >= loopTimes!)
  343. return _loopResult.length >= loopTimes!
  344. },
  345. onLoopFinish: (params) => {
  346. _runResult = params.data
  347. setRunResult(_runResult)
  348. const loopRunResult = _loopResult
  349. const currentIndex = loopRunResult.findIndex(trace => trace.id === params.data.id)
  350. const newLoopRunResult = produce(loopRunResult, (draft) => {
  351. if (currentIndex > -1) {
  352. draft[currentIndex] = {
  353. ...draft[currentIndex],
  354. ...data,
  355. }
  356. }
  357. })
  358. _loopResult = newLoopRunResult
  359. setLoopRunResult(newLoopRunResult)
  360. },
  361. onNodeStarted: (params) => {
  362. const newLoopRunResult = produce(_loopResult, (draft) => {
  363. draft.push({
  364. ...params.data,
  365. status: NodeRunningStatus.Running,
  366. })
  367. })
  368. _loopResult = newLoopRunResult
  369. setLoopRunResult(newLoopRunResult)
  370. },
  371. onNodeFinished: (params) => {
  372. const loopRunResult = _loopResult
  373. const { data } = params
  374. const currentIndex = loopRunResult.findIndex(trace => trace.id === data.id)
  375. const newLoopRunResult = produce(loopRunResult, (draft) => {
  376. if (currentIndex > -1) {
  377. draft[currentIndex] = {
  378. ...draft[currentIndex],
  379. ...data,
  380. }
  381. }
  382. })
  383. _loopResult = newLoopRunResult
  384. setLoopRunResult(newLoopRunResult)
  385. },
  386. onNodeRetry: (params) => {
  387. const newLoopRunResult = produce(_loopResult, (draft) => {
  388. draft.push(params.data)
  389. })
  390. _loopResult = newLoopRunResult
  391. setLoopRunResult(newLoopRunResult)
  392. },
  393. onError: () => {
  394. handleNodeDataUpdate({
  395. id,
  396. data: {
  397. ...data,
  398. _singleRunningStatus: NodeRunningStatus.Failed,
  399. },
  400. })
  401. },
  402. },
  403. )
  404. }
  405. if (res && res.error)
  406. throw new Error(res.error)
  407. }
  408. catch (e: any) {
  409. console.error(e)
  410. if (!isIteration && !isLoop) {
  411. handleNodeDataUpdate({
  412. id,
  413. data: {
  414. ...data,
  415. _singleRunningStatus: NodeRunningStatus.Failed,
  416. },
  417. })
  418. return false
  419. }
  420. }
  421. finally {
  422. if (!isIteration && !isLoop) {
  423. setRunResult({
  424. ...res,
  425. total_tokens: res.execution_metadata?.total_tokens || 0,
  426. created_by: res.created_by_account?.name || '',
  427. })
  428. }
  429. }
  430. if (!isIteration && !isLoop) {
  431. handleNodeDataUpdate({
  432. id,
  433. data: {
  434. ...data,
  435. _singleRunningStatus: NodeRunningStatus.Succeeded,
  436. },
  437. })
  438. }
  439. }
  440. const handleStop = () => {
  441. handleNodeDataUpdate({
  442. id,
  443. data: {
  444. ...data,
  445. _singleRunningStatus: NodeRunningStatus.NotStart,
  446. },
  447. })
  448. }
  449. const toVarInputs = (variables: Variable[]): InputVar[] => {
  450. if (!variables)
  451. return []
  452. const varInputs = variables.filter(item => !isENV(item.value_selector)).map((item) => {
  453. const originalVar = getVar(item.value_selector)
  454. if (!originalVar) {
  455. return {
  456. label: item.label || item.variable,
  457. variable: item.variable,
  458. type: InputVarType.textInput,
  459. required: true,
  460. value_selector: item.value_selector,
  461. }
  462. }
  463. return {
  464. label: item.label || item.variable,
  465. variable: item.variable,
  466. type: varTypeToInputVarType(originalVar.type, {
  467. isSelect: !!originalVar.isSelect,
  468. isParagraph: !!originalVar.isParagraph,
  469. }),
  470. required: item.required !== false,
  471. options: originalVar.options,
  472. }
  473. })
  474. return varInputs
  475. }
  476. const getInputVars = (textList: string[]) => {
  477. const valueSelectors: ValueSelector[] = []
  478. textList.forEach((text) => {
  479. valueSelectors.push(...doGetInputVars(text))
  480. })
  481. const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
  482. const varInfo = getNodeInfoById(availableNodesIncludeParent, item[0])?.data
  483. return {
  484. label: {
  485. nodeType: varInfo?.type,
  486. nodeName: varInfo?.title || availableNodesIncludeParent[0]?.data.title, // default start node title
  487. variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
  488. isChatVar: isConversationVar(item),
  489. },
  490. variable: `#${item.join('.')}#`,
  491. value_selector: item,
  492. }
  493. })
  494. const varInputs = toVarInputs(variables)
  495. return varInputs
  496. }
  497. return {
  498. isShowSingleRun,
  499. hideSingleRun,
  500. showSingleRun,
  501. toVarInputs,
  502. getInputVars,
  503. runningStatus,
  504. isCompleted,
  505. handleRun,
  506. handleStop,
  507. runInputData,
  508. runInputDataRef,
  509. setRunInputData: handleSetRunInputData,
  510. runResult,
  511. iterationRunResult,
  512. loopRunResult,
  513. }
  514. }
  515. export default useOneStepRun