You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

use-workflow-run.ts 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import { useCallback } from 'react'
  2. import {
  3. useReactFlow,
  4. useStoreApi,
  5. } from 'reactflow'
  6. import produce from 'immer'
  7. import { v4 as uuidV4 } from 'uuid'
  8. import { usePathname } from 'next/navigation'
  9. import { useWorkflowStore } from '@/app/components/workflow/store'
  10. import { WorkflowRunningStatus } from '@/app/components/workflow/types'
  11. import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-interactions'
  12. import { useWorkflowRunEvent } from '@/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event'
  13. import { useStore as useAppStore } from '@/app/components/app/store'
  14. import type { IOtherOptions } from '@/service/base'
  15. import { ssePost } from '@/service/base'
  16. import { stopWorkflowRun } from '@/service/workflow'
  17. import { useFeaturesStore } from '@/app/components/base/features/hooks'
  18. import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
  19. import type { VersionHistory } from '@/types/workflow'
  20. import { noop } from 'lodash-es'
  21. import { useNodesSyncDraft } from './use-nodes-sync-draft'
  22. import { useInvalidAllLastRun } from '@/service/use-workflow'
  23. import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars'
  24. import { useConfigsMap } from './use-configs-map'
  25. export const useWorkflowRun = () => {
  26. const store = useStoreApi()
  27. const workflowStore = useWorkflowStore()
  28. const reactflow = useReactFlow()
  29. const featuresStore = useFeaturesStore()
  30. const { doSyncWorkflowDraft } = useNodesSyncDraft()
  31. const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
  32. const pathname = usePathname()
  33. const appId = useAppStore.getState().appDetail?.id
  34. const invalidAllLastRun = useInvalidAllLastRun(appId as string)
  35. const configsMap = useConfigsMap()
  36. const { fetchInspectVars } = useSetWorkflowVarsWithValue({
  37. ...configsMap,
  38. })
  39. const {
  40. handleWorkflowStarted,
  41. handleWorkflowFinished,
  42. handleWorkflowFailed,
  43. handleWorkflowNodeStarted,
  44. handleWorkflowNodeFinished,
  45. handleWorkflowNodeIterationStarted,
  46. handleWorkflowNodeIterationNext,
  47. handleWorkflowNodeIterationFinished,
  48. handleWorkflowNodeLoopStarted,
  49. handleWorkflowNodeLoopNext,
  50. handleWorkflowNodeLoopFinished,
  51. handleWorkflowNodeRetry,
  52. handleWorkflowAgentLog,
  53. handleWorkflowTextChunk,
  54. handleWorkflowTextReplace,
  55. } = useWorkflowRunEvent()
  56. const handleBackupDraft = useCallback(() => {
  57. const {
  58. getNodes,
  59. edges,
  60. } = store.getState()
  61. const { getViewport } = reactflow
  62. const {
  63. backupDraft,
  64. setBackupDraft,
  65. environmentVariables,
  66. } = workflowStore.getState()
  67. const { features } = featuresStore!.getState()
  68. if (!backupDraft) {
  69. setBackupDraft({
  70. nodes: getNodes(),
  71. edges,
  72. viewport: getViewport(),
  73. features,
  74. environmentVariables,
  75. })
  76. doSyncWorkflowDraft()
  77. }
  78. }, [reactflow, workflowStore, store, featuresStore, doSyncWorkflowDraft])
  79. const handleLoadBackupDraft = useCallback(() => {
  80. const {
  81. backupDraft,
  82. setBackupDraft,
  83. setEnvironmentVariables,
  84. } = workflowStore.getState()
  85. if (backupDraft) {
  86. const {
  87. nodes,
  88. edges,
  89. viewport,
  90. features,
  91. environmentVariables,
  92. } = backupDraft
  93. handleUpdateWorkflowCanvas({
  94. nodes,
  95. edges,
  96. viewport,
  97. })
  98. setEnvironmentVariables(environmentVariables)
  99. featuresStore!.setState({ features })
  100. setBackupDraft(undefined)
  101. }
  102. }, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
  103. const handleRun = useCallback(async (
  104. params: any,
  105. callback?: IOtherOptions,
  106. ) => {
  107. const {
  108. getNodes,
  109. setNodes,
  110. } = store.getState()
  111. const newNodes = produce(getNodes(), (draft) => {
  112. draft.forEach((node) => {
  113. node.data.selected = false
  114. node.data._runningStatus = undefined
  115. })
  116. })
  117. setNodes(newNodes)
  118. await doSyncWorkflowDraft()
  119. const {
  120. onWorkflowStarted,
  121. onWorkflowFinished,
  122. onNodeStarted,
  123. onNodeFinished,
  124. onIterationStart,
  125. onIterationNext,
  126. onIterationFinish,
  127. onLoopStart,
  128. onLoopNext,
  129. onLoopFinish,
  130. onNodeRetry,
  131. onAgentLog,
  132. onError,
  133. ...restCallback
  134. } = callback || {}
  135. workflowStore.setState({ historyWorkflowData: undefined })
  136. const appDetail = useAppStore.getState().appDetail
  137. const workflowContainer = document.getElementById('workflow-container')
  138. const {
  139. clientWidth,
  140. clientHeight,
  141. } = workflowContainer!
  142. const isInWorkflowDebug = appDetail?.mode === 'workflow'
  143. let url = ''
  144. if (appDetail?.mode === 'advanced-chat')
  145. url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
  146. if (isInWorkflowDebug)
  147. url = `/apps/${appDetail.id}/workflows/draft/run`
  148. const {
  149. setWorkflowRunningData,
  150. } = workflowStore.getState()
  151. setWorkflowRunningData({
  152. result: {
  153. status: WorkflowRunningStatus.Running,
  154. },
  155. tracing: [],
  156. resultText: '',
  157. })
  158. let ttsUrl = ''
  159. let ttsIsPublic = false
  160. if (params.token) {
  161. ttsUrl = '/text-to-audio'
  162. ttsIsPublic = true
  163. }
  164. else if (params.appId) {
  165. if (pathname.search('explore/installed') > -1)
  166. ttsUrl = `/installed-apps/${params.appId}/text-to-audio`
  167. else
  168. ttsUrl = `/apps/${params.appId}/text-to-audio`
  169. }
  170. const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', noop)
  171. ssePost(
  172. url,
  173. {
  174. body: params,
  175. },
  176. {
  177. onWorkflowStarted: (params) => {
  178. handleWorkflowStarted(params)
  179. if (onWorkflowStarted)
  180. onWorkflowStarted(params)
  181. },
  182. onWorkflowFinished: (params) => {
  183. handleWorkflowFinished(params)
  184. if (onWorkflowFinished)
  185. onWorkflowFinished(params)
  186. if (isInWorkflowDebug) {
  187. fetchInspectVars()
  188. invalidAllLastRun()
  189. }
  190. },
  191. onError: (params) => {
  192. handleWorkflowFailed()
  193. if (onError)
  194. onError(params)
  195. },
  196. onNodeStarted: (params) => {
  197. handleWorkflowNodeStarted(
  198. params,
  199. {
  200. clientWidth,
  201. clientHeight,
  202. },
  203. )
  204. if (onNodeStarted)
  205. onNodeStarted(params)
  206. },
  207. onNodeFinished: (params) => {
  208. handleWorkflowNodeFinished(params)
  209. if (onNodeFinished)
  210. onNodeFinished(params)
  211. },
  212. onIterationStart: (params) => {
  213. handleWorkflowNodeIterationStarted(
  214. params,
  215. {
  216. clientWidth,
  217. clientHeight,
  218. },
  219. )
  220. if (onIterationStart)
  221. onIterationStart(params)
  222. },
  223. onIterationNext: (params) => {
  224. handleWorkflowNodeIterationNext(params)
  225. if (onIterationNext)
  226. onIterationNext(params)
  227. },
  228. onIterationFinish: (params) => {
  229. handleWorkflowNodeIterationFinished(params)
  230. if (onIterationFinish)
  231. onIterationFinish(params)
  232. },
  233. onLoopStart: (params) => {
  234. handleWorkflowNodeLoopStarted(
  235. params,
  236. {
  237. clientWidth,
  238. clientHeight,
  239. },
  240. )
  241. if (onLoopStart)
  242. onLoopStart(params)
  243. },
  244. onLoopNext: (params) => {
  245. handleWorkflowNodeLoopNext(params)
  246. if (onLoopNext)
  247. onLoopNext(params)
  248. },
  249. onLoopFinish: (params) => {
  250. handleWorkflowNodeLoopFinished(params)
  251. if (onLoopFinish)
  252. onLoopFinish(params)
  253. },
  254. onNodeRetry: (params) => {
  255. handleWorkflowNodeRetry(params)
  256. if (onNodeRetry)
  257. onNodeRetry(params)
  258. },
  259. onAgentLog: (params) => {
  260. handleWorkflowAgentLog(params)
  261. if (onAgentLog)
  262. onAgentLog(params)
  263. },
  264. onTextChunk: (params) => {
  265. handleWorkflowTextChunk(params)
  266. },
  267. onTextReplace: (params) => {
  268. handleWorkflowTextReplace(params)
  269. },
  270. onTTSChunk: (messageId: string, audio: string) => {
  271. if (!audio || audio === '')
  272. return
  273. player.playAudioWithAudio(audio, true)
  274. AudioPlayerManager.getInstance().resetMsgId(messageId)
  275. },
  276. onTTSEnd: (messageId: string, audio: string) => {
  277. player.playAudioWithAudio(audio, false)
  278. },
  279. ...restCallback,
  280. },
  281. )
  282. }, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace],
  283. )
  284. const handleStopRun = useCallback((taskId: string) => {
  285. const appId = useAppStore.getState().appDetail?.id
  286. stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
  287. }, [])
  288. const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
  289. const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } }))
  290. const edges = publishedWorkflow.graph.edges
  291. const viewport = publishedWorkflow.graph.viewport!
  292. handleUpdateWorkflowCanvas({
  293. nodes,
  294. edges,
  295. viewport,
  296. })
  297. const mappedFeatures = {
  298. opening: {
  299. enabled: !!publishedWorkflow.features.opening_statement || !!publishedWorkflow.features.suggested_questions.length,
  300. opening_statement: publishedWorkflow.features.opening_statement,
  301. suggested_questions: publishedWorkflow.features.suggested_questions,
  302. },
  303. suggested: publishedWorkflow.features.suggested_questions_after_answer,
  304. text2speech: publishedWorkflow.features.text_to_speech,
  305. speech2text: publishedWorkflow.features.speech_to_text,
  306. citation: publishedWorkflow.features.retriever_resource,
  307. moderation: publishedWorkflow.features.sensitive_word_avoidance,
  308. file: publishedWorkflow.features.file_upload,
  309. }
  310. featuresStore?.setState({ features: mappedFeatures })
  311. workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
  312. }, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
  313. return {
  314. handleBackupDraft,
  315. handleLoadBackupDraft,
  316. handleRun,
  317. handleStopRun,
  318. handleRestoreFromPublishedWorkflow,
  319. }
  320. }