mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 05:23:57 +00:00
feat: copy settings config
This commit is contained in:
@@ -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?",
|
||||||
|
@@ -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": "确认删除该数据?",
|
||||||
|
@@ -81,3 +81,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: {
|
||||||
|
sizeLimit: '20mb'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -446,3 +446,11 @@ export function responseStatus({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: {
|
||||||
|
sizeLimit: '20mb'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -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;
|
@@ -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} />}
|
||||||
|
@@ -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} />}
|
||||||
|
@@ -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);
|
||||||
|
Reference in New Issue
Block a user