Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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