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