From 64c83f300a1cb6990c230c99b440d5342fd93fcd Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 13 Jun 2024 09:09:34 +0800 Subject: [PATCH] feat: duplicate node #918 (#1136) ### What problem does this PR solve? feat: duplicate node #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/operate-dropdown/index.tsx | 29 ++++++++------ .../pages/flow/canvas/context-menu/index.tsx | 2 + web/src/pages/flow/canvas/index.tsx | 12 +----- web/src/pages/flow/canvas/node/index.tsx | 39 +++++++++++++++++-- web/src/pages/flow/index.tsx | 1 - web/src/pages/flow/store.ts | 23 +++++++++++ 6 files changed, 79 insertions(+), 27 deletions(-) diff --git a/web/src/components/operate-dropdown/index.tsx b/web/src/components/operate-dropdown/index.tsx index 27865fd1b..4dbcce8ce 100644 --- a/web/src/components/operate-dropdown/index.tsx +++ b/web/src/components/operate-dropdown/index.tsx @@ -3,18 +3,20 @@ import { DeleteOutlined, MoreOutlined } from '@ant-design/icons'; import { Dropdown, MenuProps, Space } from 'antd'; import { useTranslation } from 'react-i18next'; -import React from 'react'; +import React, { useMemo } from 'react'; import styles from './index.less'; interface IProps { deleteItem: () => Promise | void; iconFontSize?: number; + items?: MenuProps['items']; } const OperateDropdown = ({ deleteItem, children, iconFontSize = 30, + items: otherItems = [], }: React.PropsWithChildren) => { const { t } = useTranslation(); const showDeleteConfirm = useShowDeleteConfirm(); @@ -31,17 +33,20 @@ const OperateDropdown = ({ } }; - const items: MenuProps['items'] = [ - { - key: '1', - label: ( - - {t('common.delete')} - - - ), - }, - ]; + const items: MenuProps['items'] = useMemo(() => { + return [ + { + key: '1', + label: ( + + {t('common.delete')} + + + ), + }, + ...otherItems, + ]; + }, [t, otherItems]); return ( { const [menu, setMenu] = useState({} as INodeContextMenu); const ref = useRef(null); diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx index fb10a6d84..b3010995c 100644 --- a/web/src/pages/flow/canvas/index.tsx +++ b/web/src/pages/flow/canvas/index.tsx @@ -8,7 +8,6 @@ import ReactFlow, { } from 'reactflow'; import 'reactflow/dist/style.css'; -import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu'; import { ButtonEdge } from './edge'; import FlowDrawer from '../flow-drawer'; @@ -30,12 +29,11 @@ const edgeTypes = { }; interface IProps { - sideWidth: number; chatDrawerVisible: boolean; hideChatDrawer(): void; } -function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) { +function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) { const { nodes, edges, @@ -45,8 +43,6 @@ function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) { onSelectionChange, } = useSelectCanvasData(); - const { ref, menu, onNodeContextMenu, onPaneClick } = - useHandleNodeContextMenu(sideWidth); const { drawerVisible, hideDrawer, showDrawer, clickedNode } = useShowDrawer(); @@ -64,18 +60,15 @@ function FlowCanvas({ sideWidth, chatDrawerVisible, hideChatDrawer }: IProps) { return (
- {Object.keys(menu).length > 0 && ( - - )} ) { + const { t } = useTranslation(); const deleteNodeById = useGraphStore((store) => store.deleteNodeById); + const duplicateNodeById = useGraphStore((store) => store.duplicateNode); + const deleteNode = useCallback(() => { deleteNodeById(id); }, [id, deleteNodeById]); + const duplicateNode = useCallback(() => { + duplicateNodeById(id); + }, [id, duplicateNodeById]); + + const description = operatorMap[data.label as Operator].description; + + const items: MenuProps['items'] = [ + { + key: '2', + onClick: duplicateNode, + label: ( + + {t('common.copy')} + + + ), + }, + ]; + return (
-
- {operatorMap[data.label as Operator].description} +
+ + {description} +
); diff --git a/web/src/pages/flow/index.tsx b/web/src/pages/flow/index.tsx index 86295b570..396f051cd 100644 --- a/web/src/pages/flow/index.tsx +++ b/web/src/pages/flow/index.tsx @@ -27,7 +27,6 @@ function RagFlow() { diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts index 191eb05a7..4ffd1cb6b 100644 --- a/web/src/pages/flow/store.ts +++ b/web/src/pages/flow/store.ts @@ -1,4 +1,5 @@ import type {} from '@redux-devtools/extension'; +import { humanId } from 'human-id'; import { Connection, Edge, @@ -32,6 +33,8 @@ export type RFState = { updateNodeForm: (nodeId: string, values: any) => void; onSelectionChange: OnSelectionChangeFunc; addNode: (nodes: Node) => void; + getNode: (id: string) => Node | undefined; + duplicateNode: (id: string) => void; deleteEdge: () => void; deleteEdgeById: (id: string) => void; deleteNodeById: (id: string) => void; @@ -76,6 +79,26 @@ const useGraphStore = create()( addNode: (node: Node) => { set({ nodes: get().nodes.concat(node) }); }, + getNode: (id: string) => { + return get().nodes.find((x) => x.id === id); + }, + duplicateNode: (id: string) => { + const { getNode, addNode } = get(); + const node = getNode(id); + const position = { + x: (node?.position?.x || 0) + 30, + y: (node?.position?.y || 0) + 20, + }; + + addNode({ + ...(node || {}), + data: node?.data, + selected: false, + dragging: false, + id: `${node?.data?.label}:${humanId()}`, + position, + }); + }, deleteEdge: () => { const { edges, selectedEdgeIds } = get(); set({