Chore/slice workflow (#18351)

This commit is contained in:
zxhlyh
2025-04-18 13:59:12 +08:00
committed by GitHub
parent 523efbfea5
commit efe5db38ee
44 changed files with 1855 additions and 1239 deletions

View File

@ -5,11 +5,8 @@ import {
memo,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import useSWR from 'swr'
import { setAutoFreeze } from 'immer'
import {
useEventListener,
@ -31,17 +28,14 @@ import 'reactflow/dist/style.css'
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,
@ -49,11 +43,9 @@ import {
useSelectionInteractions,
useShortcuts,
useWorkflow,
useWorkflowInit,
useWorkflowReadOnly,
useWorkflowUpdate,
} from './hooks'
import Header from './header'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
@ -66,42 +58,28 @@ 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 PluginDependency from './plugin-dependency'
import {
useStore,
useWorkflowStore,
} from './store'
import {
initialEdges,
initialNodes,
} from './utils'
import {
CUSTOM_EDGE,
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 { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { fetchFileUploadConfig } from '@/service/common'
import DatasetsDetailProvider from './datasets-detail-store/provider'
import { HooksStoreContextProvider } from './hooks-store'
import type { Shape as HooksStoreShape } from './hooks-store'
const nodeTypes = {
[CUSTOM_NODE]: CustomNode,
@ -114,32 +92,32 @@ const edgeTypes = {
[CUSTOM_EDGE]: CustomEdge,
}
type WorkflowProps = {
export type WorkflowProps = {
nodes: Node[]
edges: Edge[]
viewport?: Viewport
children?: React.ReactNode
onWorkflowDataUpdate?: (v: any) => void
}
const Workflow: FC<WorkflowProps> = memo(({
export 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 {
@ -148,9 +126,6 @@ const Workflow: FC<WorkflowProps> = memo(({
} = useNodesSyncDraft()
const { workflowReadOnly } = useWorkflowReadOnly()
const { nodesReadOnly } = useNodesReadOnly()
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
@ -161,19 +136,13 @@ const Workflow: FC<WorkflowProps> = memo(({
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(() => {
@ -231,6 +200,12 @@ const Workflow: FC<WorkflowProps> = memo(({
})
}
})
const { handleFetchAllTools } = useFetchToolsData()
useEffect(() => {
handleFetchAllTools('builtin')
handleFetchAllTools('custom')
handleFetchAllTools('workflow')
}, [handleFetchAllTools])
const {
handleNodeDragStart,
@ -258,15 +233,10 @@ const Workflow: FC<WorkflowProps> = memo(({
} = useSelectionInteractions()
const {
handlePaneContextMenu,
handlePaneContextmenuCancel,
} = usePanelInteractions()
const {
isValidConnection,
} = useWorkflow()
const {
exportCheck,
handleExportDSL,
} = useDSL()
useOnViewportChange({
onEnd: () => {
@ -297,12 +267,7 @@ const Workflow: FC<WorkflowProps> = memo(({
>
<SyncingDataModal />
<CandidateNode />
<Header />
<Panel />
<Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
{
showFeaturesPanel && <Features />
}
<PanelContextmenu />
<NodeContextmenu />
<HelpLine />
@ -317,26 +282,8 @@ const Workflow: FC<WorkflowProps> = memo(({
/>
)
}
{
showImportDSLModal && (
<UpdateDSLModal
onCancel={() => setShowImportDSLModal(false)}
onBackup={exportCheck}
onImport={handlePaneContextmenuCancel}
/>
)
}
{
secretEnvList.length > 0 && (
<DSLExportConfirmModal
envList={secretEnvList}
onConfirm={handleExportDSL}
onClose={() => setSecretEnvList([])}
/>
)
}
<LimitTips />
<PluginDependency />
{children}
<ReactFlow
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
@ -389,89 +336,43 @@ const Workflow: FC<WorkflowProps> = memo(({
</div>
)
})
Workflow.displayName = 'Workflow'
const WorkflowWrap = memo(() => {
const {
data,
isLoading,
} = useWorkflowInit()
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
type WorkflowWithInnerContextProps = WorkflowProps & {
hooksStore?: Partial<HooksStoreShape>
}
export const WorkflowWithInnerContext = memo(({
hooksStore,
...restProps
}: WorkflowWithInnerContextProps) => {
return (
<HooksStoreContextProvider {...hooksStore}>
<Workflow {...restProps} />
</HooksStoreContextProvider>
)
})
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='relative flex h-full w-full items-center justify-center'>
<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'],
},
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 },
type WorkflowWithDefaultContextProps =
Pick<WorkflowProps, 'edges' | 'nodes'>
& {
children: React.ReactNode
}
const WorkflowWithDefaultContext = ({
nodes,
edges,
children,
}: WorkflowWithDefaultContextProps) => {
return (
<ReactFlowProvider>
<WorkflowHistoryProvider
nodes={nodesData}
edges={edgesData} >
<FeaturesProvider features={initialFeatures}>
<DatasetsDetailProvider nodes={nodesData}>
<Workflow
nodes={nodesData}
edges={edgesData}
viewport={data?.graph.viewport}
/>
</DatasetsDetailProvider>
</FeaturesProvider>
nodes={nodes}
edges={edges} >
<DatasetsDetailProvider nodes={nodes}>
{children}
</DatasetsDetailProvider>
</WorkflowHistoryProvider>
</ReactFlowProvider>
)
})
WorkflowWrap.displayName = 'WorkflowWrap'
const WorkflowContainer = () => {
return (
<WorkflowContextProvider>
<WorkflowWrap />
</WorkflowContextProvider>
)
}
export default memo(WorkflowContainer)
export default memo(WorkflowWithDefaultContext)