perf: http body;perf: create by json;perf: create by curl (#3570)
* perf: http body * feat: create app by json (#3557) * feat: create app by json * fix build * perf: create by json;perf: create by curl * fix: ts --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
136
projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx
Normal file
136
projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React, { DragEvent, useCallback, useState } from 'react';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
const ImportAppConfigEditor = ({ value, onChange, rows = 16 }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const { File, onOpen } = useSelectFile({
|
||||
fileType: 'json',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const readJSONFile = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!file.name.endsWith('.json')) {
|
||||
toast({
|
||||
title: t('app:not_json_file'),
|
||||
status: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.target) {
|
||||
const res = JSON.parse(e.target.result as string);
|
||||
onChange(JSON.stringify(res, null, 2));
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[onChange, t, toast]
|
||||
);
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
readJSONFile(file);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
console.log(file);
|
||||
readJSONFile(file);
|
||||
setIsDragging(false);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box w={['100%', '31rem']}>
|
||||
{isDragging ? (
|
||||
<Flex
|
||||
align={'center'}
|
||||
justify={'center'}
|
||||
w={'full'}
|
||||
h={'17.5rem'}
|
||||
borderRadius={'md'}
|
||||
border={'1px dashed'}
|
||||
borderColor={'myGray.400'}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Flex align={'center'} justify={'center'} flexDir={'column'} gap={'0.62rem'}>
|
||||
<MyIcon name={'configmap'} w={'2rem'} color={'primary.500'} />
|
||||
<Box color={'primary.600'} fontSize={'sm'}>
|
||||
{t('app:file_recover')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Box>
|
||||
<Flex justify={'space-between'} align={'center'} pb={3}>
|
||||
<Box fontSize={'sm'} color={'myGray.900'} fontWeight={'500'}>
|
||||
{t('common:common.json_config')}
|
||||
</Box>
|
||||
<Button onClick={onOpen} variant={'whiteBase'} p={0}>
|
||||
<Flex px={'0.88rem'} py={'0.44rem'} color={'myGray.600'} fontSize={'mini'}>
|
||||
<MyIcon name={'file/uploadFile'} w={'1rem'} mr={'0.38rem'} />
|
||||
{t('common:common.upload_file')}
|
||||
</Flex>
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
border={'1px solid'}
|
||||
borderRadius={'md'}
|
||||
borderColor={'myGray.200'}
|
||||
value={value}
|
||||
placeholder={t('app:or_drag_JSON')}
|
||||
rows={rows}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{File && <File onSelect={onSelectFile} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ImportAppConfigEditor);
|
||||
23
projects/app/src/pageComponents/app/constants.ts
Normal file
23
projects/app/src/pageComponents/app/constants.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
export const appTypeMap = {
|
||||
[AppTypeEnum.simple]: {
|
||||
icon: 'core/app/simpleBot',
|
||||
title: i18nT('app:type.Create simple bot'),
|
||||
avatar: 'core/app/type/simpleFill',
|
||||
emptyCreateText: i18nT('app:create_empty_app')
|
||||
},
|
||||
[AppTypeEnum.workflow]: {
|
||||
icon: 'core/app/type/workflowFill',
|
||||
avatar: 'core/app/type/workflowFill',
|
||||
title: i18nT('app:type.Create workflow bot'),
|
||||
emptyCreateText: i18nT('app:create_empty_workflow')
|
||||
},
|
||||
[AppTypeEnum.plugin]: {
|
||||
icon: 'core/app/type/pluginFill',
|
||||
avatar: 'core/app/type/pluginFill',
|
||||
title: i18nT('app:type.Create plugin bot'),
|
||||
emptyCreateText: i18nT('app:create_empty_plugin')
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { filterSensitiveNodesData } from '@/web/core/workflow/utils';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { AppChatConfigType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { filterSensitiveFormData } from '@/web/core/app/utils';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
|
||||
const ExportConfigPopover = ({
|
||||
appForm,
|
||||
getWorkflowData,
|
||||
|
||||
chatConfig,
|
||||
appName
|
||||
}: {
|
||||
appName: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
} & RequireOnlyOne<{
|
||||
getWorkflowData: () =>
|
||||
| {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
appForm: AppSimpleEditFormType;
|
||||
}>) => {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const onExportWorkflow = useCallback(
|
||||
async (mode: 'copy' | 'json') => {
|
||||
let config = '';
|
||||
|
||||
if (appForm) {
|
||||
config = JSON.stringify(filterSensitiveFormData(appForm), null, 2);
|
||||
} else if (getWorkflowData) {
|
||||
const workflowData = getWorkflowData();
|
||||
if (!workflowData) return;
|
||||
config = JSON.stringify(
|
||||
{
|
||||
nodes: filterSensitiveNodesData(workflowData.nodes),
|
||||
edges: workflowData.edges,
|
||||
chatConfig
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'copy') {
|
||||
copyData(config, t('app:export_config_successful'));
|
||||
} else if (mode === 'json') {
|
||||
fileDownload({
|
||||
text: config,
|
||||
type: 'application/json;charset=utf-8',
|
||||
filename: `${appName}.json`
|
||||
});
|
||||
}
|
||||
},
|
||||
[appForm, appName, chatConfig, copyData, getWorkflowData, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyPopover
|
||||
placement={'right-start'}
|
||||
offset={[0, 20]}
|
||||
hasArrow
|
||||
trigger={'hover'}
|
||||
w={'8.6rem'}
|
||||
Trigger={
|
||||
<MyBox display={'flex'} cursor={'pointer'}>
|
||||
<MyIcon name={'export'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>
|
||||
</MyBox>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box p={1}>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('copy')}
|
||||
>
|
||||
<MyIcon name={'copy'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.copy_to_clipboard')}</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('json')}
|
||||
>
|
||||
<MyIcon name={'configmap'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.export_to_json')}</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExportConfigPopover);
|
||||
Reference in New Issue
Block a user