index.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. 'use client'
  2. import type { FC } from 'react'
  3. import {
  4. memo,
  5. useCallback,
  6. useEffect,
  7. useMemo,
  8. } from 'react'
  9. import { setAutoFreeze } from 'immer'
  10. import {
  11. useKeyPress,
  12. } from 'ahooks'
  13. import ReactFlow, {
  14. Background,
  15. ReactFlowProvider,
  16. useEdgesState,
  17. useNodesState,
  18. useOnViewportChange,
  19. } from 'reactflow'
  20. import type { Viewport } from 'reactflow'
  21. import 'reactflow/dist/style.css'
  22. import './style.css'
  23. import type {
  24. Edge,
  25. Node,
  26. } from './types'
  27. import { WorkflowContextProvider } from './context'
  28. import {
  29. useEdgesInteractions,
  30. useNodesInteractions,
  31. useNodesReadOnly,
  32. useNodesSyncDraft,
  33. useWorkflow,
  34. useWorkflowInit,
  35. useWorkflowReadOnly,
  36. } from './hooks'
  37. import Header from './header'
  38. import CustomNode from './nodes'
  39. import Operator from './operator'
  40. import CustomEdge from './custom-edge'
  41. import CustomConnectionLine from './custom-connection-line'
  42. import Panel from './panel'
  43. import Features from './features'
  44. import HelpLine from './help-line'
  45. import { useStore } from './store'
  46. import {
  47. initialEdges,
  48. initialNodes,
  49. } from './utils'
  50. import { WORKFLOW_DATA_UPDATE } from './constants'
  51. import Loading from '@/app/components/base/loading'
  52. import { FeaturesProvider } from '@/app/components/base/features'
  53. import type { Features as FeaturesData } from '@/app/components/base/features/types'
  54. import { useEventEmitterContextContext } from '@/context/event-emitter'
  55. const nodeTypes = {
  56. custom: CustomNode,
  57. }
  58. const edgeTypes = {
  59. custom: CustomEdge,
  60. }
  61. type WorkflowProps = {
  62. nodes: Node[]
  63. edges: Edge[]
  64. viewport?: Viewport
  65. }
  66. const Workflow: FC<WorkflowProps> = memo(({
  67. nodes: originalNodes,
  68. edges: originalEdges,
  69. viewport,
  70. }) => {
  71. const [nodes, setNodes] = useNodesState(originalNodes)
  72. const [edges, setEdges] = useEdgesState(originalEdges)
  73. const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
  74. const nodeAnimation = useStore(s => s.nodeAnimation)
  75. const {
  76. handleSyncWorkflowDraft,
  77. syncWorkflowDraftWhenPageClose,
  78. } = useNodesSyncDraft()
  79. const { workflowReadOnly } = useWorkflowReadOnly()
  80. const { nodesReadOnly } = useNodesReadOnly()
  81. const { eventEmitter } = useEventEmitterContextContext()
  82. eventEmitter?.useSubscription((v: any) => {
  83. if (v.type === WORKFLOW_DATA_UPDATE) {
  84. setNodes(v.payload.nodes)
  85. setEdges(v.payload.edges)
  86. }
  87. })
  88. useEffect(() => {
  89. setAutoFreeze(false)
  90. return () => {
  91. setAutoFreeze(true)
  92. }
  93. }, [])
  94. useEffect(() => {
  95. return () => {
  96. handleSyncWorkflowDraft(true)
  97. }
  98. }, [])
  99. const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {
  100. if (document.visibilityState === 'hidden')
  101. syncWorkflowDraftWhenPageClose()
  102. }, [syncWorkflowDraftWhenPageClose])
  103. useEffect(() => {
  104. document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
  105. return () => {
  106. document.removeEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
  107. }
  108. }, [handleSyncWorkflowDraftWhenPageClose])
  109. const {
  110. handleNodeDragStart,
  111. handleNodeDrag,
  112. handleNodeDragStop,
  113. handleNodeEnter,
  114. handleNodeLeave,
  115. handleNodeClick,
  116. handleNodeConnect,
  117. handleNodeConnectStart,
  118. handleNodeConnectEnd,
  119. handleNodeDuplicateSelected,
  120. handleNodeCopySelected,
  121. handleNodeCut,
  122. handleNodeDeleteSelected,
  123. handleNodePaste,
  124. } = useNodesInteractions()
  125. const {
  126. handleEdgeEnter,
  127. handleEdgeLeave,
  128. handleEdgeDelete,
  129. handleEdgesChange,
  130. } = useEdgesInteractions()
  131. const {
  132. isValidConnection,
  133. enableShortcuts,
  134. disableShortcuts,
  135. } = useWorkflow()
  136. useOnViewportChange({
  137. onEnd: () => {
  138. handleSyncWorkflowDraft()
  139. },
  140. })
  141. useKeyPress(['delete', 'backspace'], handleNodeDeleteSelected)
  142. useKeyPress(['delete', 'backspace'], handleEdgeDelete)
  143. useKeyPress(['ctrl.c', 'meta.c'], handleNodeCopySelected)
  144. useKeyPress(['ctrl.x', 'meta.x'], handleNodeCut)
  145. useKeyPress(['ctrl.v', 'meta.v'], handleNodePaste)
  146. useKeyPress(['ctrl.alt.d', 'meta.shift.d'], handleNodeDuplicateSelected)
  147. return (
  148. <div
  149. id='workflow-container'
  150. className={`
  151. relative w-full min-w-[960px] h-full bg-[#F0F2F7]
  152. ${workflowReadOnly && 'workflow-panel-animation'}
  153. ${nodeAnimation && 'workflow-node-animation'}
  154. `}
  155. >
  156. <Header />
  157. <Panel />
  158. <Operator />
  159. {
  160. showFeaturesPanel && <Features />
  161. }
  162. <HelpLine />
  163. <ReactFlow
  164. nodeTypes={nodeTypes}
  165. edgeTypes={edgeTypes}
  166. nodes={nodes}
  167. edges={edges}
  168. onPointerDown={enableShortcuts}
  169. onMouseLeave={disableShortcuts}
  170. onNodeDragStart={handleNodeDragStart}
  171. onNodeDrag={handleNodeDrag}
  172. onNodeDragStop={handleNodeDragStop}
  173. onNodeMouseEnter={handleNodeEnter}
  174. onNodeMouseLeave={handleNodeLeave}
  175. onNodeClick={handleNodeClick}
  176. onConnect={handleNodeConnect}
  177. onConnectStart={handleNodeConnectStart}
  178. onConnectEnd={handleNodeConnectEnd}
  179. onEdgeMouseEnter={handleEdgeEnter}
  180. onEdgeMouseLeave={handleEdgeLeave}
  181. onEdgesChange={handleEdgesChange}
  182. connectionLineComponent={CustomConnectionLine}
  183. defaultViewport={viewport}
  184. multiSelectionKeyCode={null}
  185. deleteKeyCode={null}
  186. nodesDraggable={!nodesReadOnly}
  187. nodesConnectable={!nodesReadOnly}
  188. nodesFocusable={!nodesReadOnly}
  189. edgesFocusable={!nodesReadOnly}
  190. panOnDrag={!workflowReadOnly}
  191. zoomOnPinch={!workflowReadOnly}
  192. zoomOnScroll={!workflowReadOnly}
  193. zoomOnDoubleClick={!workflowReadOnly}
  194. isValidConnection={isValidConnection}
  195. >
  196. <Background
  197. gap={[14, 14]}
  198. size={2}
  199. color='#E4E5E7'
  200. />
  201. </ReactFlow>
  202. </div>
  203. )
  204. })
  205. Workflow.displayName = 'Workflow'
  206. const WorkflowWrap = memo(() => {
  207. const {
  208. data,
  209. isLoading,
  210. } = useWorkflowInit()
  211. const nodesData = useMemo(() => {
  212. if (data)
  213. return initialNodes(data.graph.nodes, data.graph.edges)
  214. return []
  215. }, [data])
  216. const edgesData = useMemo(() => {
  217. if (data)
  218. return initialEdges(data.graph.edges, data.graph.nodes)
  219. return []
  220. }, [data])
  221. if (!data || isLoading) {
  222. return (
  223. <div className='flex justify-center items-center relative w-full h-full bg-[#F0F2F7]'>
  224. <Loading />
  225. </div>
  226. )
  227. }
  228. const features = data.features || {}
  229. const initialFeatures: FeaturesData = {
  230. file: {
  231. image: {
  232. enabled: !!features.file_upload?.image.enabled,
  233. number_limits: features.file_upload?.image.number_limits || 3,
  234. transfer_methods: features.file_upload?.image.transfer_methods || ['local_file', 'remote_url'],
  235. },
  236. },
  237. opening: {
  238. enabled: !!features.opening_statement,
  239. opening_statement: features.opening_statement,
  240. suggested_questions: features.suggested_questions,
  241. },
  242. suggested: features.suggested_questions_after_answer || { enabled: false },
  243. speech2text: features.speech_to_text || { enabled: false },
  244. text2speech: features.text_to_speech || { enabled: false },
  245. citation: features.retriever_resource || { enabled: false },
  246. moderation: features.sensitive_word_avoidance || { enabled: false },
  247. }
  248. return (
  249. <ReactFlowProvider>
  250. <FeaturesProvider features={initialFeatures}>
  251. <Workflow
  252. nodes={nodesData}
  253. edges={edgesData}
  254. viewport={data?.graph.viewport}
  255. />
  256. </FeaturesProvider>
  257. </ReactFlowProvider>
  258. )
  259. })
  260. WorkflowWrap.displayName = 'WorkflowWrap'
  261. const WorkflowContainer = () => {
  262. return (
  263. <WorkflowContextProvider>
  264. <WorkflowWrap />
  265. </WorkflowContextProvider>
  266. )
  267. }
  268. export default memo(WorkflowContainer)