diff --git a/projects/app/src/pages/api/core/app/version/list.ts b/projects/app/src/pages/api/core/app/version/list.ts deleted file mode 100644 index 47b3a5065..000000000 --- a/projects/app/src/pages/api/core/app/version/list.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { NextAPI } from '@/service/middleware/entry'; -import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; -import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; -import { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; -import { ApiRequestProps } from '@fastgpt/service/type/next'; - -type Props = PaginationProps<{ - appId: string; -}>; - -type Response = PaginationResponse; - -async function handler(req: ApiRequestProps, res: NextApiResponse): Promise { - const { offset, pageSize, appId } = req.body; - - const [result, total] = await Promise.all([ - MongoAppVersion.find({ - appId - }) - .sort({ - time: -1 - }) - .skip(offset) - .limit(pageSize), - MongoAppVersion.countDocuments({ appId }) - ]); - - return { - total, - list: result - }; -} - -export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/version/listWorkflow.tsx b/projects/app/src/pages/api/core/app/version/list.tsx similarity index 100% rename from projects/app/src/pages/api/core/app/version/listWorkflow.tsx rename to projects/app/src/pages/api/core/app/version/list.tsx diff --git a/projects/app/src/pages/api/core/app/version/revert.ts b/projects/app/src/pages/api/core/app/version/revert.ts deleted file mode 100644 index 9d1b9bf5f..000000000 --- a/projects/app/src/pages/api/core/app/version/revert.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { NextAPI } from '@/service/middleware/entry'; -import { authApp } from '@fastgpt/service/support/permission/app/auth'; -import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; -import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; -import { MongoApp } from '@fastgpt/service/core/app/schema'; -import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller'; -import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; -import { PostRevertAppProps } from '@/global/core/app/api'; -import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; -import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; - -type Response = {}; - -async function handler(req: NextApiRequest, res: NextApiResponse): Promise<{}> { - const { appId } = req.query as { appId: string }; - const { - editNodes = [], - editEdges = [], - editChatConfig, - versionId - } = req.body as PostRevertAppProps; - - const { app } = await authApp({ appId, req, per: WritePermissionVal, authToken: true }); - - const version = await MongoAppVersion.findOne({ - _id: versionId, - appId - }); - - if (!version) { - throw new Error('version not found'); - } - - const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes }); - - const scheduledTriggerConfig = version.chatConfig?.scheduledTriggerConfig; - - await mongoSessionRun(async (session) => { - // 为编辑中的数据创建一个版本 - await MongoAppVersion.create( - [ - { - appId, - nodes: formatEditNodes, - edges: editEdges, - chatConfig: editChatConfig - } - ], - { session } - ); - - // 为历史版本再创建一个版本 - const [{ _id }] = await MongoAppVersion.create( - [ - { - appId, - nodes: version.nodes, - edges: version.edges, - chatConfig: version.chatConfig - } - ], - { session } - ); - - // update app - await MongoApp.findByIdAndUpdate(appId, { - modules: version.nodes, - edges: version.edges, - chatConfig: version.chatConfig, - updateTime: new Date(), - scheduledTriggerConfig: scheduledTriggerConfig ? scheduledTriggerConfig : null, - scheduledTriggerNextTime: scheduledTriggerConfig?.cronString - ? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig) - : null, - 'pluginData.nodeVersion': _id - }); - }); - - return {}; -} - -export default NextAPI(handler); diff --git a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx index 99da9fa5a..b7e3d1b22 100644 --- a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx +++ b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx @@ -23,7 +23,7 @@ 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'; +import type { versionListResponse } from '@/pages/api/core/app/version/list'; const PublishHistoriesSlider = ({ onClose, 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 6fb1dcbf9..1699fd848 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx @@ -15,10 +15,11 @@ 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 { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; import { SnapshotsType } from '../WorkflowComponents/context'; +import { SaveSnapshotFnType } from './useSnapshots'; const Edit = ({ appForm, @@ -29,32 +30,33 @@ const Edit = ({ appForm: AppSimpleEditFormType; setAppForm: React.Dispatch>; past: SnapshotsType[]; - saveSnapshot: ( - this: any, - { pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any - ) => Promise; + saveSnapshot: SaveSnapshotFnType; }) => { const { isPc } = useSystem(); const { loadAllDatasets } = useDatasetStore(); const { appDetail } = useContextSelector(AppContext, (v) => v); const { t } = useTranslation(); - // show selected dataset + // Init app form useMount(() => { + // show selected dataset loadAllDatasets(); - saveSnapshot({ - pastNodes: appDetail.modules?.map((item) => storeNode2FlowNode({ item, t })), - pastEdges: appDetail.edges?.map((item) => storeEdgesRenderEdge({ edge: item })), - chatConfig: appDetail.chatConfig, - isSaved: true - }); + + // Get the latest snapshot if (past.length > 0) { const storeWorkflow = uiWorkflow2StoreWorkflow(past[0]); const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: past[0].chatConfig }); - setAppForm(currentAppForm); - return; + return setAppForm(currentAppForm); } + + // Set the first snapshot + saveSnapshot({ + pastNodes: appDetail.modules?.map((item) => storeNode2FlowNode({ item, t })), + chatConfig: appDetail.chatConfig, + isSaved: true + }); + setAppForm( appWorkflow2Form({ nodes: appDetail.modules, 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 dcb92a351..ac5cd1776 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx @@ -30,6 +30,7 @@ import { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; +import { SaveSnapshotFnType } from './useSnapshots'; const PublishHistories = dynamic(() => import('../PublishHistoriesSlider')); @@ -44,15 +45,12 @@ const Header = ({ setAppForm: (form: AppSimpleEditFormType) => void; past: SnapshotsType[]; setPast: (value: React.SetStateAction) => void; - saveSnapshot: ( - this: any, - { pastNodes, pastEdges, chatConfig, customTitle, isSaved }: any - ) => Promise; + saveSnapshot: SaveSnapshotFnType; }) => { const { t } = useTranslation(); const { isPc } = useSystem(); const router = useRouter(); - const { appId, appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v); + const { appId, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v); const { lastAppListRouteType } = useSystemStore(); const { allDatasets } = useDatasetStore(); @@ -62,8 +60,6 @@ const Header = ({ }); const onClickRoute = useCallback( (parentId: string) => { - localStorage.removeItem(`${appDetail._id}-past`); - router.push({ pathname: '/app/list', query: { @@ -75,8 +71,6 @@ const Header = ({ [router, lastAppListRouteType] ); - const [isPublished, setIsPublished] = useState(false); - const { runAsync: onClickSave, loading } = useRequest2( async ({ isPublish, @@ -109,20 +103,23 @@ const Header = ({ const [historiesDefaultData, setHistoriesDefaultData] = useState(); - const resetSnapshot = (data: SnapshotsType) => { - const storeWorkflow = uiWorkflow2StoreWorkflow(data); - const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: data.chatConfig }); + const resetSnapshot = useCallback( + (data: SnapshotsType) => { + const storeWorkflow = uiWorkflow2StoreWorkflow(data); + const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: data.chatConfig }); - setAppForm(currentAppForm); - }; + setAppForm(currentAppForm); + }, + [setAppForm] + ); + // Save snapshot to local 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 }); }, @@ -130,10 +127,13 @@ const Header = ({ { wait: 500 } ); + // Check if the workflow is published + const [isPublished, setIsPublished] = useState(false); useDebounceEffect( () => { const savedSnapshot = past.find((snapshot) => snapshot.isSaved); - const data = form2AppWorkflow(appForm, t); + const editFormData = form2AppWorkflow(appForm, t); + console.log(savedSnapshot?.nodes, editFormData.chatConfig); const val = compareSnapshot( { nodes: savedSnapshot?.nodes, @@ -141,14 +141,14 @@ const Header = ({ chatConfig: savedSnapshot?.chatConfig }, { - nodes: data.nodes?.map((item) => storeNode2FlowNode({ item, t })), + nodes: editFormData.nodes?.map((item) => storeNode2FlowNode({ item, t })), edges: [], - chatConfig: data.chatConfig + chatConfig: editFormData.chatConfig } ); setIsPublished(val); }, - [past], + [past, allDatasets], { wait: 500 } ); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx index f33b93087..30b843c6b 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/useSnapshots.tsx @@ -1,8 +1,16 @@ import { useLocalStorageState, useMemoizedFn } from 'ahooks'; -import { SnapshotsType } from '../WorkflowComponents/context'; -import { SetStateAction } from 'react'; +import { SaveSnapshotParams, SnapshotsType } from '../WorkflowComponents/context'; +import { SetStateAction, useEffect } from 'react'; import { compareSnapshot } from '@/web/core/workflow/utils'; import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; +import { AppChatConfigType } from '@fastgpt/global/core/app/type'; +import { Node } from 'reactflow'; + +export type SaveSnapshotFnType = ( + props: SaveSnapshotParams & { + isSaved?: boolean; + } +) => Promise; const useSnapshots = (appId: string) => { const [past, setPast] = useLocalStorageState(`${appId}-past-simple`, { @@ -10,13 +18,16 @@ const useSnapshots = (appId: string) => { listenStorageChange: true }) as [SnapshotsType[], (value: SetStateAction) => void]; - const saveSnapshot = useMemoizedFn( - async ({ pastNodes, pastEdges, chatConfig, customTitle, isSaved }) => { + const saveSnapshot: SaveSnapshotFnType = useMemoizedFn( + async ({ pastNodes, chatConfig, customTitle, isSaved }) => { + if (!pastNodes) return false; + const pastState = past[0]; + const isPastEqual = compareSnapshot( { nodes: pastNodes, - edges: pastEdges, + edges: [], chatConfig: chatConfig }, { @@ -30,7 +41,7 @@ const useSnapshots = (appId: string) => { setPast((past) => [ { nodes: pastNodes, - edges: pastEdges, + edges: [], title: customTitle || formatTime2YMDHMS(new Date()), chatConfig, isSaved @@ -41,6 +52,18 @@ const useSnapshots = (appId: string) => { } ); + // remove other app's snapshot + useEffect(() => { + const keys = Object.keys(localStorage); + const snapshotKeys = keys.filter((key) => key.endsWith('-past-simple')); + snapshotKeys.forEach((key) => { + const keyAppId = key.split('-')[0]; + if (keyAppId !== appId) { + localStorage.removeItem(key); + } + }); + }, [appId]); + return { past, setPast, saveSnapshot }; }; 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 bcfbd7868..688dd128c 100644 --- a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx @@ -279,20 +279,22 @@ const Header = () => { currentTab, isPublished, onBack, - isOpenBackConfirm, onOpenBackConfirm, - onCloseBackConfirm, - t, - loading, isV2Workflow, historiesDefaultData, + t, + loading, onClickSave, + flowData2StoreDataAndCheck, + past, + saveSnapshot, + resetSnapshot, + isOpenBackConfirm, + onCloseBackConfirm, setHistoriesDefaultData, appDetail.chatConfig, - flowData2StoreDataAndCheck, setWorkflowTestData, - toast, - past + toast ]); return Render; 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 02770b3a7..421953d80 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx @@ -63,7 +63,7 @@ export type SaveSnapshotParams = { pastNodes?: Node[]; pastEdges?: Edge[]; customTitle?: string; - chatConfig?: AppChatConfigType; + chatConfig: AppChatConfigType; }; export type InitProps = { nodes: AppSchema['modules']; @@ -892,6 +892,18 @@ const WorkflowContextProvider = ({ } }); + // remove other app's snapshot + useEffect(() => { + const keys = Object.keys(localStorage); + const snapshotKeys = keys.filter((key) => key.endsWith('-past') || key.endsWith('-future')); + snapshotKeys.forEach((key) => { + const keyAppId = key.split('-')[0]; + if (keyAppId !== appId) { + localStorage.removeItem(key); + } + }); + }, [appId]); + const initData = useMemoizedFn( async (e: Parameters[0], isInit?: boolean) => { /* @@ -925,18 +937,6 @@ const WorkflowContextProvider = ({ } ); - // remove other app's snapshot - useEffect(() => { - const keys = Object.keys(localStorage); - const snapshotKeys = keys.filter((key) => key.endsWith('-past') || key.endsWith('-future')); - snapshotKeys.forEach((key) => { - const keyAppId = key.split('-')[0]; - if (keyAppId !== appId) { - localStorage.removeItem(key); - } - }); - }, [appId]); - /* Version histories */ const [historiesDefaultData, setHistoriesDefaultData] = useState(); diff --git a/projects/app/src/web/core/app/api/version.ts b/projects/app/src/web/core/app/api/version.ts index bd9717986..e9a84e2e8 100644 --- a/projects/app/src/web/core/app/api/version.ts +++ b/projects/app/src/web/core/app/api/version.ts @@ -1,13 +1,13 @@ import { PostPublishAppProps, PostRevertAppProps } from '@/global/core/app/api'; import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; -import { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; +import type { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import type { getLatestVersionQuery, getLatestVersionResponse } from '@/pages/api/core/app/version/latest'; -import { UpdateAppVersionBody } from '@/pages/api/core/app/version/update'; -import { versionListResponse } from '@/pages/api/core/app/version/listWorkflow'; +import type { UpdateAppVersionBody } from '@/pages/api/core/app/version/update'; +import type { versionListResponse } from '@/pages/api/core/app/version/list'; export const getAppLatestVersion = (data: getLatestVersionQuery) => GET('/core/app/version/latest', data); @@ -15,17 +15,11 @@ 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 getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) => - POST>('/core/app/version/listWorkflow', data); + POST>('/core/app/version/list', data); 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 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 9a9e1ce4a..959eb343c 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -440,14 +440,14 @@ export const getLatestNodeTemplate = ( export const compareSnapshot = ( snapshot1: { - nodes: Node[] | undefined; + nodes?: Node[]; edges: Edge[] | undefined; - chatConfig: AppChatConfigType | undefined; + chatConfig?: AppChatConfigType; }, snapshot2: { - nodes: Node[]; + nodes?: Node[]; edges: Edge[]; - chatConfig: AppChatConfigType; + chatConfig?: AppChatConfigType; } ) => { const clone1 = cloneDeep(snapshot1);