diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx
index 21279da0b..7833b8763 100644
--- a/web/src/pages/agent/canvas/index.tsx
+++ b/web/src/pages/agent/canvas/index.tsx
@@ -149,38 +149,40 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
-
-
-
+
+
+
+
+
{formDrawerVisible && (
>
)}
diff --git a/web/src/pages/agent/canvas/node/begin-node.tsx b/web/src/pages/agent/canvas/node/begin-node.tsx
index c13db7c6f..d4f92e116 100644
--- a/web/src/pages/agent/canvas/node/begin-node.tsx
+++ b/web/src/pages/agent/canvas/node/begin-node.tsx
@@ -17,7 +17,7 @@ import styles from './index.less';
import { NodeWrapper } from './node-wrapper';
// TODO: do not allow other nodes to connect to this node
-function InnerBeginNode({ data }: NodeProps) {
+function InnerBeginNode({ data, id }: NodeProps) {
const { t } = useTranslation();
const query: BeginQuery[] = get(data, 'form.query', []);
@@ -29,14 +29,15 @@ function InnerBeginNode({ data }: NodeProps) {
isConnectable
className={styles.handle}
style={RightHandleStyle}
+ nodeId={id}
>
-
+
+
{query.map((x, idx) => {
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
diff --git a/web/src/pages/agent/canvas/node/categorize-node.tsx b/web/src/pages/agent/canvas/node/categorize-node.tsx
index 397ed9eee..7460d1d99 100644
--- a/web/src/pages/agent/canvas/node/categorize-node.tsx
+++ b/web/src/pages/agent/canvas/node/categorize-node.tsx
@@ -24,6 +24,7 @@ export function InnerCategorizeNode({
position={Position.Left}
isConnectable
id={'a'}
+ nodeId={id}
>
@@ -45,6 +46,7 @@ export function InnerCategorizeNode({
position={Position.Right}
isConnectable
style={{ ...RightHandleStyle, top: position.top }}
+ nodeId={id}
>
);
diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
new file mode 100644
index 000000000..434a5bb3b
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
@@ -0,0 +1,111 @@
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { Operator } from '@/pages/agent/constant';
+import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
+import OperatorIcon from '@/pages/agent/operator-icon';
+import { PropsWithChildren, useContext } from 'react';
+
+type OperatorItemProps = { operators: Operator[] };
+
+function OperatorItemList({ operators }: OperatorItemProps) {
+ const { addCanvasNode } = useContext(AgentInstanceContext);
+ const { nodeId, id, type, position } = useContext(HandleContext);
+
+ return (
+
+ {operators.map((x) => {
+ return (
+
+
+ {x}
+
+ );
+ })}
+
+ );
+}
+
+function AccordionOperators() {
+ return (
+
+
+ AI
+
+
+
+
+
+ Dialogue
+
+
+
+
+
+ Flow
+
+
+
+
+
+
+ Data Manipulation
+
+
+
+
+
+
+ Tools
+
+
+
+
+
+ );
+}
+
+export function NextStepDropdown({ children }: PropsWithChildren) {
+ return (
+
+ {children}
+ e.stopPropagation()}
+ className="w-[300px] font-semibold"
+ >
+ Next Step
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/canvas/node/handle.tsx b/web/src/pages/agent/canvas/node/handle.tsx
index 212208e60..ad76a87eb 100644
--- a/web/src/pages/agent/canvas/node/handle.tsx
+++ b/web/src/pages/agent/canvas/node/handle.tsx
@@ -1,17 +1,41 @@
import { cn } from '@/lib/utils';
import { Handle, HandleProps } from '@xyflow/react';
import { Plus } from 'lucide-react';
+import { useMemo } from 'react';
+import { HandleContext } from '../../context';
+import { NextStepDropdown } from './dropdown/next-step-dropdown';
+
+export function CommonHandle({
+ className,
+ nodeId,
+ ...props
+}: HandleProps & { nodeId: string }) {
+ const value = useMemo(
+ () => ({
+ nodeId,
+ id: props.id,
+ type: props.type,
+ position: props.position,
+ }),
+ [nodeId, props.id, props.position, props.type],
+ );
-export function CommonHandle({ className, ...props }: HandleProps) {
return (
-
-
-
+
+
+ {
+ e.stopPropagation();
+ }}
+ >
+
+
+
+
);
}
diff --git a/web/src/pages/agent/canvas/node/index.tsx b/web/src/pages/agent/canvas/node/index.tsx
index d67ec040c..4afad2d2d 100644
--- a/web/src/pages/agent/canvas/node/index.tsx
+++ b/web/src/pages/agent/canvas/node/index.tsx
@@ -1,11 +1,10 @@
-import { useTheme } from '@/components/theme-provider';
import { IRagNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
import { memo } from 'react';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
import { ToolBar } from './toolbar';
function InnerRagNode({
@@ -14,36 +13,27 @@ function InnerRagNode({
isConnectable = true,
selected,
}: NodeProps) {
- const { theme } = useTheme();
return (
-
-
+
-
+
+ nodeId={id}
+ >
-
+
);
}
diff --git a/web/src/pages/agent/canvas/node/logic-node.tsx b/web/src/pages/agent/canvas/node/logic-node.tsx
index 3e43954ae..a98efc371 100644
--- a/web/src/pages/agent/canvas/node/logic-node.tsx
+++ b/web/src/pages/agent/canvas/node/logic-node.tsx
@@ -1,11 +1,11 @@
-import { useTheme } from '@/components/theme-provider';
import { ILogicNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
import { memo } from 'react';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
export function InnerLogicNode({
id,
@@ -13,35 +13,28 @@ export function InnerLogicNode({
isConnectable = true,
selected,
}: NodeProps) {
- const { theme } = useTheme();
return (
-
+
+
+
+
+
+
+
);
}
diff --git a/web/src/pages/agent/canvas/node/message-node.tsx b/web/src/pages/agent/canvas/node/message-node.tsx
index 5c76df9a2..7dd4f77e1 100644
--- a/web/src/pages/agent/canvas/node/message-node.tsx
+++ b/web/src/pages/agent/canvas/node/message-node.tsx
@@ -27,6 +27,7 @@ function InnerMessageNode({
position={Position.Left}
isConnectable={isConnectable}
style={LeftHandleStyle}
+ nodeId={id}
>
) {
position={Position.Left}
isConnectable
id={'a'}
+ nodeId={id}
>
@@ -94,6 +95,7 @@ function InnerSwitchNode({ id, data, selected }: NodeProps) {
position={Position.Right}
isConnectable
style={{ ...RightHandleStyle, top: position.top }}
+ nodeId={id}
>
);
diff --git a/web/src/pages/agent/context.ts b/web/src/pages/agent/context.ts
index cca63defb..eda8c50ee 100644
--- a/web/src/pages/agent/context.ts
+++ b/web/src/pages/agent/context.ts
@@ -1,4 +1,5 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { HandleType, Position } from '@xyflow/react';
import { createContext } from 'react';
import { useAddNode } from './hooks/use-add-node';
import { useCacheChatLog } from './hooks/use-cache-chat-log';
@@ -34,3 +35,14 @@ type AgentChatLogContextType = Pick<
export const AgentChatLogContext = createContext(
{} as AgentChatLogContextType,
);
+
+export type HandleContextType = {
+ nodeId?: string;
+ id?: string;
+ type: HandleType;
+ position: Position;
+};
+
+export const HandleContext = createContext(
+ {} as HandleContextType,
+);
diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx
index 82eb2cd31..bf2a1a6d3 100644
--- a/web/src/pages/agent/form/agent-form/index.tsx
+++ b/web/src/pages/agent/form/agent-form/index.tsx
@@ -11,6 +11,7 @@ import {
FormLabel,
} from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
+import { Position } from '@xyflow/react';
import { useContext, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@@ -109,7 +110,12 @@ const AgentForm = ({ node }: INextOperatorForm) => {
)}
/>
-
+
Add Agent
diff --git a/web/src/pages/agent/hooks/use-add-node.ts b/web/src/pages/agent/hooks/use-add-node.ts
index b234805d8..445850a30 100644
--- a/web/src/pages/agent/hooks/use-add-node.ts
+++ b/web/src/pages/agent/hooks/use-add-node.ts
@@ -124,6 +124,39 @@ export const useGetNodeName = () => {
};
};
+export function useCalculateNewlyChildPosition() {
+ const getNode = useGraphStore((state) => state.getNode);
+ const nodes = useGraphStore((state) => state.nodes);
+ const edges = useGraphStore((state) => state.edges);
+
+ const calculateNewlyBackChildPosition = useCallback(
+ (id?: string, sourceHandle?: string) => {
+ const parentNode = getNode(id);
+
+ // Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
+ const allChildNodeIds = edges
+ .filter((x) => x.source === id && x.sourceHandle === sourceHandle)
+ .map((x) => x.target);
+
+ const yAxises = nodes
+ .filter((x) => allChildNodeIds.some((y) => y === x.id))
+ .map((x) => x.position.y);
+
+ const maxY = Math.max(...yAxises);
+
+ const position = {
+ y: yAxises.length > 0 ? maxY + 262 : (parentNode?.position.y || 0) + 82,
+ x: (parentNode?.position.x || 0) + 140,
+ };
+
+ return position;
+ },
+ [edges, getNode, nodes],
+ );
+
+ return { calculateNewlyBackChildPosition };
+}
+
export function useAddNode(reactFlowInstance?: ReactFlowInstance) {
const addNode = useGraphStore((state) => state.addNode);
const getNode = useGraphStore((state) => state.getNode);
@@ -132,95 +165,111 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) {
const edges = useGraphStore((state) => state.edges);
const getNodeName = useGetNodeName();
const initializeOperatorParams = useInitializeOperatorParams();
+ const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition();
// const [reactFlowInstance, setReactFlowInstance] =
// useState>();
const addCanvasNode = useCallback(
- (type: string, id?: string) => (event: React.MouseEvent) => {
- // reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
- // and you don't need to subtract the reactFlowBounds.left/top anymore
- // details: https://@xyflow/react.dev/whats-new/2023-11-10
- const position = reactFlowInstance?.screenToFlowPosition({
- x: event.clientX,
- y: event.clientY,
- });
+ (
+ type: string,
+ params: { id?: string; position?: Position; sourceHandle?: string } = {
+ position: Position.Right,
+ },
+ ) =>
+ (event: React.MouseEvent) => {
+ const id = params.id;
- const newNode: Node = {
- id: `${type}:${humanId()}`,
- type: NodeMap[type as Operator] || 'ragNode',
- position: position || {
- x: 0,
- y: 0,
- },
- data: {
- label: `${type}`,
- name: generateNodeNamesWithIncreasingIndex(getNodeName(type), nodes),
- form: initializeOperatorParams(type as Operator),
- },
- sourcePosition: Position.Right,
- targetPosition: Position.Left,
- dragHandle: getNodeDragHandle(type),
- };
+ // reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
+ // and you don't need to subtract the reactFlowBounds.left/top anymore
+ // details: https://@xyflow/react.dev/whats-new/2023-11-10
+ let position = reactFlowInstance?.screenToFlowPosition({
+ x: event.clientX,
+ y: event.clientY,
+ });
- if (type === Operator.Iteration) {
- newNode.width = 500;
- newNode.height = 250;
- const iterationStartNode: Node = {
- id: `${Operator.IterationStart}:${humanId()}`,
- type: 'iterationStartNode',
- position: { x: 50, y: 100 },
- // draggable: false,
- data: {
- label: Operator.IterationStart,
- name: Operator.IterationStart,
- form: {},
+ if (params.position === Position.Right) {
+ position = calculateNewlyBackChildPosition(id, params.sourceHandle);
+ }
+
+ const newNode: Node = {
+ id: `${type}:${humanId()}`,
+ type: NodeMap[type as Operator] || 'ragNode',
+ position: position || {
+ x: 0,
+ y: 0,
},
- parentId: newNode.id,
- extent: 'parent',
+ data: {
+ label: `${type}`,
+ name: generateNodeNamesWithIncreasingIndex(
+ getNodeName(type),
+ nodes,
+ ),
+ form: initializeOperatorParams(type as Operator),
+ },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ dragHandle: getNodeDragHandle(type),
};
- addNode(newNode);
- addNode(iterationStartNode);
- } else if (type === Operator.Agent) {
- const agentNode = getNode(id);
- if (agentNode) {
- // Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
- const allChildAgentNodeIds = edges
- .filter((x) => x.source === id && x.sourceHandle === 'e')
- .map((x) => x.target);
- const xAxises = nodes
- .filter((x) => allChildAgentNodeIds.some((y) => y === x.id))
- .map((x) => x.position.x);
-
- const maxX = Math.max(...xAxises);
-
- newNode.position = {
- x: xAxises.length > 0 ? maxX + 262 : agentNode.position.x + 82,
- y: agentNode.position.y + 140,
+ if (type === Operator.Iteration) {
+ newNode.width = 500;
+ newNode.height = 250;
+ const iterationStartNode: Node = {
+ id: `${Operator.IterationStart}:${humanId()}`,
+ type: 'iterationStartNode',
+ position: { x: 50, y: 100 },
+ // draggable: false,
+ data: {
+ label: Operator.IterationStart,
+ name: Operator.IterationStart,
+ form: {},
+ },
+ parentId: newNode.id,
+ extent: 'parent',
};
+ addNode(newNode);
+ addNode(iterationStartNode);
+ } else if (type === Operator.Agent) {
+ const agentNode = getNode(id);
+ if (agentNode) {
+ // Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
+ const allChildAgentNodeIds = edges
+ .filter((x) => x.source === id && x.sourceHandle === 'e')
+ .map((x) => x.target);
+
+ const xAxises = nodes
+ .filter((x) => allChildAgentNodeIds.some((y) => y === x.id))
+ .map((x) => x.position.x);
+
+ const maxX = Math.max(...xAxises);
+
+ newNode.position = {
+ x: xAxises.length > 0 ? maxX + 262 : agentNode.position.x + 82,
+ y: agentNode.position.y + 140,
+ };
+ }
+ addNode(newNode);
+ if (id) {
+ addEdge({
+ source: id,
+ target: newNode.id,
+ sourceHandle: 'e',
+ targetHandle: 'f',
+ });
+ }
+ } else {
+ const subNodeOfIteration = getRelativePositionToIterationNode(
+ nodes,
+ position,
+ );
+ if (subNodeOfIteration) {
+ newNode.parentId = subNodeOfIteration.parentId;
+ newNode.position = subNodeOfIteration.position;
+ newNode.extent = 'parent';
+ }
+ addNode(newNode);
}
- addNode(newNode);
- if (id) {
- addEdge({
- source: id,
- target: newNode.id,
- sourceHandle: 'e',
- targetHandle: 'f',
- });
- }
- } else {
- const subNodeOfIteration = getRelativePositionToIterationNode(
- nodes,
- position,
- );
- if (subNodeOfIteration) {
- newNode.parentId = subNodeOfIteration.parentId;
- newNode.position = subNodeOfIteration.position;
- newNode.extent = 'parent';
- }
- addNode(newNode);
- }
- },
+ },
[
addEdge,
addNode,