index.tsx 7.1 KB

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