From 41a647fe3295faf7c4a2e943e82f4e067d78cbf4 Mon Sep 17 00:00:00 2001 From: balibabu Date: Tue, 21 Oct 2025 13:55:46 +0800 Subject: [PATCH] Feat: A pipeline's child node can only have one node #9869 (#10695) ### What problem does this PR solve? Feat: A pipeline's child node can only have one node #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/locales/en.ts | 4 ++-- web/src/pages/agent/canvas/node/handle.tsx | 12 +++++++++++- web/src/pages/agent/store.ts | 5 +++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 00447433c..d0ce59023 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1533,8 +1533,8 @@ This delimiter is used to split the input text into several text pieces echo of 'Your users will see this welcome message at the beginning.', modeTip: 'The mode defines how the workflow is initiated.', mode: 'Mode', - conversational: 'conversational', - task: 'task', + conversational: 'Conversational', + task: 'Task', beginInputTip: 'By defining input parameters, this content can be accessed by other components in subsequent processes.', query: 'Query variables', diff --git a/web/src/pages/agent/canvas/node/handle.tsx b/web/src/pages/agent/canvas/node/handle.tsx index 2f7530fd6..642db55db 100644 --- a/web/src/pages/agent/canvas/node/handle.tsx +++ b/web/src/pages/agent/canvas/node/handle.tsx @@ -5,6 +5,8 @@ import { Plus } from 'lucide-react'; import { useMemo } from 'react'; import { NodeHandleId } from '../../constant'; import { HandleContext } from '../../context'; +import { useIsPipeline } from '../../hooks/use-is-pipeline'; +import useGraphStore from '../../store'; import { useDropdownManager } from '../context'; import { NextStepDropdown } from './dropdown/next-step-dropdown'; @@ -14,9 +16,12 @@ export function CommonHandle({ ...props }: HandleProps & { nodeId: string }) { const { visible, hideModal, showModal } = useSetModalState(); - const { canShowDropdown, setActiveDropdown, clearActiveDropdown } = useDropdownManager(); + const { hasChildNode } = useGraphStore((state) => state); + const isPipeline = useIsPipeline(); + + const isConnectable = !(isPipeline && hasChildNode(nodeId)); // Using useMemo will cause isConnectable to not be updated when the subsequent connection line is deleted const value = useMemo( () => ({ @@ -33,6 +38,7 @@ export function CommonHandle({ { e.stopPropagation(); + if (!isConnectable) { + return; + } + if (!canShowDropdown()) { return; } diff --git a/web/src/pages/agent/store.ts b/web/src/pages/agent/store.ts index 69b118a98..dbfbf1eb0 100644 --- a/web/src/pages/agent/store.ts +++ b/web/src/pages/agent/store.ts @@ -89,6 +89,7 @@ export type RFState = { ) => void; // Deleting a condition of a classification operator will delete the related edge findAgentToolNodeById: (id: string | null) => string | undefined; selectNodeIds: (nodeIds: string[]) => void; + hasChildNode: (nodeId: string) => boolean; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -527,6 +528,10 @@ const useGraphStore = create()( })), ); }, + hasChildNode: (nodeId) => { + const { edges } = get(); + return edges.some((edge) => edge.source === nodeId); + }, })), { name: 'graph', trace: true }, ),