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={
-
- }
- >
- {t('common:common.Save')}
-
- }
- >
- {({ 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={
-
- }
- >
- {t('common:common.Save')}
-
- }
- >
- {({ 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={
+
+ }
+ >
+ {t('common:common.Save')}
+
+ }
+ >
+ {({ 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;