From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001
From: wwf <yearningwang@iqtogether.com>
Date: 星期三, 04 六月 2025 15:17:49 +0800
Subject: [PATCH] 初始化

---
 app/components/workflow/index.tsx |  205 ++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 145 insertions(+), 60 deletions(-)

diff --git a/app/components/workflow/index.tsx b/app/components/workflow/index.tsx
index 549117f..2a0572b 100644
--- a/app/components/workflow/index.tsx
+++ b/app/components/workflow/index.tsx
@@ -5,8 +5,11 @@
   memo,
   useCallback,
   useEffect,
+  useMemo,
   useRef,
+  useState,
 } from 'react'
+import useSWR from 'swr'
 import { setAutoFreeze } from 'immer'
 import {
   useEventListener,
@@ -28,14 +31,17 @@
 import './style.css'
 import type {
   Edge,
+  EnvironmentVariable,
   Node,
 } from './types'
 import {
   ControlMode,
+  SupportUploadFileTypes,
 } from './types'
+import { WorkflowContextProvider } from './context'
 import {
+  useDSL,
   useEdgesInteractions,
-  useFetchToolsData,
   useNodesInteractions,
   useNodesReadOnly,
   useNodesSyncDraft,
@@ -43,81 +49,88 @@
   useSelectionInteractions,
   useShortcuts,
   useWorkflow,
+  useWorkflowInit,
   useWorkflowReadOnly,
-  useWorkflowRefreshDraft,
+  useWorkflowUpdate,
 } from './hooks'
+import Header from './header'
 import CustomNode from './nodes'
 import CustomNoteNode from './note-node'
 import { CUSTOM_NOTE_NODE } from './note-node/constants'
 import CustomIterationStartNode from './nodes/iteration-start'
 import { CUSTOM_ITERATION_START_NODE } from './nodes/iteration-start/constants'
-import CustomLoopStartNode from './nodes/loop-start'
-import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants'
-import CustomSimpleNode from './simple-node'
-import { CUSTOM_SIMPLE_NODE } from './simple-node/constants'
 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 CandidateNode from './candidate-node'
 import PanelContextmenu from './panel-contextmenu'
 import NodeContextmenu from './node-contextmenu'
 import SyncingDataModal from './syncing-data-modal'
+import UpdateDSLModal from './update-dsl-modal'
+import DSLExportConfirmModal from './dsl-export-confirm-modal'
 import LimitTips from './limit-tips'
 import {
   useStore,
   useWorkflowStore,
 } from './store'
 import {
-  CUSTOM_EDGE,
+  initialEdges,
+  initialNodes,
+} from './utils'
+import {
   CUSTOM_NODE,
+  DSL_EXPORT_CHECK,
   ITERATION_CHILDREN_Z_INDEX,
   WORKFLOW_DATA_UPDATE,
 } from './constants'
 import { WorkflowHistoryProvider } from './workflow-history-store'
+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'
+import { useFeaturesStore } from '@/app/components/base/features/hooks'
 import { useEventEmitterContextContext } from '@/context/event-emitter'
 import Confirm from '@/app/components/base/confirm'
-import DatasetsDetailProvider from './datasets-detail-store/provider'
-import { HooksStoreContextProvider } from './hooks-store'
-import type { Shape as HooksStoreShape } from './hooks-store'
+import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
+import { fetchFileUploadConfig } from '@/service/common'
 
 const nodeTypes = {
   [CUSTOM_NODE]: CustomNode,
   [CUSTOM_NOTE_NODE]: CustomNoteNode,
-  [CUSTOM_SIMPLE_NODE]: CustomSimpleNode,
   [CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode,
-  [CUSTOM_LOOP_START_NODE]: CustomLoopStartNode,
 }
 const edgeTypes = {
-  [CUSTOM_EDGE]: CustomEdge,
+  [CUSTOM_NODE]: CustomEdge,
 }
 
-export type WorkflowProps = {
+type WorkflowProps = {
   nodes: Node[]
   edges: Edge[]
   viewport?: Viewport
-  children?: React.ReactNode
-  onWorkflowDataUpdate?: (v: any) => void
 }
-export const Workflow: FC<WorkflowProps> = memo(({
+const Workflow: FC<WorkflowProps> = memo(({
   nodes: originalNodes,
   edges: originalEdges,
   viewport,
-  children,
-  onWorkflowDataUpdate,
 }) => {
   const workflowContainerRef = useRef<HTMLDivElement>(null)
   const workflowStore = useWorkflowStore()
   const reactflow = useReactFlow()
+  const featuresStore = useFeaturesStore()
   const [nodes, setNodes] = useNodesState(originalNodes)
   const [edges, setEdges] = useEdgesState(originalEdges)
+  const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
   const controlMode = useStore(s => s.controlMode)
   const nodeAnimation = useStore(s => s.nodeAnimation)
   const showConfirm = useStore(s => s.showConfirm)
+  const showImportDSLModal = useStore(s => s.showImportDSLModal)
 
   const {
     setShowConfirm,
     setControlPromptEditorRerenderKey,
+    setShowImportDSLModal,
     setSyncWorkflowDraftHash,
   } = workflowStore.getState()
   const {
@@ -126,6 +139,9 @@
   } = useNodesSyncDraft()
   const { workflowReadOnly } = useWorkflowReadOnly()
   const { nodesReadOnly } = useNodesReadOnly()
+
+  const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
+
   const { eventEmitter } = useEventEmitterContextContext()
 
   eventEmitter?.useSubscription((v: any) => {
@@ -136,13 +152,19 @@
       if (v.payload.viewport)
         reactflow.setViewport(v.payload.viewport)
 
+      if (v.payload.features && featuresStore) {
+        const { setFeatures } = featuresStore.getState()
+
+        setFeatures(v.payload.features)
+      }
+
       if (v.payload.hash)
         setSyncWorkflowDraftHash(v.payload.hash)
 
-      onWorkflowDataUpdate?.(v.payload)
-
       setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
     }
+    if (v.type === DSL_EXPORT_CHECK)
+      setSecretEnvList(v.payload.data as EnvironmentVariable[])
   })
 
   useEffect(() => {
@@ -160,7 +182,7 @@
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [])
 
-  const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
+  const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
   const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {
     if (document.visibilityState === 'hidden')
       syncWorkflowDraftWhenPageClose()
@@ -200,12 +222,6 @@
       })
     }
   })
-  const { handleFetchAllTools } = useFetchToolsData()
-  useEffect(() => {
-    handleFetchAllTools('builtin')
-    handleFetchAllTools('custom')
-    handleFetchAllTools('workflow')
-  }, [handleFetchAllTools])
 
   const {
     handleNodeDragStart,
@@ -233,10 +249,15 @@
   } = useSelectionInteractions()
   const {
     handlePaneContextMenu,
+    handlePaneContextmenuCancel,
   } = usePanelInteractions()
   const {
     isValidConnection,
   } = useWorkflow()
+  const {
+    exportCheck,
+    handleExportDSL,
+  } = useDSL()
 
   useOnViewportChange({
     onEnd: () => {
@@ -259,7 +280,7 @@
     <div
       id='workflow-container'
       className={`
-        relative h-full w-full min-w-[960px]
+        relative w-full min-w-[960px] h-full 
         ${workflowReadOnly && 'workflow-panel-animation'}
         ${nodeAnimation && 'workflow-node-animation'}
       `}
@@ -267,7 +288,12 @@
     >
       <SyncingDataModal />
       <CandidateNode />
+      <Header />
+      <Panel />
       <Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
+      {
+        showFeaturesPanel && <Features />
+      }
       <PanelContextmenu />
       <NodeContextmenu />
       <HelpLine />
@@ -282,8 +308,25 @@
           />
         )
       }
+      {
+        showImportDSLModal && (
+          <UpdateDSLModal
+            onCancel={() => setShowImportDSLModal(false)}
+            onBackup={exportCheck}
+            onImport={handlePaneContextmenuCancel}
+          />
+        )
+      }
+      {
+        secretEnvList.length > 0 && (
+          <DSLExportConfirmModal
+            envList={secretEnvList}
+            onConfirm={handleExportDSL}
+            onClose={() => setSecretEnvList([])}
+          />
+        )
+      }
       <LimitTips />
-      {children}
       <ReactFlow
         nodeTypes={nodeTypes}
         edgeTypes={edgeTypes}
@@ -307,7 +350,6 @@
         onSelectionDrag={handleSelectionDrag}
         onPaneContextMenu={handlePaneContextMenu}
         connectionLineComponent={CustomConnectionLine}
-        // TODO: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
         connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
         defaultViewport={viewport}
         multiSelectionKeyCode={null}
@@ -316,7 +358,6 @@
         nodesConnectable={!nodesReadOnly}
         nodesFocusable={!nodesReadOnly}
         edgesFocusable={!nodesReadOnly}
-        panOnScroll={false}
         panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
         zoomOnPinch={!workflowReadOnly}
         zoomOnScroll={!workflowReadOnly}
@@ -337,43 +378,87 @@
     </div>
   )
 })
+Workflow.displayName = 'Workflow'
 
-type WorkflowWithInnerContextProps = WorkflowProps & {
-  hooksStore?: Partial<HooksStoreShape>
-}
-export const WorkflowWithInnerContext = memo(({
-  hooksStore,
-  ...restProps
-}: WorkflowWithInnerContextProps) => {
-  return (
-    <HooksStoreContextProvider {...hooksStore}>
-      <Workflow {...restProps} />
-    </HooksStoreContextProvider>
-  )
-})
+const WorkflowWrap = memo(() => {
+  const {
+    data,
+    isLoading,
+  } = useWorkflowInit()
+  const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
 
-type WorkflowWithDefaultContextProps =
-  Pick<WorkflowProps, 'edges' | 'nodes'>
-  & {
-    children: React.ReactNode
+  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'>
+        <Loading />
+      </div>
+    )
   }
 
-const WorkflowWithDefaultContext = ({
-  nodes,
-  edges,
-  children,
-}: WorkflowWithDefaultContextProps) => {
+  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'],
+      },
+      enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
+      allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
+      allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
+      allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
+      number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
+      fileUploadConfig: fileUploadConfigResponse,
+    },
+    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>
       <WorkflowHistoryProvider
-        nodes={nodes}
-        edges={edges} >
-        <DatasetsDetailProvider nodes={nodes}>
-          {children}
-        </DatasetsDetailProvider>
+        nodes={nodesData}
+        edges={edgesData} >
+        <FeaturesProvider features={initialFeatures}>
+          <Workflow
+            nodes={nodesData}
+            edges={edgesData}
+            viewport={data?.graph.viewport}
+          />
+        </FeaturesProvider>
       </WorkflowHistoryProvider>
     </ReactFlowProvider>
   )
+})
+WorkflowWrap.displayName = 'WorkflowWrap'
+
+const WorkflowContainer = () => {
+  return (
+    <WorkflowContextProvider>
+      <WorkflowWrap />
+    </WorkflowContextProvider>
+  )
 }
 
-export default memo(WorkflowWithDefaultContext)
+export default memo(WorkflowContainer)

--
Gitblit v1.8.0