mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 17:55:24 +00:00
Snip test (#3204)
* fix: index * fix: snapshot error; perf: snapshot diff compare * perf: init simple edit history
This commit is contained in:
@@ -14,4 +14,6 @@ weight: 810
|
|||||||
3. 新增 - 重写 chatContext,对话测试也会有日志,并且刷新后不会丢失对话。
|
3. 新增 - 重写 chatContext,对话测试也会有日志,并且刷新后不会丢失对话。
|
||||||
4. 新增 - 分享链接支持配置是否允许查看原文。
|
4. 新增 - 分享链接支持配置是否允许查看原文。
|
||||||
5. 优化 - 工作流 ui 细节。
|
5. 优化 - 工作流 ui 细节。
|
||||||
6. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持。
|
6. 优化 - 应用编辑记录采用 diff 存储,避免浏览器溢出。
|
||||||
|
7. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持。
|
||||||
|
8. 修复 - MongoDB 知识库集合唯一索引。
|
||||||
|
@@ -118,7 +118,7 @@ try {
|
|||||||
{
|
{
|
||||||
unique: true,
|
unique: true,
|
||||||
partialFilterExpression: {
|
partialFilterExpression: {
|
||||||
externalFileId: { $exists: true }
|
externalFileId: { $exists: true, $ne: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -13,11 +13,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import {
|
import { WorkflowContext, WorkflowSnapshotsType } from '../WorkflowComponents/context';
|
||||||
WorkflowContext,
|
|
||||||
WorkflowSnapshotsType,
|
|
||||||
WorkflowStateType
|
|
||||||
} from '../WorkflowComponents/context';
|
|
||||||
import { AppContext, TabEnum } from '../context';
|
import { AppContext, TabEnum } from '../context';
|
||||||
import RouteTab from '../RouteTab';
|
import RouteTab from '../RouteTab';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -38,7 +34,7 @@ import {
|
|||||||
WorkflowInitContext
|
WorkflowInitContext
|
||||||
} from '../WorkflowComponents/context/workflowInitContext';
|
} from '../WorkflowComponents/context/workflowInitContext';
|
||||||
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
||||||
import { applyDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -56,16 +52,19 @@ const Header = () => {
|
|||||||
|
|
||||||
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
|
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
|
||||||
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
|
||||||
const {
|
|
||||||
flowData2StoreData,
|
const flowData2StoreData = useContextSelector(WorkflowContext, (v) => v.flowData2StoreData);
|
||||||
flowData2StoreDataAndCheck,
|
const flowData2StoreDataAndCheck = useContextSelector(
|
||||||
setWorkflowTestData,
|
WorkflowContext,
|
||||||
past,
|
(v) => v.flowData2StoreDataAndCheck
|
||||||
future,
|
);
|
||||||
setPast,
|
const setWorkflowTestData = useContextSelector(WorkflowContext, (v) => v.setWorkflowTestData);
|
||||||
onSwitchTmpVersion,
|
const past = useContextSelector(WorkflowContext, (v) => v.past);
|
||||||
onSwitchCloudVersion
|
const future = useContextSelector(WorkflowContext, (v) => v.future);
|
||||||
} = useContextSelector(WorkflowContext, (v) => v);
|
const setPast = useContextSelector(WorkflowContext, (v) => v.setPast);
|
||||||
|
const onSwitchTmpVersion = useContextSelector(WorkflowContext, (v) => v.onSwitchTmpVersion);
|
||||||
|
const onSwitchCloudVersion = useContextSelector(WorkflowContext, (v) => v.onSwitchCloudVersion);
|
||||||
|
|
||||||
const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
|
const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
|
||||||
const setShowHistoryModal = useContextSelector(
|
const setShowHistoryModal = useContextSelector(
|
||||||
WorkflowEventContext,
|
WorkflowEventContext,
|
||||||
@@ -82,17 +81,19 @@ const Header = () => {
|
|||||||
past.find((snapshot) => snapshot.isSaved);
|
past.find((snapshot) => snapshot.isSaved);
|
||||||
|
|
||||||
const initialState = past[past.length - 1]?.state;
|
const initialState = past[past.length - 1]?.state;
|
||||||
const savedSnapshotState = applyDiff(initialState, savedSnapshot?.diff);
|
const savedSnapshotState = getAppConfigByDiff(initialState, savedSnapshot?.diff);
|
||||||
|
|
||||||
const val = compareSnapshot(
|
const val = compareSnapshot(
|
||||||
|
// nodes of the saved snapshot
|
||||||
{
|
{
|
||||||
nodes: savedSnapshotState?.nodes,
|
nodes: savedSnapshotState?.nodes,
|
||||||
edges: savedSnapshotState?.edges,
|
edges: savedSnapshotState?.edges,
|
||||||
chatConfig: savedSnapshotState?.chatConfig
|
chatConfig: savedSnapshotState?.chatConfig
|
||||||
},
|
},
|
||||||
|
// nodes of the current canvas
|
||||||
{
|
{
|
||||||
nodes: nodes,
|
nodes,
|
||||||
edges: edges,
|
edges,
|
||||||
chatConfig: appDetail.chatConfig
|
chatConfig: appDetail.chatConfig
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -140,8 +141,6 @@ const Header = () => {
|
|||||||
|
|
||||||
const onBack = useCallback(async () => {
|
const onBack = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(`${appDetail._id}-past`);
|
|
||||||
localStorage.removeItem(`${appDetail._id}-future`);
|
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/app/list',
|
pathname: '/app/list',
|
||||||
query: {
|
query: {
|
||||||
@@ -150,7 +149,7 @@ const Header = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [appDetail._id, appDetail.parentId, lastAppListRouteType, router]);
|
}, [appDetail.parentId, lastAppListRouteType, router]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
@@ -17,17 +17,44 @@ import styles from './styles.module.scss';
|
|||||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { onSaveSnapshotFnType, SimpleAppSnapshotType } from './useSnapshots';
|
import { onSaveSnapshotFnType, SimpleAppSnapshotType } from './useSnapshots';
|
||||||
import { applyDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff, getAppDiffConfig } from '@/web/core/app/diff';
|
||||||
|
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||||
|
|
||||||
|
const convertOldFormatHistory = (past: SimpleAppSnapshotType[]) => {
|
||||||
|
const baseState = past[past.length - 1].appForm;
|
||||||
|
|
||||||
|
return past.map((item, index) => {
|
||||||
|
if (index === past.length - 1) {
|
||||||
|
return {
|
||||||
|
title: item.title,
|
||||||
|
isSaved: item.isSaved,
|
||||||
|
state: baseState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentState = item.appForm;
|
||||||
|
|
||||||
|
const diff = getAppDiffConfig(baseState, currentState);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: item.title || formatTime2YMDHMS(new Date()),
|
||||||
|
isSaved: item.isSaved,
|
||||||
|
diff
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const Edit = ({
|
const Edit = ({
|
||||||
appForm,
|
appForm,
|
||||||
setAppForm,
|
setAppForm,
|
||||||
past,
|
past,
|
||||||
|
setPast,
|
||||||
saveSnapshot
|
saveSnapshot
|
||||||
}: {
|
}: {
|
||||||
appForm: AppSimpleEditFormType;
|
appForm: AppSimpleEditFormType;
|
||||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||||
past: SimpleAppSnapshotType[];
|
past: SimpleAppSnapshotType[];
|
||||||
|
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
|
||||||
saveSnapshot: onSaveSnapshotFnType;
|
saveSnapshot: onSaveSnapshotFnType;
|
||||||
}) => {
|
}) => {
|
||||||
const { isPc } = useSystem();
|
const { isPc } = useSystem();
|
||||||
@@ -40,19 +67,33 @@ const Edit = ({
|
|||||||
// show selected dataset
|
// show selected dataset
|
||||||
loadAllDatasets();
|
loadAllDatasets();
|
||||||
|
|
||||||
|
if (appDetail.version !== 'v2') {
|
||||||
|
return setAppForm(
|
||||||
|
appWorkflow2Form({
|
||||||
|
nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes,
|
||||||
|
chatConfig: appDetail.chatConfig
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest snapshot
|
||||||
|
if (past?.[0]?.diff) {
|
||||||
|
const pastState = getAppConfigByDiff(past[past.length - 1].state, past[0].diff);
|
||||||
|
|
||||||
|
return setAppForm(pastState);
|
||||||
|
} else if (past && past.length > 0 && past?.every((item) => item.appForm)) {
|
||||||
|
// 格式化成 diff
|
||||||
|
const newPast = convertOldFormatHistory(past);
|
||||||
|
|
||||||
|
setPast(newPast);
|
||||||
|
return setAppForm(getAppConfigByDiff(newPast[newPast.length - 1].state, newPast[0].diff));
|
||||||
|
}
|
||||||
|
|
||||||
const appForm = appWorkflow2Form({
|
const appForm = appWorkflow2Form({
|
||||||
nodes: appDetail.modules,
|
nodes: appDetail.modules,
|
||||||
chatConfig: appDetail.chatConfig
|
chatConfig: appDetail.chatConfig
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the latest snapshot
|
|
||||||
if (past?.[0]?.diff) {
|
|
||||||
const pastState = applyDiff(past[past.length - 1].state, past[0].diff);
|
|
||||||
|
|
||||||
return setAppForm(pastState);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAppForm(appForm);
|
|
||||||
// Set the first snapshot
|
// Set the first snapshot
|
||||||
if (past.length === 0) {
|
if (past.length === 0) {
|
||||||
saveSnapshot({
|
saveSnapshot({
|
||||||
@@ -62,14 +103,7 @@ const Edit = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appDetail.version !== 'v2') {
|
setAppForm(appForm);
|
||||||
setAppForm(
|
|
||||||
appWorkflow2Form({
|
|
||||||
nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes,
|
|
||||||
chatConfig: appDetail.chatConfig
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -29,7 +29,7 @@ import {
|
|||||||
} from './useSnapshots';
|
} from './useSnapshots';
|
||||||
import PublishHistories from '../PublishHistoriesSlider';
|
import PublishHistories from '../PublishHistoriesSlider';
|
||||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||||
import { applyDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
||||||
|
|
||||||
const Header = ({
|
const Header = ({
|
||||||
forbiddenSaveSnapshot,
|
forbiddenSaveSnapshot,
|
||||||
@@ -49,20 +49,13 @@ const Header = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isPc } = useSystem();
|
const { isPc } = useSystem();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { appId, onSaveApp, currentTab, appLatestVersion } = useContextSelector(
|
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||||
AppContext,
|
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
|
||||||
(v) => v
|
const currentTab = useContextSelector(AppContext, (v) => v.currentTab);
|
||||||
);
|
const appLatestVersion = useContextSelector(AppContext, (v) => v.appLatestVersion);
|
||||||
|
|
||||||
const { lastAppListRouteType } = useSystemStore();
|
const { lastAppListRouteType } = useSystemStore();
|
||||||
const { allDatasets } = useDatasetStore();
|
const { allDatasets } = useDatasetStore();
|
||||||
const initialAppForm = useMemo(
|
|
||||||
() =>
|
|
||||||
appWorkflow2Form({
|
|
||||||
nodes: appLatestVersion?.nodes || [],
|
|
||||||
chatConfig: appLatestVersion?.chatConfig || {}
|
|
||||||
}),
|
|
||||||
[appLatestVersion]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), {
|
const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), {
|
||||||
manual: false,
|
manual: false,
|
||||||
@@ -114,9 +107,18 @@ const Header = ({
|
|||||||
const [isShowHistories, { setTrue: setIsShowHistories, setFalse: closeHistories }] =
|
const [isShowHistories, { setTrue: setIsShowHistories, setFalse: closeHistories }] =
|
||||||
useBoolean(false);
|
useBoolean(false);
|
||||||
|
|
||||||
|
const initialAppForm = useMemo(
|
||||||
|
() =>
|
||||||
|
appWorkflow2Form({
|
||||||
|
nodes: appLatestVersion?.nodes || [],
|
||||||
|
chatConfig: appLatestVersion?.chatConfig || {}
|
||||||
|
}),
|
||||||
|
[appLatestVersion]
|
||||||
|
);
|
||||||
|
|
||||||
const onSwitchTmpVersion = useCallback(
|
const onSwitchTmpVersion = useCallback(
|
||||||
(data: SimpleAppSnapshotType, customTitle: string) => {
|
(data: SimpleAppSnapshotType, customTitle: string) => {
|
||||||
const pastState = applyDiff(initialAppForm, data.diff);
|
const pastState = getAppConfigByDiff(initialAppForm, data.diff);
|
||||||
setAppForm(pastState);
|
setAppForm(pastState);
|
||||||
|
|
||||||
// Remove multiple "copy-"
|
// Remove multiple "copy-"
|
||||||
@@ -156,7 +158,7 @@ const Header = ({
|
|||||||
useDebounceEffect(
|
useDebounceEffect(
|
||||||
() => {
|
() => {
|
||||||
const savedSnapshot = past.find((snapshot) => snapshot.isSaved);
|
const savedSnapshot = past.find((snapshot) => snapshot.isSaved);
|
||||||
const pastState = applyDiff(initialAppForm, savedSnapshot?.diff);
|
const pastState = getAppConfigByDiff(initialAppForm, savedSnapshot?.diff);
|
||||||
const val = compareSimpleAppSnapshot(pastState, appForm);
|
const val = compareSimpleAppSnapshot(pastState, appForm);
|
||||||
setIsPublished(val);
|
setIsPublished(val);
|
||||||
},
|
},
|
||||||
|
@@ -49,7 +49,13 @@ const SimpleEdit = () => {
|
|||||||
saveSnapshot={saveSnapshot}
|
saveSnapshot={saveSnapshot}
|
||||||
/>
|
/>
|
||||||
{currentTab === TabEnum.appEdit ? (
|
{currentTab === TabEnum.appEdit ? (
|
||||||
<Edit appForm={appForm} setAppForm={setAppForm} past={past} saveSnapshot={saveSnapshot} />
|
<Edit
|
||||||
|
appForm={appForm}
|
||||||
|
setAppForm={setAppForm}
|
||||||
|
past={past}
|
||||||
|
setPast={setPast}
|
||||||
|
saveSnapshot={saveSnapshot}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box flex={'1 0 0'} h={0} mt={[4, 0]}>
|
<Box flex={'1 0 0'} h={0} mt={[4, 0]}>
|
||||||
{currentTab === TabEnum.publish && <PublishChannel />}
|
{currentTab === TabEnum.publish && <PublishChannel />}
|
||||||
|
@@ -3,16 +3,19 @@ import { SetStateAction, useEffect, useRef } from 'react';
|
|||||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { applyDiff, createDiff } from '@/web/core/app/diff';
|
import { getAppDiffConfig } from '@/web/core/app/diff';
|
||||||
|
|
||||||
export type SimpleAppSnapshotType = {
|
export type SimpleAppSnapshotType = {
|
||||||
diff?: Record<string, any>;
|
diff?: Record<string, any>;
|
||||||
title: string;
|
title: string;
|
||||||
isSaved?: boolean;
|
isSaved?: boolean;
|
||||||
state?: AppSimpleEditFormType;
|
state?: AppSimpleEditFormType;
|
||||||
|
|
||||||
|
// old format
|
||||||
|
appForm?: AppSimpleEditFormType;
|
||||||
};
|
};
|
||||||
export type onSaveSnapshotFnType = (props: {
|
export type onSaveSnapshotFnType = (props: {
|
||||||
appForm: AppSimpleEditFormType;
|
appForm: AppSimpleEditFormType; // Current edited app form data
|
||||||
title?: string;
|
title?: string;
|
||||||
isSaved?: boolean;
|
isSaved?: boolean;
|
||||||
}) => Promise<boolean>;
|
}) => Promise<boolean>;
|
||||||
@@ -58,7 +61,7 @@ export const compareSimpleAppSnapshot = (
|
|||||||
|
|
||||||
export const useSimpleAppSnapshots = (appId: string) => {
|
export const useSimpleAppSnapshots = (appId: string) => {
|
||||||
const forbiddenSaveSnapshot = useRef(false);
|
const forbiddenSaveSnapshot = useRef(false);
|
||||||
const [past, setPast] = useLocalStorageState<SimpleAppSnapshotType[]>(`${appId}-past-simple`, {
|
const [past, setPast] = useLocalStorageState<SimpleAppSnapshotType[]>(`${appId}-past`, {
|
||||||
defaultValue: []
|
defaultValue: []
|
||||||
}) as [SimpleAppSnapshotType[], (value: SetStateAction<SimpleAppSnapshotType[]>) => void];
|
}) as [SimpleAppSnapshotType[], (value: SetStateAction<SimpleAppSnapshotType[]>) => void];
|
||||||
|
|
||||||
@@ -79,33 +82,36 @@ export const useSimpleAppSnapshots = (appId: string) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = past[past.length - 1].state;
|
const lastPast = past[past.length - 1];
|
||||||
if (!initialState) return false;
|
if (!lastPast?.state) return false;
|
||||||
|
|
||||||
if (past.length > 0) {
|
// Get the diff between the current app form data and the initial state
|
||||||
const pastState = applyDiff(initialState, past[0].diff);
|
const diff = getAppDiffConfig(lastPast.state, appForm);
|
||||||
|
|
||||||
const isPastEqual = compareSimpleAppSnapshot(pastState, appForm);
|
// If the diff is the same as the previous snapshot, do not save
|
||||||
if (isPastEqual) return false;
|
if (past[0].diff && isEqual(past[0].diff, diff)) return false;
|
||||||
}
|
|
||||||
|
|
||||||
const diff = createDiff(initialState, appForm);
|
setPast((past) => {
|
||||||
|
const newPast = {
|
||||||
setPast((past) => [
|
|
||||||
{
|
|
||||||
diff,
|
diff,
|
||||||
title: title || formatTime2YMDHMS(new Date()),
|
title: title || formatTime2YMDHMS(new Date()),
|
||||||
isSaved
|
isSaved
|
||||||
},
|
};
|
||||||
...past.slice(0, 199)
|
|
||||||
]);
|
if (past.length >= 100) {
|
||||||
|
return [newPast, ...past.slice(0, 98), lastPast];
|
||||||
|
}
|
||||||
|
return [newPast, ...past];
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove other app's snapshot
|
// remove other app's snapshot
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const keys = Object.keys(localStorage);
|
const keys = Object.keys(localStorage);
|
||||||
const snapshotKeys = keys.filter((key) => key.endsWith('-past-simple'));
|
const snapshotKeys = keys.filter(
|
||||||
|
(key) => key.endsWith('-past') || key.endsWith('-past-simple')
|
||||||
|
);
|
||||||
snapshotKeys.forEach((key) => {
|
snapshotKeys.forEach((key) => {
|
||||||
const keyAppId = key.split('-')[0];
|
const keyAppId = key.split('-')[0];
|
||||||
if (keyAppId !== appId) {
|
if (keyAppId !== appId) {
|
||||||
@@ -114,6 +120,20 @@ export const useSimpleAppSnapshots = (appId: string) => {
|
|||||||
});
|
});
|
||||||
}, [appId]);
|
}, [appId]);
|
||||||
|
|
||||||
|
// 旧的编辑记录,直接重置到新的变量中
|
||||||
|
const [oldPast, setOldPast] = useLocalStorageState<SimpleAppSnapshotType[]>(
|
||||||
|
`${appId}-past-simple`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (oldPast && oldPast.length > 0) {
|
||||||
|
setPast(past);
|
||||||
|
setOldPast([]);
|
||||||
|
// refresh page
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}, [oldPast]);
|
||||||
|
|
||||||
return { forbiddenSaveSnapshot, past, setPast, saveSnapshot };
|
return { forbiddenSaveSnapshot, past, setPast, saveSnapshot };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext, WorkflowStateType } from '../WorkflowComponents/context';
|
import { WorkflowContext } from '../WorkflowComponents/context';
|
||||||
import { AppContext, TabEnum } from '../context';
|
import { AppContext, TabEnum } from '../context';
|
||||||
import RouteTab from '../RouteTab';
|
import RouteTab from '../RouteTab';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
WorkflowInitContext
|
WorkflowInitContext
|
||||||
} from '../WorkflowComponents/context/workflowInitContext';
|
} from '../WorkflowComponents/context/workflowInitContext';
|
||||||
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
||||||
import { applyDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -56,16 +56,19 @@ const Header = () => {
|
|||||||
|
|
||||||
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
|
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
|
||||||
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
|
||||||
const {
|
|
||||||
flowData2StoreData,
|
const flowData2StoreData = useContextSelector(WorkflowContext, (v) => v.flowData2StoreData);
|
||||||
flowData2StoreDataAndCheck,
|
const flowData2StoreDataAndCheck = useContextSelector(
|
||||||
setWorkflowTestData,
|
WorkflowContext,
|
||||||
past,
|
(v) => v.flowData2StoreDataAndCheck
|
||||||
future,
|
);
|
||||||
setPast,
|
const setWorkflowTestData = useContextSelector(WorkflowContext, (v) => v.setWorkflowTestData);
|
||||||
onSwitchTmpVersion,
|
const past = useContextSelector(WorkflowContext, (v) => v.past);
|
||||||
onSwitchCloudVersion
|
const future = useContextSelector(WorkflowContext, (v) => v.future);
|
||||||
} = useContextSelector(WorkflowContext, (v) => v);
|
const setPast = useContextSelector(WorkflowContext, (v) => v.setPast);
|
||||||
|
const onSwitchTmpVersion = useContextSelector(WorkflowContext, (v) => v.onSwitchTmpVersion);
|
||||||
|
const onSwitchCloudVersion = useContextSelector(WorkflowContext, (v) => v.onSwitchCloudVersion);
|
||||||
|
|
||||||
const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
|
const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
|
||||||
const setShowHistoryModal = useContextSelector(
|
const setShowHistoryModal = useContextSelector(
|
||||||
WorkflowEventContext,
|
WorkflowEventContext,
|
||||||
@@ -83,7 +86,7 @@ const Header = () => {
|
|||||||
past.find((snapshot) => snapshot.isSaved);
|
past.find((snapshot) => snapshot.isSaved);
|
||||||
|
|
||||||
const initialState = past[past.length - 1]?.state;
|
const initialState = past[past.length - 1]?.state;
|
||||||
const savedSnapshotState = applyDiff(initialState, savedSnapshot?.diff);
|
const savedSnapshotState = getAppConfigByDiff(initialState, savedSnapshot?.diff);
|
||||||
|
|
||||||
const val = compareSnapshot(
|
const val = compareSnapshot(
|
||||||
{
|
{
|
||||||
@@ -141,8 +144,6 @@ const Header = () => {
|
|||||||
|
|
||||||
const onBack = useCallback(async () => {
|
const onBack = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(`${appDetail._id}-past`);
|
|
||||||
localStorage.removeItem(`${appDetail._id}-future`);
|
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/app/list',
|
pathname: '/app/list',
|
||||||
query: {
|
query: {
|
||||||
@@ -151,7 +152,7 @@ const Header = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [appDetail._id, appDetail.parentId, lastAppListRouteType, router]);
|
}, [appDetail.parentId, lastAppListRouteType, router]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { postWorkflowDebug } from '@/web/core/workflow/api';
|
import { postWorkflowDebug } from '@/web/core/workflow/api';
|
||||||
import {
|
import {
|
||||||
checkWorkflowNodeAndConnection,
|
checkWorkflowNodeAndConnection,
|
||||||
compareSnapshot,
|
simplifyWorkflowNodes,
|
||||||
simplifyNodes,
|
|
||||||
storeEdgesRenderEdge,
|
storeEdgesRenderEdge,
|
||||||
storeNode2FlowNode
|
storeNode2FlowNode
|
||||||
} from '@/web/core/workflow/utils';
|
} from '@/web/core/workflow/utils';
|
||||||
@@ -38,11 +37,11 @@ import { useDisclosure } from '@chakra-ui/react';
|
|||||||
import { uiWorkflow2StoreWorkflow } from '../utils';
|
import { uiWorkflow2StoreWorkflow } from '../utils';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep, isEqual } from 'lodash';
|
||||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||||
import WorkflowInitContextProvider, { WorkflowNodeEdgeContext } from './workflowInitContext';
|
import WorkflowInitContextProvider, { WorkflowNodeEdgeContext } from './workflowInitContext';
|
||||||
import WorkflowEventContextProvider from './workflowEventContext';
|
import WorkflowEventContextProvider from './workflowEventContext';
|
||||||
import { applyDiff, createDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff, getAppDiffConfig } from '@/web/core/app/diff';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Context
|
Context
|
||||||
@@ -773,53 +772,38 @@ const WorkflowContextProvider = ({
|
|||||||
const pushPastSnapshot = useMemoizedFn(
|
const pushPastSnapshot = useMemoizedFn(
|
||||||
({ pastNodes, pastEdges, chatConfig, customTitle, isSaved }) => {
|
({ pastNodes, pastEdges, chatConfig, customTitle, isSaved }) => {
|
||||||
if (!pastNodes || !pastEdges || !chatConfig) return false;
|
if (!pastNodes || !pastEdges || !chatConfig) return false;
|
||||||
|
|
||||||
if (forbiddenSaveSnapshot.current) {
|
if (forbiddenSaveSnapshot.current) {
|
||||||
forbiddenSaveSnapshot.current = false;
|
forbiddenSaveSnapshot.current = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get initial state
|
// Get initial state
|
||||||
const initialState = past[past.length - 1]?.state;
|
const lastSnapshot = past[past.length - 1];
|
||||||
if (!initialState) return false;
|
if (!lastSnapshot?.state) return false;
|
||||||
|
|
||||||
// Apply latest diff to get past state
|
|
||||||
const pastState = applyDiff(initialState, past[0].diff);
|
|
||||||
|
|
||||||
const isPastEqual = compareSnapshot(
|
|
||||||
{
|
|
||||||
nodes: pastNodes,
|
|
||||||
edges: pastEdges,
|
|
||||||
chatConfig: chatConfig
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodes: pastState?.nodes,
|
|
||||||
edges: pastState?.edges,
|
|
||||||
chatConfig: pastState?.chatConfig
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isPastEqual) return false;
|
|
||||||
|
|
||||||
// Create current state object
|
// Create current state object
|
||||||
const newState = {
|
const newState = {
|
||||||
nodes: simplifyNodes(pastNodes),
|
nodes: simplifyWorkflowNodes(pastNodes),
|
||||||
edges: pastEdges,
|
edges: pastEdges,
|
||||||
chatConfig
|
chatConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate diff from initial state
|
// Calculate diff from initial state
|
||||||
const diff = createDiff(initialState, newState);
|
const diff = getAppDiffConfig(lastSnapshot.state, newState);
|
||||||
|
if (past[0].diff && isEqual(past[0].diff, diff)) return false;
|
||||||
|
|
||||||
setFuture([]);
|
setFuture([]);
|
||||||
setPast((past) => [
|
setPast((past) => {
|
||||||
{
|
const newPast = {
|
||||||
diff,
|
diff,
|
||||||
title: customTitle || formatTime2YMDHMS(new Date()),
|
title: customTitle || formatTime2YMDHMS(new Date()),
|
||||||
isSaved
|
isSaved
|
||||||
},
|
};
|
||||||
...past.slice(0, 199)
|
if (past.length >= 100) {
|
||||||
]);
|
return [newPast, ...past.slice(0, 98), lastSnapshot];
|
||||||
|
}
|
||||||
|
return [newPast, ...past];
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -830,7 +814,7 @@ const WorkflowContextProvider = ({
|
|||||||
const copyText = t('app:version_copy');
|
const copyText = t('app:version_copy');
|
||||||
const regex = new RegExp(`(${copyText}-)\\1+`, 'g');
|
const regex = new RegExp(`(${copyText}-)\\1+`, 'g');
|
||||||
const title = customTitle.replace(regex, `$1`);
|
const title = customTitle.replace(regex, `$1`);
|
||||||
const pastState = applyDiff(past[past.length - 1].state, params.diff);
|
const pastState = getAppConfigByDiff(past[past.length - 1].state, params.diff);
|
||||||
|
|
||||||
resetSnapshot(pastState);
|
resetSnapshot(pastState);
|
||||||
|
|
||||||
@@ -863,14 +847,14 @@ const WorkflowContextProvider = ({
|
|||||||
if (past[1]) {
|
if (past[1]) {
|
||||||
setFuture((future) => [past[0], ...future]);
|
setFuture((future) => [past[0], ...future]);
|
||||||
setPast((past) => past.slice(1));
|
setPast((past) => past.slice(1));
|
||||||
const pastState = applyDiff(past[past.length - 1].state, past[1].diff);
|
const pastState = getAppConfigByDiff(past[past.length - 1].state, past[1].diff);
|
||||||
resetSnapshot(pastState);
|
resetSnapshot(pastState);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const redo = useMemoizedFn(() => {
|
const redo = useMemoizedFn(() => {
|
||||||
if (!future[0]) return;
|
if (!future[0]) return;
|
||||||
|
|
||||||
const futureState = applyDiff(past[past.length - 1].state, future[0].diff);
|
const futureState = getAppConfigByDiff(past[past.length - 1].state, future[0].diff);
|
||||||
|
|
||||||
if (futureState) {
|
if (futureState) {
|
||||||
setPast((past) => [future[0], ...past]);
|
setPast((past) => [future[0], ...past]);
|
||||||
@@ -892,39 +876,6 @@ const WorkflowContextProvider = ({
|
|||||||
});
|
});
|
||||||
}, [appId]);
|
}, [appId]);
|
||||||
|
|
||||||
// Convert old history format to new format
|
|
||||||
const convertOldFormatHistory = (past: WorkflowSnapshotsType[]) => {
|
|
||||||
const baseState = {
|
|
||||||
nodes: past[past.length - 1].state?.nodes || [],
|
|
||||||
edges: past[past.length - 1].state?.edges || [],
|
|
||||||
chatConfig: past[past.length - 1].state?.chatConfig || {}
|
|
||||||
};
|
|
||||||
|
|
||||||
return past.map((item, index) => {
|
|
||||||
if (index === past.length - 1) {
|
|
||||||
return {
|
|
||||||
title: item.title,
|
|
||||||
isSaved: item.isSaved,
|
|
||||||
state: baseState
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentState = {
|
|
||||||
nodes: item.nodes || [],
|
|
||||||
edges: item.edges || [],
|
|
||||||
chatConfig: item.chatConfig || {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const diff = createDiff(baseState, currentState);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: item.title || formatTime2YMDHMS(new Date()),
|
|
||||||
isSaved: item.isSaved,
|
|
||||||
diff
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const initData = useCallback(
|
const initData = useCallback(
|
||||||
async (
|
async (
|
||||||
e: {
|
e: {
|
||||||
@@ -938,15 +889,15 @@ const WorkflowContextProvider = ({
|
|||||||
const edges = e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || [];
|
const edges = e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || [];
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
nodes: simplifyNodes(nodes),
|
nodes: simplifyWorkflowNodes(nodes),
|
||||||
edges,
|
edges,
|
||||||
chatConfig: e.chatConfig || appDetail.chatConfig
|
chatConfig: e.chatConfig || appDetail.chatConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isInit && past.length > 0) {
|
if (isInit && past.length > 0) {
|
||||||
// new format
|
// new format
|
||||||
if (past[0].diff) {
|
if (past[0].diff && past[past.length - 1].state) {
|
||||||
const targetState = applyDiff(
|
const targetState = getAppConfigByDiff(
|
||||||
past[past.length - 1].state,
|
past[past.length - 1].state,
|
||||||
past[0].diff
|
past[0].diff
|
||||||
) as WorkflowStateType;
|
) as WorkflowStateType;
|
||||||
@@ -960,13 +911,13 @@ const WorkflowContextProvider = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// old format
|
// 适配旧的编辑记录(4.8.15去除)
|
||||||
if (past.some((item) => !item.state && (item.nodes || item.edges))) {
|
if (past.every((item) => item.nodes)) {
|
||||||
const newPast = convertOldFormatHistory(past);
|
const newPast = convertOldFormatHistory(past);
|
||||||
|
|
||||||
setPast(newPast);
|
setPast(newPast);
|
||||||
|
|
||||||
const latestState = applyDiff(
|
const latestState = getAppConfigByDiff(
|
||||||
newPast[newPast.length - 1].state,
|
newPast[newPast.length - 1].state,
|
||||||
newPast[0].diff
|
newPast[0].diff
|
||||||
) as WorkflowStateType;
|
) as WorkflowStateType;
|
||||||
@@ -1084,3 +1035,36 @@ const WorkflowContextProvider = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default React.memo(WorkflowContextProvider);
|
export default React.memo(WorkflowContextProvider);
|
||||||
|
|
||||||
|
// Convert old history format to new format
|
||||||
|
const convertOldFormatHistory = (past: WorkflowSnapshotsType[]) => {
|
||||||
|
const baseState = {
|
||||||
|
nodes: past[past.length - 1].state?.nodes || [],
|
||||||
|
edges: past[past.length - 1].state?.edges || [],
|
||||||
|
chatConfig: past[past.length - 1].state?.chatConfig || {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return past.map((item, index) => {
|
||||||
|
if (index === past.length - 1) {
|
||||||
|
return {
|
||||||
|
title: item.title,
|
||||||
|
isSaved: item.isSaved,
|
||||||
|
state: baseState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentState = {
|
||||||
|
nodes: item.nodes || [],
|
||||||
|
edges: item.edges || [],
|
||||||
|
chatConfig: item.chatConfig || {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const diff = getAppDiffConfig(baseState, currentState);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: item.title || formatTime2YMDHMS(new Date()),
|
||||||
|
isSaved: item.isSaved,
|
||||||
|
diff
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@@ -8,11 +8,14 @@ const createWorkflowDiffPatcher = () =>
|
|||||||
|
|
||||||
const diffPatcher = createWorkflowDiffPatcher();
|
const diffPatcher = createWorkflowDiffPatcher();
|
||||||
|
|
||||||
export const createDiff = <T extends Record<string, unknown>>(initialState?: T, newState?: T) => {
|
export const getAppDiffConfig = <T extends Record<string, unknown>>(
|
||||||
|
initialState?: T,
|
||||||
|
newState?: T
|
||||||
|
) => {
|
||||||
return diffPatcher.diff(initialState, newState);
|
return diffPatcher.diff(initialState, newState);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const applyDiff = <T extends Record<string, unknown>>(
|
export const getAppConfigByDiff = <T extends Record<string, unknown>>(
|
||||||
initialState?: T,
|
initialState?: T,
|
||||||
diff?: ReturnType<typeof diffPatcher.diff>
|
diff?: ReturnType<typeof diffPatcher.diff>
|
||||||
) => {
|
) => {
|
||||||
|
@@ -633,12 +633,11 @@ export const compareSnapshot = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
// remove node size
|
// remove node size
|
||||||
export const simplifyNodes = (nodes: Node[]) => {
|
export const simplifyWorkflowNodes = (nodes: Node[]) => {
|
||||||
return nodes.map((node) => ({
|
return nodes.map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: node.type,
|
type: node.type,
|
||||||
position: node.position,
|
position: node.position,
|
||||||
data: node.data,
|
data: node.data
|
||||||
zIndex: node.zIndex
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user