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!", "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 is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different", "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", "Input Field Settings": "Input Field Settings",
"My Apps": "My Apps", "My Apps": "My Apps",
"Output Field Settings": "Output Field Settings" "Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config"
}, },
"chat": { "chat": {
"Complete Response": "Complete Response", "Complete Response": "Complete Response",
@@ -31,12 +37,14 @@
"Cancel": "Cancel", "Cancel": "Cancel",
"Collect": "Collect", "Collect": "Collect",
"Copy": "Copy", "Copy": "Copy",
"Copy Successful": "Copy Successful",
"Course": "", "Course": "",
"Delete": "Delete", "Delete": "Delete",
"Filed is repeat": "Filed is repeated", "Filed is repeat": "Filed is repeated",
"Filed is repeated": "", "Filed is repeated": "",
"Input": "Input", "Input": "Input",
"Output": "Output" "Output": "Output",
"export": ""
}, },
"dataset": { "dataset": {
"Confirm to delete the data": "Confirm to delete the data?", "Confirm to delete the data": "Confirm to delete the data?",

View File

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

View File

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

View File

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