| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 | 'use client'import type { FC } from 'react'import {  memo,  useCallback,  useEffect,  useMemo,} from 'react'import { setAutoFreeze } from 'immer'import {  useKeyPress,} from 'ahooks'import ReactFlow, {  Background,  ReactFlowProvider,  useOnViewportChange,} from 'reactflow'import type { Viewport } from 'reactflow'import 'reactflow/dist/style.css'import './style.css'import type {  Edge,  Node,} from './types'import { WorkflowContextProvider } from './context'import {  useEdgesInteractions,  useNodesInteractions,  useNodesReadOnly,  useNodesSyncDraft,  useWorkflow,  useWorkflowInit,  useWorkflowReadOnly,} from './hooks'import Header from './header'import CustomNode from './nodes'import Operator from './operator'import CustomEdge from './custom-edge'import CustomConnectionLine from './custom-connection-line'import Panel from './panel'import Features from './features'import HelpLine from './help-line'import { useStore } from './store'import {  initialEdges,  initialNodes,} from './utils'import Loading from '@/app/components/base/loading'import { FeaturesProvider } from '@/app/components/base/features'import type { Features as FeaturesData } from '@/app/components/base/features/types'const nodeTypes = {  custom: CustomNode,}const edgeTypes = {  custom: CustomEdge,}type WorkflowProps = {  nodes: Node[]  edges: Edge[]  viewport?: Viewport}const Workflow: FC<WorkflowProps> = memo(({  nodes,  edges,  viewport,}) => {  const showFeaturesPanel = useStore(state => state.showFeaturesPanel)  const nodeAnimation = useStore(s => s.nodeAnimation)  const {    handleSyncWorkflowDraft,    syncWorkflowDraftWhenPageClose,  } = useNodesSyncDraft()  const { workflowReadOnly } = useWorkflowReadOnly()  const { nodesReadOnly } = useNodesReadOnly()  useEffect(() => {    setAutoFreeze(false)    return () => {      setAutoFreeze(true)    }  }, [])  useEffect(() => {    return () => {      handleSyncWorkflowDraft(true)    }  }, [])  const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {    if (document.visibilityState === 'hidden')      syncWorkflowDraftWhenPageClose()  }, [syncWorkflowDraftWhenPageClose])  useEffect(() => {    document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)    return () => {      document.removeEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)    }  }, [handleSyncWorkflowDraftWhenPageClose])  const {    handleNodeDragStart,    handleNodeDrag,    handleNodeDragStop,    handleNodeEnter,    handleNodeLeave,    handleNodeClick,    handleNodeConnect,    handleNodeConnectStart,    handleNodeConnectEnd,    handleNodeDuplicateSelected,    handleNodeCopySelected,    handleNodeCut,    handleNodeDeleteSelected,    handleNodePaste,  } = useNodesInteractions()  const {    handleEdgeEnter,    handleEdgeLeave,    handleEdgeDelete,    handleEdgesChange,  } = useEdgesInteractions()  const {    isValidConnection,    enableShortcuts,    disableShortcuts,  } = useWorkflow()  useOnViewportChange({    onEnd: () => {      handleSyncWorkflowDraft()    },  })  useKeyPress(['delete'], handleEdgeDelete)  useKeyPress(['delete', 'backspace'], handleNodeDeleteSelected)  useKeyPress(['ctrl.c', 'meta.c'], handleNodeCopySelected)  useKeyPress(['ctrl.x', 'meta.x'], handleNodeCut)  useKeyPress(['ctrl.v', 'meta.v'], handleNodePaste)  useKeyPress(['ctrl.alt.d', 'meta.shift.d'], handleNodeDuplicateSelected)  return (    <div      id='workflow-container'      className={`        relative w-full min-w-[960px] h-full bg-[#F0F2F7]        ${workflowReadOnly && 'workflow-panel-animation'}        ${nodeAnimation && 'workflow-node-animation'}      `}    >      <Header />      <Panel />      <Operator />      {        showFeaturesPanel && <Features />      }      <HelpLine />      <ReactFlow        nodeTypes={nodeTypes}        edgeTypes={edgeTypes}        nodes={nodes}        edges={edges}        onPointerDown={enableShortcuts}        onMouseLeave={disableShortcuts}        onNodeDragStart={handleNodeDragStart}        onNodeDrag={handleNodeDrag}        onNodeDragStop={handleNodeDragStop}        onNodeMouseEnter={handleNodeEnter}        onNodeMouseLeave={handleNodeLeave}        onNodeClick={handleNodeClick}        onConnect={handleNodeConnect}        onConnectStart={handleNodeConnectStart}        onConnectEnd={handleNodeConnectEnd}        onEdgeMouseEnter={handleEdgeEnter}        onEdgeMouseLeave={handleEdgeLeave}        onEdgesChange={handleEdgesChange}        connectionLineComponent={CustomConnectionLine}        defaultViewport={viewport}        multiSelectionKeyCode={null}        deleteKeyCode={null}        nodesDraggable={!nodesReadOnly}        nodesConnectable={!nodesReadOnly}        nodesFocusable={!nodesReadOnly}        edgesFocusable={!nodesReadOnly}        panOnDrag={!workflowReadOnly}        zoomOnPinch={!workflowReadOnly}        zoomOnScroll={!workflowReadOnly}        zoomOnDoubleClick={!workflowReadOnly}        isValidConnection={isValidConnection}      >        <Background          gap={[14, 14]}          size={2}          color='#E4E5E7'        />      </ReactFlow>    </div>  )})Workflow.displayName = 'Workflow'const WorkflowWrap = memo(() => {  const {    data,    isLoading,  } = useWorkflowInit()  const nodesData = useMemo(() => {    if (data)      return initialNodes(data.graph.nodes, data.graph.edges)    return []  }, [data])  const edgesData = useMemo(() => {    if (data)      return initialEdges(data.graph.edges, data.graph.nodes)    return []  }, [data])  if (!data || isLoading) {    return (      <div className='flex justify-center items-center relative w-full h-full bg-[#F0F2F7]'>        <Loading />      </div>    )  }  const features = data.features || {}  const initialFeatures: FeaturesData = {    file: {      image: {        enabled: !!features.file_upload?.image.enabled,        number_limits: features.file_upload?.image.number_limits || 3,        transfer_methods: features.file_upload?.image.transfer_methods || ['local_file', 'remote_url'],      },    },    opening: {      enabled: !!features.opening_statement,      opening_statement: features.opening_statement,      suggested_questions: features.suggested_questions,    },    suggested: features.suggested_questions_after_answer || { enabled: false },    speech2text: features.speech_to_text || { enabled: false },    text2speech: features.text_to_speech || { enabled: false },    citation: features.retriever_resource || { enabled: false },    moderation: features.sensitive_word_avoidance || { enabled: false },  }  return (    <ReactFlowProvider>      <FeaturesProvider features={initialFeatures}>        <Workflow          nodes={nodesData}          edges={edgesData}          viewport={data?.graph.viewport}        />      </FeaturesProvider>    </ReactFlowProvider>  )})WorkflowWrap.displayName = 'WorkflowWrap'const WorkflowContainer = () => {  return (    <WorkflowContextProvider>      <WorkflowWrap />    </WorkflowContextProvider>  )}export default memo(WorkflowContainer)
 |