Commit 6bb10ca1 by heheer Committed by GitHub

perf: optimize simple app history (#2782)

* simple app history

* ui

* extract context content into hooks
parent 7aa75f8e
...@@ -40,6 +40,7 @@ export const iconPaths = { ...@@ -40,6 +40,7 @@ export const iconPaths = {
'common/language/en': () => import('./icons/common/language/en.svg'), 'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'), 'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'), 'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
'common/line': () => import('./icons/common/line.svg'),
'common/lineChange': () => import('./icons/common/lineChange.svg'), 'common/lineChange': () => import('./icons/common/lineChange.svg'),
'common/linkBlue': () => import('./icons/common/linkBlue.svg'), 'common/linkBlue': () => import('./icons/common/linkBlue.svg'),
'common/list': () => import('./icons/common/list.svg'), 'common/list': () => import('./icons/common/list.svg'),
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 12" fill="none">
<path d="M4.42017 1.30151L0.999965 10.6984" stroke="#DFE2EA" stroke-linecap="round"/>
</svg>
\ No newline at end of file
...@@ -16,6 +16,8 @@ const CustomRightDrawer = ({ ...@@ -16,6 +16,8 @@ const CustomRightDrawer = ({
iconSrc, iconSrc,
title, title,
maxW = ['90vw', '30vw'], maxW = ['90vw', '30vw'],
top = 16,
bottom = 0,
children, children,
isLoading, isLoading,
showMask = true, showMask = true,
...@@ -31,8 +33,8 @@ const CustomRightDrawer = ({ ...@@ -31,8 +33,8 @@ const CustomRightDrawer = ({
zIndex={100} zIndex={100}
maxW={maxW} maxW={maxW}
w={'100%'} w={'100%'}
top={'60px'} top={top}
bottom={0} bottom={bottom}
borderLeftRadius={'lg'} borderLeftRadius={'lg'}
border={'base'} border={'base'}
boxShadow={'2'} boxShadow={'2'}
......
...@@ -2,6 +2,7 @@ import { Box, BoxProps, Flex } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Box, BoxProps, Flex } from '@chakra-ui/react';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
const FolderPath = (props: { const FolderPath = (props: {
paths: ParentTreePathItemType[]; paths: ParentTreePathItemType[];
...@@ -35,7 +36,7 @@ const FolderPath = (props: { ...@@ -35,7 +36,7 @@ const FolderPath = (props: {
return paths.length === 0 && !!FirstPathDom ? ( return paths.length === 0 && !!FirstPathDom ? (
<>{FirstPathDom}</> <>{FirstPathDom}</>
) : ( ) : (
<Flex flex={1} ml={-1.5}> <Flex flex={1}>
{concatPaths.map((item, i) => ( {concatPaths.map((item, i) => (
<Flex key={item.parentId || i} alignItems={'center'}> <Flex key={item.parentId || i} alignItems={'center'}>
<Box <Box
...@@ -51,6 +52,7 @@ const FolderPath = (props: { ...@@ -51,6 +52,7 @@ const FolderPath = (props: {
} }
: { : {
cursor: 'pointer', cursor: 'pointer',
fontWeight: 'medium',
color: 'myGray.500', color: 'myGray.500',
_hover: { _hover: {
bg: 'myGray.100', bg: 'myGray.100',
...@@ -64,9 +66,7 @@ const FolderPath = (props: { ...@@ -64,9 +66,7 @@ const FolderPath = (props: {
{item.parentName} {item.parentName}
</Box> </Box>
{i !== concatPaths.length - 1 && ( {i !== concatPaths.length - 1 && (
<Box mx={1.5} color={'myGray.500'}> <MyIcon name={'common/line'} color={'myGray.500'} mx={1} width={'5px'} />
/
</Box>
)} )}
</Flex> </Flex>
))} ))}
......
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { import {
Box, Box,
Flex, Flex,
...@@ -22,18 +22,16 @@ import { useRouter } from 'next/router'; ...@@ -22,18 +22,16 @@ import { useRouter } from 'next/router';
import AppCard from '../WorkflowComponents/AppCard'; import AppCard from '../WorkflowComponents/AppCard';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyPopover from '@fastgpt/web/components/common/MyPopover';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { compareSnapshot } from '@/web/core/workflow/utils'; import { compareSnapshot } from '@/web/core/workflow/utils';
import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useDebounceEffect } from 'ahooks'; import { useDebounceEffect } from 'ahooks';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from '../Workflow/components/SaveButton';
const PublishHistories = dynamic(() => import('../WorkflowPublishHistoriesSlider')); const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
const Header = () => { const Header = () => {
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -48,12 +46,6 @@ const Header = () => { ...@@ -48,12 +46,6 @@ const Header = () => {
onOpen: onOpenBackConfirm, onOpen: onOpenBackConfirm,
onClose: onCloseBackConfirm onClose: onCloseBackConfirm
} = useDisclosure(); } = useDisclosure();
const {
isOpen: isSaveAndPublishModalOpen,
onOpen: onSaveAndPublishModalOpen,
onClose: onSaveAndPublishModalClose
} = useDisclosure();
const [isSave, setIsSave] = useState(false);
const { const {
flowData2StoreData, flowData2StoreData,
...@@ -65,7 +57,9 @@ const Header = () => { ...@@ -65,7 +57,9 @@ const Header = () => {
edges, edges,
past, past,
future, future,
setPast setPast,
saveSnapshot,
resetSnapshot
} = useContextSelector(WorkflowContext, (v) => v); } = useContextSelector(WorkflowContext, (v) => v);
const { lastAppListRouteType } = useSystemStore(); const { lastAppListRouteType } = useSystemStore();
...@@ -225,82 +219,12 @@ const Header = () => { ...@@ -225,82 +219,12 @@ const Header = () => {
{t('common:core.workflow.Run')} {t('common:core.workflow.Run')}
</Button> </Button>
{!historiesDefaultData && ( {!historiesDefaultData && (
<MyPopover <SaveButton
placement={'bottom-end'}
hasArrow={false}
offset={[2, 4]}
w={'116px'}
onOpenFunc={() => setIsSave(true)}
onCloseFunc={() => setIsSave(false)}
trigger={'hover'}
Trigger={
<Button
size={'sm'}
rightIcon={
<MyIcon
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
w={['14px', '16px']}
/>
}
>
<Box>{t('common:common.Save')}</Box>
</Button>
}
>
{({ onClose }) => (
<MyBox p={1.5}>
<MyBox
display={'flex'}
size={'md'}
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
isLoading={loading} isLoading={loading}
onClick={async () => {
await onClickSave({});
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/upload'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save to cloud')}</Box>
</MyBox>
<Flex
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
onClick={() => {
const data = flowData2StoreDataAndCheck();
if (data) {
onSaveAndPublishModalOpen();
}
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/publish'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save and publish')}</Box>
{isSaveAndPublishModalOpen && (
<SaveAndPublishModal
isLoading={loading}
onClose={onSaveAndPublishModalClose}
onClickSave={onClickSave} onClickSave={onClickSave}
checkData={flowData2StoreDataAndCheck}
/> />
)} )}
</Flex>
</MyBox>
)}
</MyPopover>
)}
</HStack> </HStack>
)} )}
</Flex> </Flex>
...@@ -309,6 +233,9 @@ const Header = () => { ...@@ -309,6 +233,9 @@ const Header = () => {
onClose={() => { onClose={() => {
setHistoriesDefaultData(undefined); setHistoriesDefaultData(undefined);
}} }}
past={past}
saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
/> />
)} )}
<MyModal <MyModal
...@@ -356,16 +283,12 @@ const Header = () => { ...@@ -356,16 +283,12 @@ const Header = () => {
loading, loading,
isV2Workflow, isV2Workflow,
historiesDefaultData, historiesDefaultData,
isSave,
onClickSave, onClickSave,
setHistoriesDefaultData, setHistoriesDefaultData,
appDetail.chatConfig, appDetail.chatConfig,
flowData2StoreDataAndCheck, flowData2StoreDataAndCheck,
setWorkflowTestData, setWorkflowTestData,
isSaveAndPublishModalOpen, toast
onSaveAndPublishModalClose,
toast,
onSaveAndPublishModalOpen
]); ]);
return Render; return Render;
......
...@@ -3,13 +3,11 @@ import React, { useCallback, useMemo } from 'react'; ...@@ -3,13 +3,11 @@ import React, { useCallback, useMemo } from 'react';
import { AppContext, TabEnum } from './context'; import { AppContext, TabEnum } from './context';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useI18n } from '@/web/context/I18n';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
const RouteTab = () => { const RouteTab = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const router = useRouter(); const router = useRouter();
const { appDetail, currentTab } = useContextSelector(AppContext, (v) => v); const { appDetail, currentTab } = useContextSelector(AppContext, (v) => v);
...@@ -28,20 +26,21 @@ const RouteTab = () => { ...@@ -28,20 +26,21 @@ const RouteTab = () => {
const tabList = useMemo( const tabList = useMemo(
() => [ () => [
{ {
label: appDetail.type === AppTypeEnum.plugin ? appT('setting_plugin') : appT('setting_app'), label:
appDetail.type === AppTypeEnum.plugin ? t('app:setting_plugin') : t('app:setting_app'),
id: TabEnum.appEdit id: TabEnum.appEdit
}, },
...(appDetail.permission.hasManagePer ...(appDetail.permission.hasManagePer
? [ ? [
{ {
label: appT('publish_channel'), label: t('app:publish_channel'),
id: TabEnum.publish id: TabEnum.publish
}, },
{ label: appT('chat_logs'), id: TabEnum.logs } { label: t('app:chat_logs'), id: TabEnum.logs }
] ]
: []) : [])
], ],
[appDetail.permission.hasManagePer, appDetail.type, appT] [appDetail.permission.hasManagePer, appDetail.type]
); );
return ( return (
...@@ -51,6 +50,7 @@ const RouteTab = () => { ...@@ -51,6 +50,7 @@ const RouteTab = () => {
key={tab.id} key={tab.id}
px={2} px={2}
py={0.5} py={0.5}
fontWeight={'medium'}
{...(currentTab === tab.id {...(currentTab === tab.id
? { ? {
color: 'primary.700' color: 'primary.700'
......
...@@ -15,21 +15,46 @@ import { cardStyles } from '../constants'; ...@@ -15,21 +15,46 @@ import { cardStyles } from '../constants';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { SnapshotsType } from '../WorkflowComponents/context';
const Edit = ({ const Edit = ({
appForm, appForm,
setAppForm setAppForm,
past,
saveSnapshot
}: { }: {
appForm: AppSimpleEditFormType; appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>; setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
past: SnapshotsType[];
saveSnapshot: (
this: any,
{ pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any
) => Promise<boolean>;
}) => { }) => {
const { isPc } = useSystem(); const { isPc } = useSystem();
const { loadAllDatasets } = useDatasetStore(); const { loadAllDatasets } = useDatasetStore();
const { appDetail } = useContextSelector(AppContext, (v) => v); const { appDetail } = useContextSelector(AppContext, (v) => v);
const { t } = useTranslation();
// show selected dataset // show selected dataset
useMount(() => { useMount(() => {
loadAllDatasets(); loadAllDatasets();
saveSnapshot({
pastNodes: appDetail.modules?.map((item) => storeNode2FlowNode({ item, t })),
pastEdges: appDetail.edges?.map((item) => storeEdgesRenderEdge({ edge: item })),
chatConfig: appDetail.chatConfig,
isSaved: true
});
if (past.length > 0) {
const storeWorkflow = uiWorkflow2StoreWorkflow(past[0]);
const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: past[0].chatConfig });
setAppForm(currentAppForm);
return;
}
setAppForm( setAppForm(
appWorkflow2Form({ appWorkflow2Form({
nodes: appDetail.modules, nodes: appDetail.modules,
...@@ -52,7 +77,7 @@ const Edit = ({ ...@@ -52,7 +77,7 @@ const Edit = ({
display={['block', 'flex']} display={['block', 'flex']}
flex={'1 0 0'} flex={'1 0 0'}
h={0} h={0}
pt={[2, 1.5]} mt={[4, 0]}
gap={1} gap={1}
borderRadius={'lg'} borderRadius={'lg'}
overflowY={['auto', 'unset']} overflowY={['auto', 'unset']}
...@@ -73,7 +98,7 @@ const Edit = ({ ...@@ -73,7 +98,7 @@ const Edit = ({
</Box> </Box>
</Box> </Box>
{isPc && ( {isPc && (
<Box {...cardStyles} boxShadow={'3'} flex={'2 0 0'} w={0}> <Box {...cardStyles} boxShadow={'3'} flex={'2 0 0'} w={0} mb={3}>
<ChatTest appForm={appForm} /> <ChatTest appForm={appForm} />
</Box> </Box>
)} )}
......
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context'; import { AppContext } from '../context';
import FolderPath from '@/components/common/folder/Path'; import FolderPath from '@/components/common/folder/Path';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getAppFolderPath } from '@/web/core/app/api/app'; import { getAppFolderPath } from '@/web/core/app/api/app';
import { Box, Button, Flex, IconButton } from '@chakra-ui/react'; import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import RouteTab from '../RouteTab'; import RouteTab from '../RouteTab';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { form2AppWorkflow } from '@/web/core/app/utils'; import { form2AppWorkflow } from '@/web/core/app/utils';
import { TabEnum } from '../context'; import { TabEnum } from '../context';
import PublishHistoriesSlider, { type InitProps } from '../PublishHistoriesSlider';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { compareWorkflow } from '@/web/core/workflow/utils';
import MyTag from '@fastgpt/web/components/common/Tag/index'; import MyTag from '@fastgpt/web/components/common/Tag/index';
import { publishStatusStyle } from '../constants'; import { publishStatusStyle } from '../constants';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import SaveButton from '../Workflow/components/SaveButton';
import dynamic from 'next/dynamic';
import { useDebounceEffect } from 'ahooks';
import { InitProps, SnapshotsType } from '../WorkflowComponents/context';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import {
compareSnapshot,
storeEdgesRenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
const Header = ({ const Header = ({
appForm, appForm,
setAppForm setAppForm,
past,
setPast,
saveSnapshot
}: { }: {
appForm: AppSimpleEditFormType; appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>; setAppForm: (form: AppSimpleEditFormType) => void;
past: SnapshotsType[];
setPast: (value: React.SetStateAction<SnapshotsType[]>) => void;
saveSnapshot: (
this: any,
{ pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any
) => Promise<boolean>;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isPc } = useSystem(); const { isPc } = useSystem();
const router = useRouter(); const router = useRouter();
const { toast } = useToast();
const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v); const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v);
const { lastAppListRouteType } = useSystemStore(); const { lastAppListRouteType } = useSystemStore();
const { allDatasets } = useDatasetStore(); const { allDatasets } = useDatasetStore();
...@@ -45,8 +60,10 @@ const Header = ({ ...@@ -45,8 +60,10 @@ const Header = ({
manual: false, manual: false,
refreshDeps: [appId] refreshDeps: [appId]
}); });
const onclickRoute = useCallback( const onClickRoute = useCallback(
(parentId: string) => { (parentId: string) => {
localStorage.removeItem(`${appDetail._id}-past`);
router.push({ router.push({
pathname: '/app/list', pathname: '/app/list',
query: { query: {
...@@ -58,58 +75,98 @@ const Header = ({ ...@@ -58,58 +75,98 @@ const Header = ({
[router, lastAppListRouteType] [router, lastAppListRouteType]
); );
const isPublished = useMemo(() => { const [isPublished, setIsPublished] = useState(false);
const { runAsync: onClickSave, loading } = useRequest2(
async ({
isPublish,
versionName = formatTime2YMDHMS(new Date())
}: {
isPublish?: boolean;
versionName?: string;
}) => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
await onSaveApp({
nodes,
edges,
chatConfig: appForm.chatConfig,
type: AppTypeEnum.simple,
isPublish,
versionName
});
setPast((prevPast) =>
prevPast.map((item, index) =>
index === 0
? {
...item,
isSaved: true
}
: item
)
);
}
);
const [historiesDefaultData, setHistoriesDefaultData] = useState<InitProps>();
const resetSnapshot = (data: SnapshotsType) => {
const storeWorkflow = uiWorkflow2StoreWorkflow(data);
const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: data.chatConfig });
setAppForm(currentAppForm);
};
useDebounceEffect(
() => {
const data = form2AppWorkflow(appForm, t); const data = form2AppWorkflow(appForm, t);
return compareWorkflow(
saveSnapshot({
pastNodes: data.nodes?.map((item) => storeNode2FlowNode({ item, t })),
pastEdges: data.edges?.map((item) => storeEdgesRenderEdge({ edge: item })),
chatConfig: data.chatConfig
});
},
[appForm],
{ wait: 500 }
);
useDebounceEffect(
() => {
const savedSnapshot = past.find((snapshot) => snapshot.isSaved);
const data = form2AppWorkflow(appForm, t);
const val = compareSnapshot(
{ {
nodes: appDetail.modules, nodes: savedSnapshot?.nodes,
edges: [], edges: [],
chatConfig: appDetail.chatConfig chatConfig: savedSnapshot?.chatConfig
}, },
{ {
nodes: data.nodes, nodes: data.nodes?.map((item) => storeNode2FlowNode({ item, t })),
edges: [], edges: [],
chatConfig: data.chatConfig chatConfig: data.chatConfig
} }
); );
}, [appDetail.chatConfig, appDetail.modules, appForm, allDatasets, t]); setIsPublished(val);
const onSubmitPublish = useCallback(
async (data: AppSimpleEditFormType) => {
const { nodes, edges } = form2AppWorkflow(data, t);
await onSaveApp({
nodes,
edges,
chatConfig: data.chatConfig,
type: AppTypeEnum.simple,
isPublish: true,
versionName: formatTime2YMDHMS(new Date())
});
toast({
status: 'success',
title: t('app:publish_success'),
position: 'top-right'
});
}, },
[onSaveApp, t, toast] [past],
{ wait: 500 }
); );
const [historiesDefaultData, setHistoriesDefaultData] = useState<InitProps>();
return ( return (
<Box> <Box h={14}>
{!isPc && ( {!isPc && (
<Flex pt={2} justifyContent={'center'}> <Flex justifyContent={'center'}>
<RouteTab /> <RouteTab />
</Flex> </Flex>
)} )}
<Flex pt={[2, 3]} alignItems={'flex-start'} position={'relative'}> <Flex w={'full'} alignItems={'center'} position={'relative'} h={'full'}>
<Box flex={'1'}> <Box flex={'1'}>
<FolderPath <FolderPath
rootName={t('app:all_apps')} rootName={t('app:all_apps')}
paths={paths} paths={paths}
hoverStyle={{ color: 'primary.600' }} hoverStyle={{ color: 'primary.600' }}
onClick={onclickRoute} onClick={onClickRoute}
fontSize={'14px'}
/> />
</Box> </Box>
{isPc && ( {isPc && (
...@@ -156,36 +213,23 @@ const Header = ({ ...@@ -156,36 +213,23 @@ const Header = ({
}); });
}} }}
/> />
<PopoverConfirm <SaveButton isLoading={loading} onClickSave={onClickSave} />
showCancel
content={t('common:core.app.Publish Confirm')}
Trigger={
<Box>
<MyTooltip label={t('common:core.app.Publish app tip')}>
<Button isDisabled={isPublished}>{t('common:core.app.Publish')}</Button>
</MyTooltip>
</Box>
}
onConfirm={() => onSubmitPublish(appForm)}
/>
</> </>
)} )}
</Flex> </Flex>
)} )}
</Flex> </Flex>
{!!historiesDefaultData && ( {historiesDefaultData && currentTab === TabEnum.appEdit && (
<PublishHistoriesSlider <PublishHistories
initData={({ nodes, chatConfig }) => { onClose={() => {
setAppForm( setHistoriesDefaultData(undefined);
appWorkflow2Form({
nodes,
chatConfig
})
);
}} }}
onClose={() => setHistoriesDefaultData(undefined)} past={past}
defaultData={historiesDefaultData} saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
top={14}
bottom={3}
/> />
)} )}
</Box> </Box>
......
...@@ -9,13 +9,15 @@ import dynamic from 'next/dynamic'; ...@@ -9,13 +9,15 @@ import dynamic from 'next/dynamic';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import useSnapshots from './useSnapshots';
const Logs = dynamic(() => import('../Logs/index')); const Logs = dynamic(() => import('../Logs/index'));
const PublishChannel = dynamic(() => import('../Publish')); const PublishChannel = dynamic(() => import('../Publish'));
const SimpleEdit = () => { const SimpleEdit = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentTab } = useContextSelector(AppContext, (v) => v); const { currentTab, appDetail } = useContextSelector(AppContext, (v) => v);
const { past, setPast, saveSnapshot } = useSnapshots(appDetail._id);
const [appForm, setAppForm] = useState(getDefaultAppForm()); const [appForm, setAppForm] = useState(getDefaultAppForm());
...@@ -24,12 +26,18 @@ const SimpleEdit = () => { ...@@ -24,12 +26,18 @@ const SimpleEdit = () => {
}); });
return ( return (
<Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]} pb={3}> <Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}>
<Header appForm={appForm} setAppForm={setAppForm} /> <Header
appForm={appForm}
setAppForm={setAppForm}
past={past}
setPast={setPast}
saveSnapshot={saveSnapshot}
/>
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} /> <Edit appForm={appForm} setAppForm={setAppForm} past={past} saveSnapshot={saveSnapshot} />
) : ( ) : (
<Box flex={'1 0 0'} h={0} mt={4}> <Box flex={'1 0 0'} h={0} mt={[4, 0]}>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Box> </Box>
......
import { useLocalStorageState, useMemoizedFn } from 'ahooks';
import { SnapshotsType } from '../WorkflowComponents/context';
import { SetStateAction } from 'react';
import { compareSnapshot } from '@/web/core/workflow/utils';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
const useSnapshots = (appId: string) => {
const [past, setPast] = useLocalStorageState<SnapshotsType[]>(`${appId}-past-simple`, {
defaultValue: [],
listenStorageChange: true
}) as [SnapshotsType[], (value: SetStateAction<SnapshotsType[]>) => void];
const saveSnapshot = useMemoizedFn(
async ({ pastNodes, pastEdges, chatConfig, customTitle, isSaved }) => {
const pastState = past[0];
const isPastEqual = compareSnapshot(
{
nodes: pastNodes,
edges: pastEdges,
chatConfig: chatConfig
},
{
nodes: pastState?.nodes,
edges: pastState?.edges,
chatConfig: pastState?.chatConfig
}
);
if (isPastEqual) return false;
setPast((past) => [
{
nodes: pastNodes,
edges: pastEdges,
title: customTitle || formatTime2YMDHMS(new Date()),
chatConfig,
isSaved
},
...past.slice(0, 199)
]);
return true;
}
);
return { past, setPast, saveSnapshot };
};
export default useSnapshots;
...@@ -22,18 +22,16 @@ import { useRouter } from 'next/router'; ...@@ -22,18 +22,16 @@ import { useRouter } from 'next/router';
import AppCard from '../WorkflowComponents/AppCard'; import AppCard from '../WorkflowComponents/AppCard';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyPopover from '@fastgpt/web/components/common/MyPopover';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { compareSnapshot } from '@/web/core/workflow/utils'; import { compareSnapshot } from '@/web/core/workflow/utils';
import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useDebounceEffect } from 'ahooks'; import { useDebounceEffect } from 'ahooks';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from './components/SaveButton';
const PublishHistories = dynamic(() => import('../WorkflowPublishHistoriesSlider')); const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
const Header = () => { const Header = () => {
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -48,12 +46,6 @@ const Header = () => { ...@@ -48,12 +46,6 @@ const Header = () => {
onOpen: onOpenBackConfirm, onOpen: onOpenBackConfirm,
onClose: onCloseBackConfirm onClose: onCloseBackConfirm
} = useDisclosure(); } = useDisclosure();
const {
isOpen: isSaveAndPublishModalOpen,
onOpen: onSaveAndPublishModalOpen,
onClose: onSaveAndPublishModalClose
} = useDisclosure();
const [isSave, setIsSave] = useState(false);
const { const {
flowData2StoreData, flowData2StoreData,
...@@ -65,7 +57,9 @@ const Header = () => { ...@@ -65,7 +57,9 @@ const Header = () => {
edges, edges,
past, past,
future, future,
setPast setPast,
saveSnapshot,
resetSnapshot
} = useContextSelector(WorkflowContext, (v) => v); } = useContextSelector(WorkflowContext, (v) => v);
const { lastAppListRouteType } = useSystemStore(); const { lastAppListRouteType } = useSystemStore();
...@@ -227,82 +221,12 @@ const Header = () => { ...@@ -227,82 +221,12 @@ const Header = () => {
{t('common:core.workflow.Run')} {t('common:core.workflow.Run')}
</Button> </Button>
{!historiesDefaultData && ( {!historiesDefaultData && (
<MyPopover <SaveButton
placement={'bottom-end'}
hasArrow={false}
offset={[2, 4]}
w={'116px'}
onOpenFunc={() => setIsSave(true)}
onCloseFunc={() => setIsSave(false)}
trigger={'hover'}
Trigger={
<Button
size={'sm'}
rightIcon={
<MyIcon
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
w={['14px', '16px']}
/>
}
>
<Box>{t('common:common.Save')}</Box>
</Button>
}
>
{({ onClose }) => (
<MyBox p={1.5}>
<MyBox
display={'flex'}
size={'md'}
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
isLoading={loading}
onClick={async () => {
await onClickSave({});
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/upload'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save to cloud')}</Box>
</MyBox>
<Flex
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
onClick={() => {
const data = flowData2StoreDataAndCheck();
if (data) {
onSaveAndPublishModalOpen();
}
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/publish'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save and publish')}</Box>
{isSaveAndPublishModalOpen && (
<SaveAndPublishModal
isLoading={loading} isLoading={loading}
onClose={onSaveAndPublishModalClose}
onClickSave={onClickSave} onClickSave={onClickSave}
checkData={flowData2StoreDataAndCheck}
/> />
)} )}
</Flex>
</MyBox>
)}
</MyPopover>
)}
</HStack> </HStack>
)} )}
</Flex> </Flex>
...@@ -311,8 +235,12 @@ const Header = () => { ...@@ -311,8 +235,12 @@ const Header = () => {
onClose={() => { onClose={() => {
setHistoriesDefaultData(undefined); setHistoriesDefaultData(undefined);
}} }}
past={past}
saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
/> />
)} )}
<MyModal <MyModal
isOpen={isOpenBackConfirm} isOpen={isOpenBackConfirm}
onClose={onCloseBackConfirm} onClose={onCloseBackConfirm}
...@@ -358,16 +286,13 @@ const Header = () => { ...@@ -358,16 +286,13 @@ const Header = () => {
loading, loading,
isV2Workflow, isV2Workflow,
historiesDefaultData, historiesDefaultData,
isSave,
onClickSave, onClickSave,
setHistoriesDefaultData, setHistoriesDefaultData,
appDetail.chatConfig, appDetail.chatConfig,
flowData2StoreDataAndCheck, flowData2StoreDataAndCheck,
setWorkflowTestData, setWorkflowTestData,
isSaveAndPublishModalOpen,
onSaveAndPublishModalClose,
toast, toast,
onSaveAndPublishModalOpen past
]); ]);
return Render; return Render;
......
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import MyPopover from '@fastgpt/web/components/common/MyPopover';
import React, { useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SaveAndPublishModal from '../../WorkflowComponents/Flow/components/SaveAndPublish';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
const SaveButton = ({
isLoading,
onClickSave,
checkData
}: {
isLoading: boolean;
onClickSave: (options: { isPublish?: boolean; versionName?: string }) => Promise<void>;
checkData?: (hideTip?: boolean) =>
| {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}
| undefined;
}) => {
const { t } = useTranslation();
const [isSave, setIsSave] = useState(false);
const { toast } = useToast();
const {
isOpen: isSaveAndPublishModalOpen,
onOpen: onSaveAndPublishModalOpen,
onClose: onSaveAndPublishModalClose
} = useDisclosure();
return (
<MyPopover
placement={'bottom-end'}
hasArrow={false}
offset={[2, 4]}
w={'116px'}
onOpenFunc={() => setIsSave(true)}
onCloseFunc={() => setIsSave(false)}
trigger={'hover'}
Trigger={
<Button
size={'sm'}
rightIcon={
<MyIcon
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
w={['14px', '16px']}
/>
}
>
<Box>{t('common:common.Save')}</Box>
</Button>
}
>
{({ onClose }) => (
<Box p={1.5}>
<MyBox
display={'flex'}
size={'md'}
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
isLoading={isLoading}
onClick={async () => {
await onClickSave({});
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/upload'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save to cloud')}</Box>
</MyBox>
<Flex
px={1}
py={1.5}
rounded={'4px'}
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
cursor={'pointer'}
onClick={() => {
const canOpen = !checkData || checkData();
if (canOpen) {
onSaveAndPublishModalOpen();
}
onClose();
setIsSave(false);
}}
>
<MyIcon name={'core/workflow/publish'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('common:core.workflow.Save and publish')}</Box>
{isSaveAndPublishModalOpen && (
<SaveAndPublishModal
isLoading={isLoading}
onClose={onSaveAndPublishModalClose}
onClickSave={onClickSave}
/>
)}
</Flex>
</Box>
)}
</MyPopover>
);
};
export default React.memo(SaveButton);
...@@ -39,7 +39,7 @@ import { defaultRunningStatus } from './constants'; ...@@ -39,7 +39,7 @@ import { defaultRunningStatus } from './constants';
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { AppChatConfigType } from '@fastgpt/global/core/app/type'; import { AppChatConfigType, AppSchema } from '@fastgpt/global/core/app/type';
import { AppContext } from '@/pages/app/detail/components/context'; import { AppContext } from '@/pages/app/detail/components/context';
import ChatTest from './Flow/ChatTest'; import ChatTest from './Flow/ChatTest';
import { useDisclosure } from '@chakra-ui/react'; import { useDisclosure } from '@chakra-ui/react';
...@@ -47,7 +47,6 @@ import { uiWorkflow2StoreWorkflow } from './utils'; ...@@ -47,7 +47,6 @@ import { uiWorkflow2StoreWorkflow } from './utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time'; import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { SetState } from 'ahooks/lib/createUseStorageState'; import { SetState } from 'ahooks/lib/createUseStorageState';
...@@ -60,6 +59,18 @@ export type SnapshotsType = { ...@@ -60,6 +59,18 @@ export type SnapshotsType = {
chatConfig: AppChatConfigType; chatConfig: AppChatConfigType;
isSaved?: boolean; isSaved?: boolean;
}; };
export type SaveSnapshotParams = {
pastNodes?: Node[];
pastEdges?: Edge[];
customTitle?: string;
chatConfig?: AppChatConfigType;
};
export type InitProps = {
nodes: AppSchema['modules'];
edges: AppSchema['edges'];
chatConfig: AppSchema['chatConfig'];
};
type WorkflowContextType = { type WorkflowContextType = {
appId?: string; appId?: string;
basicNodeTemplates: FlowNodeTemplateType[]; basicNodeTemplates: FlowNodeTemplateType[];
......
import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react'; import { Dispatch, ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react';
import { createContext } from 'use-context-selector'; import { createContext } from 'use-context-selector';
import { defaultApp } from '@/web/core/app/constants'; import { defaultApp } from '@/web/core/app/constants';
import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api'; import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api';
...@@ -11,7 +11,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; ...@@ -11,7 +11,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useDisclosure } from '@chakra-ui/react'; import { useDisclosure } from '@chakra-ui/react';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useI18n } from '@/web/context/I18n';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
......
...@@ -15,8 +15,8 @@ export const getAppLatestVersion = (data: getLatestVersionQuery) => ...@@ -15,8 +15,8 @@ export const getAppLatestVersion = (data: getLatestVersionQuery) =>
export const postPublishApp = (appId: string, data: PostPublishAppProps) => export const postPublishApp = (appId: string, data: PostPublishAppProps) =>
POST(`/core/app/version/publish?appId=${appId}`, data); POST(`/core/app/version/publish?appId=${appId}`, data);
export const getPublishList = (data: PaginationProps<{ appId: string }>) => // export const getPublishList = (data: PaginationProps<{ appId: string }>) =>
POST<PaginationResponse<AppVersionSchemaType>>('/core/app/version/list', data); // POST<PaginationResponse<AppVersionSchemaType>>('/core/app/version/list', data);
export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) => export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) =>
POST<PaginationResponse<versionListResponse>>('/core/app/version/listWorkflow', data); POST<PaginationResponse<versionListResponse>>('/core/app/version/listWorkflow', data);
...@@ -24,8 +24,8 @@ export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) ...@@ -24,8 +24,8 @@ export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>)
export const getAppVersionDetail = (versionId: string, appId: string) => export const getAppVersionDetail = (versionId: string, appId: string) =>
GET<AppVersionSchemaType>(`/core/app/version/detail?versionId=${versionId}&appId=${appId}`); GET<AppVersionSchemaType>(`/core/app/version/detail?versionId=${versionId}&appId=${appId}`);
export const postRevertVersion = (appId: string, data: PostRevertAppProps) => // export const postRevertVersion = (appId: string, data: PostRevertAppProps) =>
POST(`/core/app/version/revert?appId=${appId}`, data); // POST(`/core/app/version/revert?appId=${appId}`, data);
export const updateAppVersion = (data: UpdateAppVersionBody) => export const updateAppVersion = (data: UpdateAppVersionBody) =>
POST(`/core/app/version/update`, data); POST(`/core/app/version/update`, data);
...@@ -438,94 +438,6 @@ export const getLatestNodeTemplate = ( ...@@ -438,94 +438,6 @@ export const getLatestNodeTemplate = (
return updatedNode; return updatedNode;
}; };
type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
chatConfig: AppChatConfigType;
};
export const compareWorkflow = (workflow1: WorkflowType, workflow2: WorkflowType) => {
const clone1 = cloneDeep(workflow1);
const clone2 = cloneDeep(workflow2);
if (!isEqual(clone1.edges, clone2.edges)) {
console.log('Edge not equal');
return false;
}
if (
clone1.chatConfig &&
clone2.chatConfig &&
!isEqual(
{
welcomeText: clone1.chatConfig?.welcomeText || '',
variables: clone1.chatConfig?.variables || [],
questionGuide: clone1.chatConfig?.questionGuide || false,
ttsConfig: clone1.chatConfig?.ttsConfig || undefined,
whisperConfig: clone1.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: clone1.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone1.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: clone1.chatConfig?.fileSelectConfig || undefined
},
{
welcomeText: clone2.chatConfig?.welcomeText || '',
variables: clone2.chatConfig?.variables || [],
questionGuide: clone2.chatConfig?.questionGuide || false,
ttsConfig: clone2.chatConfig?.ttsConfig || undefined,
whisperConfig: clone2.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: clone2.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone2.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: clone2.chatConfig?.fileSelectConfig || undefined
}
)
) {
console.log('chatConfig not equal');
return false;
}
const formatNodes = (nodes: StoreNodeItemType[]) => {
return nodes
.filter((node) => {
if (!node) return;
if ([FlowNodeTypeEnum.systemConfig].includes(node.flowNodeType)) return;
return true;
})
.map((node) => ({
flowNodeType: node.flowNodeType,
inputs: node.inputs.map((input) => ({
key: input.key,
selectedTypeIndex: input.selectedTypeIndex ?? 0,
renderTypeLis: input.renderTypeList,
valueType: input.valueType,
value: input.value ?? undefined
})),
outputs: node.outputs.map((item) => ({
key: item.key,
type: item.type,
value: item.value ?? undefined
})),
name: node.name,
intro: node.intro,
avatar: node.avatar,
version: node.version,
position: node.position
}));
};
const node1 = formatNodes(clone1.nodes);
const node2 = formatNodes(clone2.nodes);
// console.log(node1);
// console.log(node2);
node1.forEach((node, i) => {
if (!isEqual(node, node2[i])) {
console.log('node not equal');
}
});
return isEqual(node1, node2);
};
export const compareSnapshot = ( export const compareSnapshot = (
snapshot1: { snapshot1: {
nodes: Node<FlowNodeItemType, string | undefined>[] | undefined; nodes: Node<FlowNodeItemType, string | undefined>[] | undefined;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment