mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
feat: copy module
This commit is contained in:
@@ -24,6 +24,10 @@
|
||||
},
|
||||
"common": {
|
||||
"Add": "Add",
|
||||
"Cancel": "Cancel",
|
||||
"Collect": "Collect",
|
||||
"Copy": "Copy",
|
||||
"Delete": "Delete",
|
||||
"Filed is repeat": "Filed is repeated",
|
||||
"Filed is repeated": "",
|
||||
"Input": "Input",
|
||||
|
@@ -24,6 +24,10 @@
|
||||
},
|
||||
"common": {
|
||||
"Add": "添加",
|
||||
"Cancel": "取消",
|
||||
"Collect": "收藏",
|
||||
"Copy": "复制",
|
||||
"Delete": "删除",
|
||||
"Filed is repeat": "",
|
||||
"Filed is repeated": "字段重复了",
|
||||
"Input": "输入",
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, useTheme } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@/types/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -15,6 +16,7 @@ type Props = {
|
||||
minW?: string | number;
|
||||
moduleId: string;
|
||||
onDelNode: FlowModuleItemType['onDelNode'];
|
||||
onCopyNode: FlowModuleItemType['onCopyNode'];
|
||||
};
|
||||
|
||||
const NodeCard = ({
|
||||
@@ -23,11 +25,39 @@ const NodeCard = ({
|
||||
name = '未知模块',
|
||||
description,
|
||||
minW = '300px',
|
||||
onCopyNode,
|
||||
onDelNode,
|
||||
moduleId
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const menuList = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () => onDelNode(moduleId)
|
||||
},
|
||||
// {
|
||||
// icon: 'collectionLight',
|
||||
// label: t('common.Collect'),
|
||||
// onClick: () => {}
|
||||
// },
|
||||
{
|
||||
icon: 'back',
|
||||
label: t('common.Cancel'),
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[moduleId, onCopyNode, onDelNode, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
@@ -52,19 +82,27 @@ const NodeCard = ({
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
<Flex
|
||||
className={'nodrag'}
|
||||
w={'22px'}
|
||||
h={'22px'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() => onDelNode(moduleId)}
|
||||
>
|
||||
<MyIcon name="delete" w={'16px'} />
|
||||
</Flex>
|
||||
<Menu autoSelect={false} isLazy>
|
||||
<MenuButton
|
||||
className={'nodrag'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||
{menuList.map((item) => (
|
||||
<MenuItem key={item.label} onClick={item.onClick} py={[2, 3]}>
|
||||
<MyIcon name={item.icon as any} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
{children}
|
||||
</Box>
|
||||
|
@@ -10,7 +10,7 @@ import ReactFlow, {
|
||||
Connection,
|
||||
useViewport
|
||||
} from 'reactflow';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure, position } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
edgeOptions,
|
||||
@@ -126,6 +126,32 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const onAddNode = useCallback(
|
||||
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
|
||||
if (!reactFlowWrapper.current) return;
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||
|
||||
setNodes((state) =>
|
||||
state.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...template,
|
||||
moduleId: nanoid(),
|
||||
position: { x: mouseX, y: mouseY }
|
||||
},
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[x, zoom, y]
|
||||
);
|
||||
const onDelNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
setNodes((state) => state.filter((item) => item.id !== nodeId));
|
||||
@@ -155,6 +181,45 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
const onCopyNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
setNodes((nodes) => {
|
||||
const node = nodes.find((node) => node.id === nodeId);
|
||||
if (!node) return nodes;
|
||||
const template = {
|
||||
logo: node.data.logo,
|
||||
name: node.data.name,
|
||||
intro: node.data.intro,
|
||||
description: node.data.description,
|
||||
flowType: node.data.flowType,
|
||||
inputs: node.data.inputs,
|
||||
outputs: node.data.outputs,
|
||||
showStatus: node.data.showStatus
|
||||
};
|
||||
return nodes.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...template,
|
||||
moduleId: nanoid(),
|
||||
position: { x: node.position.x + 200, y: node.position.y + 50 }
|
||||
},
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
[setNodes]
|
||||
);
|
||||
const onCollectionNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
console.log(nodes.find((node) => node.id === nodeId));
|
||||
},
|
||||
[nodes]
|
||||
);
|
||||
|
||||
const flow2AppModules = useCallback(() => {
|
||||
const modules: AppModuleItemType[] = nodes.map((item) => ({
|
||||
@@ -264,39 +329,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
})
|
||||
);
|
||||
},
|
||||
[setNodes]
|
||||
[]
|
||||
);
|
||||
|
||||
const onAddNode = useCallback(
|
||||
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
|
||||
if (!reactFlowWrapper.current) return;
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||
|
||||
setNodes((state) =>
|
||||
state.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...template,
|
||||
moduleId: nanoid(),
|
||||
position: { x: mouseX, y: mouseY }
|
||||
},
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[onDelEdge, onChangeNode, onDelNode, setNodes, x, y, zoom]
|
||||
);
|
||||
const onDelConnect = useCallback(
|
||||
(id: string) => {
|
||||
setEdges((state) => state.filter((item) => item.id !== id));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
const onDelConnect = useCallback((id: string) => {
|
||||
setEdges((state) => state.filter((item) => item.id !== id));
|
||||
}, []);
|
||||
const onConnect = useCallback(
|
||||
({ connect }: { connect: Connection }) => {
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
@@ -342,7 +380,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
)
|
||||
);
|
||||
},
|
||||
[onDelConnect, setEdges, nodes]
|
||||
[nodes]
|
||||
);
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
@@ -373,19 +411,31 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
item,
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onFixView();
|
||||
},
|
||||
[onDelConnect, setEdges, setNodes, onFixView, onChangeNode, onDelNode, onDelEdge]
|
||||
[
|
||||
onDelConnect,
|
||||
setEdges,
|
||||
setNodes,
|
||||
onFixView,
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initData(JSON.parse(JSON.stringify(app)));
|
||||
}, [app, initData]);
|
||||
}, [app]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
2
client/src/types/flow.d.ts
vendored
2
client/src/types/flow.d.ts
vendored
@@ -61,6 +61,8 @@ export type FlowModuleItemType = FlowModuleTemplateType & {
|
||||
moduleId: string;
|
||||
onChangeNode: (e: FlowModuleItemChangeProps) => void;
|
||||
onDelNode: (id: string) => void;
|
||||
onCopyNode: (id: string) => void;
|
||||
onCollectionNode: (id: string) => void;
|
||||
onDelEdge: ({
|
||||
moduleId,
|
||||
sourceHandle,
|
||||
|
@@ -64,12 +64,16 @@ export const appModule2FlowNode = ({
|
||||
item,
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
}: {
|
||||
item: AppModuleItemType;
|
||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
||||
onDelNode: FlowModuleItemType['onDelNode'];
|
||||
onDelEdge: FlowModuleItemType['onDelEdge'];
|
||||
onCopyNode: FlowModuleItemType['onCopyNode'];
|
||||
onCollectionNode: FlowModuleItemType['onCollectionNode'];
|
||||
}): Node<FlowModuleItemType> => {
|
||||
// init some static data
|
||||
const template =
|
||||
@@ -109,7 +113,9 @@ export const appModule2FlowNode = ({
|
||||
}),
|
||||
onChangeNode,
|
||||
onDelNode,
|
||||
onDelEdge
|
||||
onDelEdge,
|
||||
onCopyNode,
|
||||
onCollectionNode
|
||||
};
|
||||
|
||||
return {
|
||||
|
Reference in New Issue
Block a user