From 6bb10ca1509a86d27080c35ff1ab39129cd88493 Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 24 Sep 2024 21:09:59 +0800 Subject: [PATCH] perf: optimize simple app history (#2782) * simple app history * ui * extract context content into hooks --- .../web/components/common/Icon/constants.ts | 1 + .../common/Icon/icons/common/line.svg | 3 + .../common/MyDrawer/CustomRightDrawer.tsx | 6 +- .../app/src/components/common/folder/Path.tsx | 8 +- .../app/detail/components/Plugin/Header.tsx | 107 +--- .../components/PublishHistoriesSlider.tsx | 533 ++++++++++++------ .../pages/app/detail/components/RouteTab.tsx | 12 +- .../app/detail/components/SimpleApp/Edit.tsx | 31 +- .../detail/components/SimpleApp/Header.tsx | 178 +++--- .../app/detail/components/SimpleApp/index.tsx | 18 +- .../components/SimpleApp/useSnapshots.tsx | 47 ++ .../app/detail/components/Workflow/Header.tsx | 105 +--- .../Workflow/components/SaveButton.tsx | 115 ++++ .../components/WorkflowComponents/context.tsx | 15 +- .../WorkflowPublishHistoriesSlider.tsx | 351 ------------ .../pages/app/detail/components/context.tsx | 3 +- projects/app/src/web/core/app/api/version.ts | 8 +- projects/app/src/web/core/workflow/utils.ts | 88 --- 18 files changed, 736 insertions(+), 893 deletions(-) create mode 100644 packages/web/components/common/Icon/icons/common/line.svg create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx create mode 100644 projects/app/src/pages/app/detail/components/Workflow/components/SaveButton.tsx delete mode 100644 projects/app/src/pages/app/detail/components/WorkflowPublishHistoriesSlider.tsx diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index f0b593331..a4dc2f20a 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -40,6 +40,7 @@ export const iconPaths = { 'common/language/en': () => import('./icons/common/language/en.svg'), 'common/language/zh': () => import('./icons/common/language/zh.svg'), 'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'), + 'common/line': () => import('./icons/common/line.svg'), 'common/lineChange': () => import('./icons/common/lineChange.svg'), 'common/linkBlue': () => import('./icons/common/linkBlue.svg'), 'common/list': () => import('./icons/common/list.svg'), diff --git a/packages/web/components/common/Icon/icons/common/line.svg b/packages/web/components/common/Icon/icons/common/line.svg new file mode 100644 index 000000000..8dcb0cbbf --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/line.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/MyDrawer/CustomRightDrawer.tsx b/packages/web/components/common/MyDrawer/CustomRightDrawer.tsx index 8dc07d3d9..345673d5c 100644 --- a/packages/web/components/common/MyDrawer/CustomRightDrawer.tsx +++ b/packages/web/components/common/MyDrawer/CustomRightDrawer.tsx @@ -16,6 +16,8 @@ const CustomRightDrawer = ({ iconSrc, title, maxW = ['90vw', '30vw'], + top = 16, + bottom = 0, children, isLoading, showMask = true, @@ -31,8 +33,8 @@ const CustomRightDrawer = ({ zIndex={100} maxW={maxW} w={'100%'} - top={'60px'} - bottom={0} + top={top} + bottom={bottom} borderLeftRadius={'lg'} border={'base'} boxShadow={'2'} diff --git a/projects/app/src/components/common/folder/Path.tsx b/projects/app/src/components/common/folder/Path.tsx index 7f6cafbcc..cac95f6cd 100644 --- a/projects/app/src/components/common/folder/Path.tsx +++ b/projects/app/src/components/common/folder/Path.tsx @@ -2,6 +2,7 @@ import { Box, BoxProps, Flex } from '@chakra-ui/react'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import React, { useMemo } from 'react'; import { useTranslation } from 'next-i18next'; +import MyIcon from '@fastgpt/web/components/common/Icon'; const FolderPath = (props: { paths: ParentTreePathItemType[]; @@ -35,7 +36,7 @@ const FolderPath = (props: { return paths.length === 0 && !!FirstPathDom ? ( <>{FirstPathDom} ) : ( - + {concatPaths.map((item, i) => ( {i !== concatPaths.length - 1 && ( - - / - + )} ))} diff --git a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx index e18420a5e..738ccb5c9 100644 --- a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Box, Flex, @@ -22,18 +22,16 @@ import { useRouter } from 'next/router'; import AppCard from '../WorkflowComponents/AppCard'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; 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 MyModal from '@fastgpt/web/components/common/MyModal'; import { compareSnapshot } from '@/web/core/workflow/utils'; -import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useDebounceEffect } from 'ahooks'; 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 { t } = useTranslation(); @@ -48,12 +46,6 @@ const Header = () => { onOpen: onOpenBackConfirm, onClose: onCloseBackConfirm } = useDisclosure(); - const { - isOpen: isSaveAndPublishModalOpen, - onOpen: onSaveAndPublishModalOpen, - onClose: onSaveAndPublishModalClose - } = useDisclosure(); - const [isSave, setIsSave] = useState(false); const { flowData2StoreData, @@ -65,7 +57,9 @@ const Header = () => { edges, past, future, - setPast + setPast, + saveSnapshot, + resetSnapshot } = useContextSelector(WorkflowContext, (v) => v); const { lastAppListRouteType } = useSystemStore(); @@ -225,81 +219,11 @@ const Header = () => { {t('common:core.workflow.Run')} {!historiesDefaultData && ( - setIsSave(true)} - onCloseFunc={() => setIsSave(false)} - trigger={'hover'} - Trigger={ - - } - > - {({ onClose }) => ( - - { - await onClickSave({}); - toast({ - status: 'success', - title: t('app:saved_success'), - position: 'top-right' - }); - onClose(); - setIsSave(false); - }} - > - - {t('common:core.workflow.Save to cloud')} - - { - const data = flowData2StoreDataAndCheck(); - if (data) { - onSaveAndPublishModalOpen(); - } - onClose(); - setIsSave(false); - }} - > - - {t('common:core.workflow.Save and publish')} - {isSaveAndPublishModalOpen && ( - - )} - - - )} - + )} )} @@ -309,6 +233,9 @@ const Header = () => { onClose={() => { setHistoriesDefaultData(undefined); }} + past={past} + saveSnapshot={saveSnapshot} + resetSnapshot={resetSnapshot} /> )} { loading, isV2Workflow, historiesDefaultData, - isSave, onClickSave, setHistoriesDefaultData, appDetail.chatConfig, flowData2StoreDataAndCheck, setWorkflowTestData, - isSaveAndPublishModalOpen, - onSaveAndPublishModalClose, - toast, - onSaveAndPublishModalOpen + toast ]); return Render; diff --git a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx index 877c71b6c..99da9fa5a 100644 --- a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx +++ b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx @@ -1,204 +1,383 @@ -import React, { useCallback, useState } from 'react'; -import { getPublishList, postRevertVersion } from '@/web/core/app/api/version'; +import React, { useState } from 'react'; +import { + getAppVersionDetail, + getWorkflowVersionList, + updateAppVersion +} from '@/web/core/app/api/version'; import { useVirtualScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer'; import { useTranslation } from 'next-i18next'; -import { Box, Button, Flex } from '@chakra-ui/react'; -import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; +import { Box, Button, Flex, Input } from '@chakra-ui/react'; import { useContextSelector } from 'use-context-selector'; -import { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { AppContext } from './context'; -import { useI18n } from '@/web/context/I18n'; -import { AppSchema } from '@fastgpt/global/core/app/type'; -import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; - -export type InitProps = { - nodes: AppSchema['modules']; - edges: AppSchema['edges']; - chatConfig: AppSchema['chatConfig']; -}; +import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; +import { SaveSnapshotParams, SnapshotsType } from './WorkflowComponents/context'; +import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import Tag from '@fastgpt/web/components/common/Tag'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyPopover from '@fastgpt/web/components/common/MyPopover'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { versionListResponse } from '@/pages/api/core/app/version/listWorkflow'; const PublishHistoriesSlider = ({ onClose, - initData, - defaultData + past, + saveSnapshot, + resetSnapshot, + top, + bottom }: { onClose: () => void; - initData: (data: InitProps) => void; - defaultData: InitProps; + past: SnapshotsType[]; + saveSnapshot: (params: SaveSnapshotParams) => Promise; + resetSnapshot: (state: SnapshotsType) => void; + top?: string | number; + bottom?: string | number; }) => { const { t } = useTranslation(); - const { appT } = useI18n(); - - const { appDetail, setAppDetail, reloadAppLatestVersion } = useContextSelector( - AppContext, - (v) => v - ); - const appId = appDetail._id; - - const [selectedHistoryId, setSelectedHistoryId] = useState(); - - const { scrollDataList, ScrollList, isLoading } = useVirtualScrollPagination(getPublishList, { - itemHeight: 49, - overscan: 20, - - pageSize: 20, - defaultParams: { - appId - } - }); - - const onPreview = useCallback( - (data: AppVersionSchemaType) => { - setSelectedHistoryId(data._id); - - initData({ - nodes: data.nodes, - edges: data.edges, - chatConfig: data.chatConfig - }); - }, - [initData] - ); - const onCloseSlider = useCallback( - (data: InitProps) => { - setSelectedHistoryId(undefined); - initData(data); - onClose(); - }, - [initData, onClose] - ); - - const { runAsync: onRevert } = useRequest2( - async (data: AppVersionSchemaType) => { - if (!appId) return; - await postRevertVersion(appId, { - versionId: data._id, - editNodes: defaultData.nodes, // old workflow - editEdges: defaultData.edges, - editChatConfig: defaultData.chatConfig - }); - - setAppDetail((state) => ({ - ...state, - modules: data.nodes, - edges: data.edges - })); - - onCloseSlider(data); - reloadAppLatestVersion(); - }, - { - successToast: appT('version.Revert success') - } - ); - - const showLoading = isLoading; + const [currentTab, setCurrentTab] = useState<'myEdit' | 'teamCloud'>('myEdit'); return ( <> - onCloseSlider({ - nodes: defaultData.nodes, - edges: defaultData.edges, - chatConfig: defaultData.chatConfig - }) + onClose={() => onClose()} + title={ + ( + <> + + + ) as any } - iconSrc="core/workflow/versionHistories" - title={t('common:core.workflow.publish.histories')} - maxW={'300px'} + maxW={'340px'} px={0} showMask={false} overflow={'unset'} + top={top} + bottom={bottom} > - - - {scrollDataList.map((data, index) => { - const item = data.data; - - return ( - onPreview(item)} - > - - - {formatTime2YMDHM(item.time)} - - {item._id === selectedHistoryId && ( - onRevert(item)} - Trigger={ - - - - - - } - /> - )} - - ); - })} - + {currentTab === 'myEdit' ? ( + + ) : ( + + )} ); }; export default React.memo(PublishHistoriesSlider); + +const MyEdit = ({ + past, + saveSnapshot, + resetSnapshot +}: { + past: SnapshotsType[]; + saveSnapshot: (params: SaveSnapshotParams) => Promise; + resetSnapshot: (state: SnapshotsType) => void; +}) => { + const { t } = useTranslation(); + const { toast } = useToast(); + + return ( + + {past.length > 0 && ( + + + + )} + + {past.map((item, index) => { + return ( + { + const res = await saveSnapshot({ + pastNodes: item.nodes, + pastEdges: item.edges, + chatConfig: item.chatConfig, + customTitle: `${t('app:app.version_copy')}-${item.title}` + }); + if (res) { + resetSnapshot(item); + } + + toast({ + title: t('workflow:workflow.Switch_success'), + status: 'success' + }); + }} + > + + + {item.title} + + + ); + })} + + {t('common:common.No more data')} + + + + ); +}; + +const TeamCloud = ({ + saveSnapshot, + resetSnapshot +}: { + saveSnapshot: (params: SaveSnapshotParams) => Promise; + resetSnapshot: (state: SnapshotsType) => void; +}) => { + const { t } = useTranslation(); + const { appDetail } = useContextSelector(AppContext, (v) => v); + const { loadAndGetTeamMembers } = useUserStore(); + const { feConfigs } = useSystemStore(); + + const { scrollDataList, ScrollList, isLoading, fetchData } = useVirtualScrollPagination( + getWorkflowVersionList, + { + itemHeight: 40, + overscan: 20, + + pageSize: 30, + defaultParams: { + appId: appDetail._id + } + } + ); + const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { + manual: !feConfigs.isPlus + }); + const [editIndex, setEditIndex] = useState(undefined); + const [hoveredIndex, setHoveredIndex] = useState(undefined); + + const [isEditing, setIsEditing] = useState(false); + const { toast } = useToast(); + + const { runAsync: onChangeVersion, loading: isLoadingVersion } = useRequest2( + async (versionItem: versionListResponse) => { + const versionDetail = await getAppVersionDetail(versionItem._id, versionItem.appId); + + if (!versionDetail) return; + + const state = { + nodes: versionDetail.nodes?.map((item) => storeNode2FlowNode({ item, t })), + edges: versionDetail.edges?.map((item) => storeEdgesRenderEdge({ edge: item })), + title: versionItem.versionName, + chatConfig: versionDetail.chatConfig + }; + + await saveSnapshot({ + pastNodes: state.nodes, + pastEdges: state.edges, + chatConfig: state.chatConfig, + customTitle: `${t('app:app.version_copy')}-${state.title}` + }); + + resetSnapshot(state); + toast({ + title: t('workflow:workflow.Switch_success'), + status: 'success' + }); + } + ); + + return ( + + {scrollDataList.map((data, index) => { + const item = data.data; + const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish); + const tmb = members.find((member) => member.tmbId === item.tmbId); + + return ( + setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(undefined)} + _hover={{ + bg: 'primary.50' + }} + onClick={() => editIndex === undefined && onChangeVersion(item)} + > + + + + } + > + {({ onClose }) => ( + + + + + + + {tmb?.memberName} + + + {formatTime2YMDHMS(item.time)} + + + + )} + + {editIndex !== index ? ( + <> + + + + {item.versionName || formatTime2YMDHMS(item.time)} + + + {item.isPublish && ( + + {index === firstPublishedIndex + ? t('app:app.version_current') + : t('app:app.version_past')} + + )} + + {hoveredIndex === index && ( + { + e.stopPropagation(); + setEditIndex(index); + }} + /> + )} + + ) : ( + + e.stopPropagation()} + onBlur={async (e) => { + setIsEditing(true); + await updateAppVersion({ + appId: item.appId, + versionName: e.target.value, + versionId: item._id + }); + await fetchData(); + setEditIndex(undefined); + setIsEditing(false); + }} + /> + + )} + + ); + })} + + ); +}; diff --git a/projects/app/src/pages/app/detail/components/RouteTab.tsx b/projects/app/src/pages/app/detail/components/RouteTab.tsx index bdafac4eb..315175826 100644 --- a/projects/app/src/pages/app/detail/components/RouteTab.tsx +++ b/projects/app/src/pages/app/detail/components/RouteTab.tsx @@ -3,13 +3,11 @@ import React, { useCallback, useMemo } from 'react'; import { AppContext, TabEnum } from './context'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; -import { useI18n } from '@/web/context/I18n'; import { useContextSelector } from 'use-context-selector'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; const RouteTab = () => { const { t } = useTranslation(); - const { appT } = useI18n(); const router = useRouter(); const { appDetail, currentTab } = useContextSelector(AppContext, (v) => v); @@ -28,20 +26,21 @@ const RouteTab = () => { 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 }, ...(appDetail.permission.hasManagePer ? [ { - label: appT('publish_channel'), + label: t('app:publish_channel'), 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 ( @@ -51,6 +50,7 @@ const RouteTab = () => { key={tab.id} px={2} py={0.5} + fontWeight={'medium'} {...(currentTab === tab.id ? { color: 'primary.700' diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx index fd1c93b3e..6fb1dcbf9 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx @@ -15,21 +15,46 @@ import { cardStyles } from '../constants'; import styles from './styles.module.scss'; 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 = ({ appForm, - setAppForm + setAppForm, + past, + saveSnapshot }: { appForm: AppSimpleEditFormType; setAppForm: React.Dispatch>; + past: SnapshotsType[]; + saveSnapshot: ( + this: any, + { pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any + ) => Promise; }) => { const { isPc } = useSystem(); const { loadAllDatasets } = useDatasetStore(); const { appDetail } = useContextSelector(AppContext, (v) => v); + const { t } = useTranslation(); // show selected dataset useMount(() => { 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( appWorkflow2Form({ nodes: appDetail.modules, @@ -52,7 +77,7 @@ const Edit = ({ display={['block', 'flex']} flex={'1 0 0'} h={0} - pt={[2, 1.5]} + mt={[4, 0]} gap={1} borderRadius={'lg'} overflowY={['auto', 'unset']} @@ -73,7 +98,7 @@ const Edit = ({ {isPc && ( - + )} diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx index 407947577..dcb92a351 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx @@ -1,42 +1,57 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { useContextSelector } from 'use-context-selector'; import { AppContext } from '../context'; import FolderPath from '@/components/common/folder/Path'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; 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 RouteTab from '../RouteTab'; import { useTranslation } from 'next-i18next'; -import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { form2AppWorkflow } from '@/web/core/app/utils'; 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 { compareWorkflow } from '@/web/core/workflow/utils'; import MyTag from '@fastgpt/web/components/common/Tag/index'; import { publishStatusStyle } from '../constants'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { useSystemStore } from '@/web/common/system/useSystemStore'; 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 = ({ appForm, - setAppForm + setAppForm, + past, + setPast, + saveSnapshot }: { appForm: AppSimpleEditFormType; - setAppForm: React.Dispatch>; + setAppForm: (form: AppSimpleEditFormType) => void; + past: SnapshotsType[]; + setPast: (value: React.SetStateAction) => void; + saveSnapshot: ( + this: any, + { pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any + ) => Promise; }) => { const { t } = useTranslation(); const { isPc } = useSystem(); const router = useRouter(); - const { toast } = useToast(); const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v); const { lastAppListRouteType } = useSystemStore(); const { allDatasets } = useDatasetStore(); @@ -45,8 +60,10 @@ const Header = ({ manual: false, refreshDeps: [appId] }); - const onclickRoute = useCallback( + const onClickRoute = useCallback( (parentId: string) => { + localStorage.removeItem(`${appDetail._id}-past`); + router.push({ pathname: '/app/list', query: { @@ -58,58 +75,98 @@ const Header = ({ [router, lastAppListRouteType] ); - const isPublished = useMemo(() => { - const data = form2AppWorkflow(appForm, t); - return compareWorkflow( - { - nodes: appDetail.modules, - edges: [], - chatConfig: appDetail.chatConfig - }, - { - nodes: data.nodes, - edges: [], - chatConfig: data.chatConfig - } - ); - }, [appDetail.chatConfig, appDetail.modules, appForm, allDatasets, t]); + const [isPublished, setIsPublished] = useState(false); - const onSubmitPublish = useCallback( - async (data: AppSimpleEditFormType) => { - const { nodes, edges } = form2AppWorkflow(data, t); + 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: data.chatConfig, + chatConfig: appForm.chatConfig, type: AppTypeEnum.simple, - isPublish: true, - versionName: formatTime2YMDHMS(new Date()) + isPublish, + versionName }); - toast({ - status: 'success', - title: t('app:publish_success'), - position: 'top-right' - }); - }, - [onSaveApp, t, toast] + setPast((prevPast) => + prevPast.map((item, index) => + index === 0 + ? { + ...item, + isSaved: true + } + : item + ) + ); + } ); const [historiesDefaultData, setHistoriesDefaultData] = useState(); + const resetSnapshot = (data: SnapshotsType) => { + const storeWorkflow = uiWorkflow2StoreWorkflow(data); + const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: data.chatConfig }); + + setAppForm(currentAppForm); + }; + + useDebounceEffect( + () => { + const data = form2AppWorkflow(appForm, t); + + 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: savedSnapshot?.nodes, + edges: [], + chatConfig: savedSnapshot?.chatConfig + }, + { + nodes: data.nodes?.map((item) => storeNode2FlowNode({ item, t })), + edges: [], + chatConfig: data.chatConfig + } + ); + setIsPublished(val); + }, + [past], + { wait: 500 } + ); + return ( - + {!isPc && ( - + )} - + {isPc && ( @@ -156,36 +213,23 @@ const Header = ({ }); }} /> - - - - - - } - onConfirm={() => onSubmitPublish(appForm)} - /> + )} )} - {!!historiesDefaultData && ( - { - setAppForm( - appWorkflow2Form({ - nodes, - chatConfig - }) - ); + {historiesDefaultData && currentTab === TabEnum.appEdit && ( + { + setHistoriesDefaultData(undefined); }} - onClose={() => setHistoriesDefaultData(undefined)} - defaultData={historiesDefaultData} + past={past} + saveSnapshot={saveSnapshot} + resetSnapshot={resetSnapshot} + top={14} + bottom={3} /> )} diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx index ad6ba12b8..6c3a62b96 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx @@ -9,13 +9,15 @@ import dynamic from 'next/dynamic'; import { Box, Flex } from '@chakra-ui/react'; import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; import { useTranslation } from 'next-i18next'; +import useSnapshots from './useSnapshots'; const Logs = dynamic(() => import('../Logs/index')); const PublishChannel = dynamic(() => import('../Publish')); const SimpleEdit = () => { 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()); @@ -24,12 +26,18 @@ const SimpleEdit = () => { }); return ( - -
+ +
{currentTab === TabEnum.appEdit ? ( - + ) : ( - + {currentTab === TabEnum.publish && } {currentTab === TabEnum.logs && } diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx new file mode 100644 index 000000000..f33b93087 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx @@ -0,0 +1,47 @@ +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(`${appId}-past-simple`, { + defaultValue: [], + listenStorageChange: true + }) as [SnapshotsType[], (value: SetStateAction) => 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; diff --git a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx index b62562492..bcfbd7868 100644 --- a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx @@ -22,18 +22,16 @@ import { useRouter } from 'next/router'; import AppCard from '../WorkflowComponents/AppCard'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; 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 MyModal from '@fastgpt/web/components/common/MyModal'; import { compareSnapshot } from '@/web/core/workflow/utils'; -import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useDebounceEffect } from 'ahooks'; 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 { t } = useTranslation(); @@ -48,12 +46,6 @@ const Header = () => { onOpen: onOpenBackConfirm, onClose: onCloseBackConfirm } = useDisclosure(); - const { - isOpen: isSaveAndPublishModalOpen, - onOpen: onSaveAndPublishModalOpen, - onClose: onSaveAndPublishModalClose - } = useDisclosure(); - const [isSave, setIsSave] = useState(false); const { flowData2StoreData, @@ -65,7 +57,9 @@ const Header = () => { edges, past, future, - setPast + setPast, + saveSnapshot, + resetSnapshot } = useContextSelector(WorkflowContext, (v) => v); const { lastAppListRouteType } = useSystemStore(); @@ -227,81 +221,11 @@ const Header = () => { {t('common:core.workflow.Run')} {!historiesDefaultData && ( - setIsSave(true)} - onCloseFunc={() => setIsSave(false)} - trigger={'hover'} - Trigger={ - - } - > - {({ onClose }) => ( - - { - await onClickSave({}); - toast({ - status: 'success', - title: t('app:saved_success'), - position: 'top-right' - }); - onClose(); - setIsSave(false); - }} - > - - {t('common:core.workflow.Save to cloud')} - - { - const data = flowData2StoreDataAndCheck(); - if (data) { - onSaveAndPublishModalOpen(); - } - onClose(); - setIsSave(false); - }} - > - - {t('common:core.workflow.Save and publish')} - {isSaveAndPublishModalOpen && ( - - )} - - - )} - + )} )} @@ -311,8 +235,12 @@ const Header = () => { onClose={() => { setHistoriesDefaultData(undefined); }} + past={past} + saveSnapshot={saveSnapshot} + resetSnapshot={resetSnapshot} /> )} + { loading, isV2Workflow, historiesDefaultData, - isSave, onClickSave, setHistoriesDefaultData, appDetail.chatConfig, flowData2StoreDataAndCheck, setWorkflowTestData, - isSaveAndPublishModalOpen, - onSaveAndPublishModalClose, toast, - onSaveAndPublishModalOpen + past ]); return Render; diff --git a/projects/app/src/pages/app/detail/components/Workflow/components/SaveButton.tsx b/projects/app/src/pages/app/detail/components/Workflow/components/SaveButton.tsx new file mode 100644 index 000000000..7caedfe54 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Workflow/components/SaveButton.tsx @@ -0,0 +1,115 @@ +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; + 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 ( + setIsSave(true)} + onCloseFunc={() => setIsSave(false)} + trigger={'hover'} + Trigger={ + + } + > + {({ onClose }) => ( + + { + await onClickSave({}); + toast({ + status: 'success', + title: t('app:saved_success'), + position: 'top-right' + }); + onClose(); + setIsSave(false); + }} + > + + {t('common:core.workflow.Save to cloud')} + + { + const canOpen = !checkData || checkData(); + if (canOpen) { + onSaveAndPublishModalOpen(); + } + onClose(); + setIsSave(false); + }} + > + + {t('common:core.workflow.Save and publish')} + {isSaveAndPublishModalOpen && ( + + )} + + + )} + + ); +}; + +export default React.memo(SaveButton); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx index 879ef9e62..02770b3a7 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx @@ -39,7 +39,7 @@ import { defaultRunningStatus } from './constants'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; 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 ChatTest from './Flow/ChatTest'; import { useDisclosure } from '@chakra-ui/react'; @@ -47,7 +47,6 @@ import { uiWorkflow2StoreWorkflow } from './utils'; import { useTranslation } from 'next-i18next'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time'; -import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider'; import { cloneDeep } from 'lodash'; import { SetState } from 'ahooks/lib/createUseStorageState'; @@ -60,6 +59,18 @@ export type SnapshotsType = { chatConfig: AppChatConfigType; 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 = { appId?: string; basicNodeTemplates: FlowNodeTemplateType[]; diff --git a/projects/app/src/pages/app/detail/components/WorkflowPublishHistoriesSlider.tsx b/projects/app/src/pages/app/detail/components/WorkflowPublishHistoriesSlider.tsx deleted file mode 100644 index 81731c0d8..000000000 --- a/projects/app/src/pages/app/detail/components/WorkflowPublishHistoriesSlider.tsx +++ /dev/null @@ -1,351 +0,0 @@ -import React, { useState } from 'react'; -import { - getAppVersionDetail, - getWorkflowVersionList, - updateAppVersion -} from '@/web/core/app/api/version'; -import { useVirtualScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; -import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer'; -import { useTranslation } from 'next-i18next'; -import { Box, Button, Flex, Input } from '@chakra-ui/react'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from './context'; -import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; -import { WorkflowContext } from './WorkflowComponents/context'; -import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; -import Avatar from '@fastgpt/web/components/common/Avatar'; -import Tag from '@fastgpt/web/components/common/Tag'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import MyPopover from '@fastgpt/web/components/common/MyPopover'; -import MyBox from '@fastgpt/web/components/common/MyBox'; -import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { useToast } from '@fastgpt/web/hooks/useToast'; -import { versionListResponse } from '@/pages/api/core/app/version/listWorkflow'; - -const WorkflowPublishHistoriesSlider = ({ onClose }: { onClose: () => void }) => { - const { t } = useTranslation(); - const [currentTab, setCurrentTab] = useState<'myEdit' | 'teamCloud'>('myEdit'); - - return ( - <> - onClose()} - title={ - ( - <> - - - ) as any - } - maxW={'340px'} - px={0} - showMask={false} - overflow={'unset'} - > - {currentTab === 'myEdit' ? : } - - - ); -}; - -export default React.memo(WorkflowPublishHistoriesSlider); - -const MyEdit = () => { - const { past, saveSnapshot, resetSnapshot } = useContextSelector(WorkflowContext, (v) => v); - const { t } = useTranslation(); - const { toast } = useToast(); - - return ( - - {past.length > 0 && ( - - - - )} - - {past.map((item, index) => { - return ( - { - const res = await saveSnapshot({ - pastNodes: item.nodes, - pastEdges: item.edges, - chatConfig: item.chatConfig, - customTitle: `${t('app:app.version_copy')}-${item.title}` - }); - if (res) { - resetSnapshot(item); - } - - toast({ - title: t('workflow:workflow.Switch_success'), - status: 'success' - }); - }} - > - - - {item.title} - - - ); - })} - - {t('common:common.No more data')} - - - - ); -}; - -const TeamCloud = () => { - const { t } = useTranslation(); - const { appDetail } = useContextSelector(AppContext, (v) => v); - const { saveSnapshot, resetSnapshot } = useContextSelector(WorkflowContext, (v) => v); - const { loadAndGetTeamMembers } = useUserStore(); - const { feConfigs } = useSystemStore(); - - const { scrollDataList, ScrollList, isLoading, fetchData } = useVirtualScrollPagination( - getWorkflowVersionList, - { - itemHeight: 40, - overscan: 20, - - pageSize: 30, - defaultParams: { - appId: appDetail._id - } - } - ); - const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { - manual: !feConfigs.isPlus - }); - const [editIndex, setEditIndex] = useState(undefined); - const [hoveredIndex, setHoveredIndex] = useState(undefined); - - const [isEditing, setIsEditing] = useState(false); - const { toast } = useToast(); - - const { runAsync: onChangeVersion, loading: isLoadingVersion } = useRequest2( - async (versionItem: versionListResponse) => { - const versionDetail = await getAppVersionDetail(versionItem._id, versionItem.appId); - - if (!versionDetail) return; - - const state = { - nodes: versionDetail.nodes?.map((item) => storeNode2FlowNode({ item, t })), - edges: versionDetail.edges?.map((item) => storeEdgesRenderEdge({ edge: item })), - title: versionItem.versionName, - chatConfig: versionDetail.chatConfig - }; - - await saveSnapshot({ - pastNodes: state.nodes, - pastEdges: state.edges, - chatConfig: state.chatConfig, - customTitle: `${t('app:app.version_copy')}-${state.title}` - }); - - resetSnapshot(state); - toast({ - title: t('workflow:workflow.Switch_success'), - status: 'success' - }); - } - ); - - return ( - - {scrollDataList.map((data, index) => { - const item = data.data; - const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish); - const tmb = members.find((member) => member.tmbId === item.tmbId); - - return ( - setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(undefined)} - _hover={{ - bg: 'primary.50' - }} - onClick={() => editIndex === undefined && onChangeVersion(item)} - > - - - - } - > - {({ onClose }) => ( - - - - - - - {tmb?.memberName} - - - {formatTime2YMDHMS(item.time)} - - - - )} - - {editIndex !== index ? ( - <> - - - - {item.versionName || formatTime2YMDHMS(item.time)} - - - {item.isPublish && ( - - {index === firstPublishedIndex - ? t('app:app.version_current') - : t('app:app.version_past')} - - )} - - {hoveredIndex === index && ( - { - e.stopPropagation(); - setEditIndex(index); - }} - /> - )} - - ) : ( - - e.stopPropagation()} - onBlur={async (e) => { - setIsEditing(true); - await updateAppVersion({ - appId: item.appId, - versionName: e.target.value, - versionId: item._id - }); - await fetchData(); - setEditIndex(undefined); - setIsEditing(false); - }} - /> - - )} - - ); - })} - - ); -}; diff --git a/projects/app/src/pages/app/detail/components/context.tsx b/projects/app/src/pages/app/detail/components/context.tsx index ee0e97d6e..e7806fb0c 100644 --- a/projects/app/src/pages/app/detail/components/context.tsx +++ b/projects/app/src/pages/app/detail/components/context.tsx @@ -1,4 +1,4 @@ -import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react'; +import { Dispatch, ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react'; import { createContext } from 'use-context-selector'; import { defaultApp } from '@/web/core/app/constants'; import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api'; @@ -11,7 +11,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import dynamic from 'next/dynamic'; import { useDisclosure } from '@chakra-ui/react'; 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 { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; diff --git a/projects/app/src/web/core/app/api/version.ts b/projects/app/src/web/core/app/api/version.ts index cb308d7b3..bd9717986 100644 --- a/projects/app/src/web/core/app/api/version.ts +++ b/projects/app/src/web/core/app/api/version.ts @@ -15,8 +15,8 @@ export const getAppLatestVersion = (data: getLatestVersionQuery) => export const postPublishApp = (appId: string, data: PostPublishAppProps) => POST(`/core/app/version/publish?appId=${appId}`, data); -export const getPublishList = (data: PaginationProps<{ appId: string }>) => - POST>('/core/app/version/list', data); +// export const getPublishList = (data: PaginationProps<{ appId: string }>) => +// POST>('/core/app/version/list', data); export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) => POST>('/core/app/version/listWorkflow', data); @@ -24,8 +24,8 @@ export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) export const getAppVersionDetail = (versionId: string, appId: string) => GET(`/core/app/version/detail?versionId=${versionId}&appId=${appId}`); -export const postRevertVersion = (appId: string, data: PostRevertAppProps) => - POST(`/core/app/version/revert?appId=${appId}`, data); +// export const postRevertVersion = (appId: string, data: PostRevertAppProps) => +// POST(`/core/app/version/revert?appId=${appId}`, data); export const updateAppVersion = (data: UpdateAppVersionBody) => POST(`/core/app/version/update`, data); diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index cc2b37d56..9a9e1ce4a 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -438,94 +438,6 @@ export const getLatestNodeTemplate = ( 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 = ( snapshot1: { nodes: Node[] | undefined;