feat: copy settings config

This commit is contained in:
archer
2023-08-16 18:21:55 +08:00
parent a149b3a2ce
commit 2c174aa91c
8 changed files with 173 additions and 69 deletions

View File

@@ -11,9 +11,15 @@
"Confirm Save App Tip": "The application may be in advanced orchestration mode, and the advanced orchestration configuration will be overwritten after saving, please confirm!",
"Connection is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different",
"Export Config Successful": "The configuration has been copied. Please check for important data",
"Export Configs": "Export Configs",
"Import Config": "Import Config",
"Import Config Failed": "Failed to import the configuration, please ensure that the configuration is normal!",
"Import Configs": "Import Configs",
"Input Field Settings": "Input Field Settings",
"My Apps": "My Apps",
"Output Field Settings": "Output Field Settings"
"Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config"
},
"chat": {
"Complete Response": "Complete Response",
@@ -31,12 +37,14 @@
"Cancel": "Cancel",
"Collect": "Collect",
"Copy": "Copy",
"Copy Successful": "Copy Successful",
"Course": "",
"Delete": "Delete",
"Filed is repeat": "Filed is repeated",
"Filed is repeated": "",
"Input": "Input",
"Output": "Output"
"Output": "Output",
"export": ""
},
"dataset": {
"Confirm to delete the data": "Confirm to delete the data?",

View File

@@ -11,9 +11,15 @@
"Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"Export Config Successful": "已复制配置,请注意检查是否有重要数据",
"Export Configs": "导出配置",
"Import Config": "导入配置",
"Import Config Failed": "导入配置失败,请确保配置正常!",
"Import Configs": "导入配置",
"Input Field Settings": "输入字段编辑",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑"
"Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置"
},
"chat": {
"Complete Response": "完整响应",
@@ -31,12 +37,14 @@
"Cancel": "取消",
"Collect": "收藏",
"Copy": "复制",
"Copy Successful": "复制成功",
"Course": "",
"Delete": "删除",
"Filed is repeat": "",
"Filed is repeated": "字段重复了",
"Input": "输入",
"Output": "输出"
"Output": "输出",
"export": ""
},
"dataset": {
"Confirm to delete the data": "确认删除该数据?",

View File

@@ -81,3 +81,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res.end();
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '20mb'
}
}
};

View File

@@ -446,3 +446,11 @@ export function responseStatus({
})
});
}
export const config = {
api: {
bodyParser: {
sizeLimit: '20mb'
}
}
};

View File

