From 77950e48c76f4a3b29d01831d43039caba29888a Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期二, 18 十一月 2025 14:12:42 +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