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:
Archer
2025-01-12 22:49:03 +08:00
committed by GitHub
parent f1f0ae2839
commit d0d1a2cae8
34 changed files with 1280 additions and 520 deletions

View 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);

View 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')
}
};

View File

@@ -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);