@@ -0,0 +1,54 @@
import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import { AppModuleItemType } from '@/types/app';
import { useTranslation } from 'react-i18next';
import { useToast } from '@/hooks/useToast';
const ImportSettings = ({
onClose,
onSuccess
}: {
onClose: () => void;
onSuccess: (modules: AppModuleItemType[]) => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const [value, setValue] = useState('');
return (
<MyModal isOpen w={'600px'} onClose={onClose} title={t('app.Import Config')}>
<ModalBody>
<Textarea
placeholder={t('app.Paste Config') || 'app.Paste Config'}
defaultValue={value}
rows={16}
onChange={(e) => setValue(e.target.value)}
/>
</ModalBody>
<ModalFooter>
<Button
variant="base"
onClick={() => {
if (!value) {
return onClose();
}
try {
const data = JSON.parse(value);
onSuccess(data);
onClose();
} catch (error) {
toast({
title: t('app.Import Config Failed')
});
}
}}
>
</Button>
</ModalFooter>
</MyModal>
);
};
export default ImportSettings;

View File

@@ -10,7 +10,7 @@ import ReactFlow, {
Connection,
useViewport
} from 'reactflow';
import { Box, Flex, IconButton, useTheme, useDisclosure, position } from '@chakra-ui/react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import {
edgeOptions,
@@ -33,6 +33,7 @@ import type { AppSchema } from '@/types/mongoSchema';
import { useUserStore } from '@/store/user';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { useCopyData } from '@/utils/tools';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
@@ -41,6 +42,9 @@ import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
const ImportSettings = dynamic(() => import('./components/ImportSettings'), {
ssr: false
});
const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'), {
ssr: false
});
@@ -98,14 +102,17 @@ const nodeTypes = {
const edgeTypes = {
buttonedge: ButtonEdge
};
type Props = { app: AppSchema; fullScreen: boolean; onFullScreen: (val: boolean) => void };
type Props = { app: AppSchema; onCloseSettings: () => void };
const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const AppEdit = ({ app, onCloseSettings }: Props) => {
const theme = useTheme();
const { toast } = useToast();
const { t } = useTranslation();
const { copyData } = useCopyData();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const { updateAppDetail } = useUserStore();
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
@@ -115,6 +122,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
@@ -398,15 +406,15 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
});
const initData = useCallback(
(app: AppSchema) => {
(modules: AppModuleItemType[]) => {
const edges = appModule2FlowEdge({
modules: app.modules,
modules,
onDelete: onDelConnect
});
setEdges(edges);
setNodes(
app.modules.map((item) =>
modules.map((item) =>
appModule2FlowNode({
item,
onChangeNode,
@@ -434,8 +442,8 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
);
useEffect(() => {
initData(JSON.parse(JSON.stringify(app)));
}, [app]);
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
return (
<>
@@ -447,49 +455,53 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
alignItems={'center'}
userSelect={'none'}
>
{fullScreen ? (
<>
<MyTooltip label={'返回'} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'back'} w={'14px'} />}
borderRadius={'md'}
borderColor={'myGray.300'}
variant={'base'}
aria-label={''}
onClick={() => {
onFullScreen(false);
onFixView();
}}
/>
</MyTooltip>
<Box ml={5} fontSize={['lg', '2xl']} flex={1}>
{app.name}
</Box>
</>
) : (
<>
<Box fontSize={['lg', '2xl']} flex={1}>
</Box>
<MyTooltip label={'全屏'}>
<IconButton
mr={6}
icon={<MyIcon name={'fullScreenLight'} w={['14px', '16px']} />}
borderRadius={'lg'}
variant={'base'}
aria-label={'fullScreenLight'}
onClick={() => {
onFullScreen(true);
onFixView();
}}
/>
</MyTooltip>
</>
)}
<MyTooltip label={'返回'} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'back'} w={'14px'} />}
borderRadius={'md'}
borderColor={'myGray.300'}
variant={'base'}
aria-label={''}
onClick={() => {
onCloseSettings();
onFixView();
}}
/>
</MyTooltip>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
</Box>
<MyTooltip label={t('app.Import Configs')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'importLight'} w={['14px', '16px']} />}
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={onOpenImport}
/>
</MyTooltip>
<MyTooltip label={t('app.Export Configs')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'export'} w={['14px', '16px']} />}
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={() =>
copyData(
JSON.stringify(flow2AppModules(), null, 2),
t('app.Export Config Successful')
)
}
/>
</MyTooltip>
{testModules ? (
<IconButton
mr={6}
mr={[3, 6]}
icon={<SmallCloseIcon fontSize={'25px'} />}
variant={'base'}
color={'myGray.600'}
@@ -500,7 +512,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
) : (
<MyTooltip label={'测试对话'}>
<IconButton
mr={6}
mr={[3, 6]}
icon={<MyIcon name={'chat'} w={['14px', '16px']} />}
borderRadius={'lg'}
aria-label={'save'}
@@ -591,20 +603,24 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
onClose={() => setTestModules(undefined)}
/>
</Box>
{isOpenImport && (
<ImportSettings
onClose={onCloseImport}
onSuccess={(data) => {
setEdges([]);
setNodes([]);
setTimeout(() => {
initData(data);
}, 10);
}}
/>
)}
</>
);
};
const Flow = (data: Props) => (
<Box
h={'100%'}
position={data.fullScreen ? 'fixed' : 'relative'}
zIndex={999}
top={0}
left={0}
right={0}
bottom={0}
>
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<ReactFlowProvider>
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{!!data.app._id && <AppEdit {...data} />}

View File

@@ -171,11 +171,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
{currentTab === TabEnum.basicEdit && <BasicEdit appId={appId} />}
{currentTab === TabEnum.adEdit && appDetail && (
<AdEdit
app={appDetail}
fullScreen={true}
onFullScreen={() => setCurrentTab(TabEnum.basicEdit)}
/>
<AdEdit app={appDetail} onCloseSettings={() => setCurrentTab(TabEnum.basicEdit)} />
)}
{currentTab === TabEnum.API && <API appId={appId} />}
{currentTab === TabEnum.outLink && <OutLink appId={appId} />}

View File

@@ -1,15 +1,21 @@
import crypto from 'crypto';
import { useToast } from '@/hooks/useToast';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
/**
* copy text data
*/
export const useCopyData = () => {
const { t } = useTranslation();
const { toast } = useToast();
return {
copyData: async (data: string, title: string = '复制成功', duration = 1000) => {
copyData: async (
data: string,
title: string | null = t('common.Copy Successful'),
duration = 1000
) => {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(data);