diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 52f652e4b5742f..cc86b56df5da83 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -326,6 +326,7 @@ export const ITERATION_PADDING = { bottom: 20, left: 16, } +export const PARALLEL_LIMIT = 10 export const RETRIEVAL_OUTPUT_STRUCT = `{ "content": "", diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 9430a7768a910c..fabd96a3c3803b 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -17,6 +17,7 @@ import { useReactFlow, useStoreApi, } from 'reactflow' +import { uniq } from 'lodash-es' import type { ToolDefaultValue } from '../block-selector/types' import type { Edge, @@ -212,7 +213,7 @@ export const useNodesInteractions = () => { setEdges(newEdges) const incomesNodes = getIncomers(node, nodes, edges) if (incomesNodes.length) { - const incomesNodesOutgoersId = incomesNodes.map(incomeNode => getOutgoers(incomeNode, nodes, edges)).flat().map(outgoer => outgoer.id) + const incomesNodesOutgoersId = uniq(incomesNodes.map(incomeNode => getOutgoers(incomeNode, nodes, edges)).flat().map(outgoer => outgoer.id)) if (incomesNodesOutgoersId.length > 1) { const newNodes = produce(nodes, (draft) => { diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 4a77c734e639d6..74cbf15e4a8aa3 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -6,6 +6,7 @@ import { } from 'react' import dayjs from 'dayjs' import { uniqBy } from 'lodash-es' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { getIncomers, @@ -29,6 +30,7 @@ import { useWorkflowStore, } from '../store' import { + PARALLEL_LIMIT, SUPPORT_OUTPUT_VARS_NODE, } from '../constants' import { CUSTOM_NOTE_NODE } from '../note-node/constants' @@ -59,6 +61,7 @@ export const useIsChatMode = () => { } export const useWorkflow = () => { + const { t } = useTranslation() const { locale } = useContext(I18n) const store = useStoreApi() const workflowStore = useWorkflowStore() @@ -288,6 +291,14 @@ export const useWorkflow = () => { if (sourceNode.type === CUSTOM_NOTE_NODE || targetNode.type === CUSTOM_NOTE_NODE) return false + const sourceNodeOutgoers = getOutgoers(sourceNode, nodes, edges) + + if (sourceNodeOutgoers.length > PARALLEL_LIMIT - 1) { + const { setShowTips } = workflowStore.getState() + setShowTips(t('workflow.common.parallelTip.limit', { num: PARALLEL_LIMIT })) + return false + } + if (sourceNode && targetNode) { const sourceNodeAvailableNextNodes = nodesExtraData[sourceNode.data.type].availableNextNodes const targetNodeAvailablePrevNodes = [...nodesExtraData[targetNode.data.type].availablePrevNodes, BlockEnum.Start] diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 4aebfae5a7d1d2..a42bcb6353af29 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -69,6 +69,7 @@ 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, @@ -320,6 +321,7 @@ const Workflow: FC = memo(({ /> ) } + { + const showTips = useStore(s => s.showTips) + const setShowTips = useStore(s => s.setShowTips) + + if (!showTips) + return null + + return ( +
+
+ +
+
+ {showTips} +
+ setShowTips('')} + > + + +
+ ) +} + +export default LimitTips diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index 429b19b2aac46b..cbee6837c9d761 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -16,10 +16,15 @@ import BlockSelector from '../../../block-selector' import type { ToolDefaultValue } from '../../../block-selector/types' import { useAvailableBlocks, + useIsChatMode, useNodesInteractions, useNodesReadOnly, } from '../../../hooks' -import { useStore } from '../../../store' +import { + useStore, + useWorkflowStore, +} from '../../../store' +import { PARALLEL_LIMIT } from '../../../constants' import Tooltip from '@/app/components/base/tooltip' type NodeHandleProps = { @@ -114,11 +119,13 @@ export const NodeSourceHandle = memo(({ }: NodeHandleProps) => { const { t } = useTranslation() const notInitialWorkflow = useStore(s => s.notInitialWorkflow) + const workflowStore = useWorkflowStore() const [open, setOpen] = useState(false) const { handleNodeAdd } = useNodesInteractions() const { getNodesReadOnly } = useNodesReadOnly() const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration) const isConnectable = !!availableNextBlocks.length + const isChatMode = useIsChatMode() const connected = data._connectedSourceHandleIds?.includes(handleId) const handleOpenChange = useCallback((v: boolean) => { @@ -126,8 +133,13 @@ export const NodeSourceHandle = memo(({ }, []) const handleHandleClick = useCallback((e: MouseEvent) => { e.stopPropagation() + if (data._connectedSourceHandleIds!.length > PARALLEL_LIMIT - 1) { + const { setShowTips } = workflowStore.getState() + setShowTips(t('workflow.common.parallelTip.limit', { num: PARALLEL_LIMIT })) + return + } setOpen(v => !v) - }, []) + }, [data._connectedSourceHandleIds]) const handleSelect = useCallback((type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => { handleNodeAdd( { @@ -142,7 +154,7 @@ export const NodeSourceHandle = memo(({ }, [handleNodeAdd, id, handleId]) useEffect(() => { - if (notInitialWorkflow && data.type === BlockEnum.Start) + if (notInitialWorkflow && data.type === BlockEnum.Start && !isChatMode) setOpen(true) }, [notInitialWorkflow, data.type]) diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index e3178d0627c044..8368028185ce4f 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -164,6 +164,8 @@ type Shape = { setShowImportDSLModal: (showImportDSLModal: boolean) => void showAddBlock: boolean setShowAddBlock: (showAddBlock: boolean) => void + showTips: string + setShowTips: (showTips: string) => void } export const createWorkflowStore = () => { @@ -266,6 +268,8 @@ export const createWorkflowStore = () => { setShowImportDSLModal: showImportDSLModal => set(() => ({ showImportDSLModal })), showAddBlock: false, setShowAddBlock: showAddBlock => set(() => ({ showAddBlock })), + showTips: '', + setShowTips: showTips => set(() => ({ showTips })), })) } diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index c6766e5f070bac..54142ab925d674 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -87,6 +87,7 @@ const translation = { title: 'Drag', desc: ' to connect', }, + limit: 'Parallelism is limited to {{num}} branches.', }, }, env: { diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index dd1e19625088a0..1824d447531d00 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -87,6 +87,7 @@ const translation = { title: '拖拽', desc: '连接节点', }, + limit: '并行分支限制为 {{num}} 个', }, }, env: {