* fix: index

* fix: snapshot error; perf: snapshot diff compare

* perf: init simple edit history
This commit is contained in:
Archer
2024-11-21 16:26:43 +08:00
committed by shilin66
parent bdb1b6e8e8
commit c0478e73cb
11 changed files with 220 additions and 170 deletions

View File

@@ -14,4 +14,6 @@ weight: 810
3. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话。 3. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话。
4. 新增 - 分享链接支持配置是否允许查看原文。 4. 新增 - 分享链接支持配置是否允许查看原文。
5. 优化 - 工作流 ui 细节。 5. 优化 - 工作流 ui 细节。
6. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持 6. 优化 - 应用编辑记录采用 diff 存储,避免浏览器溢出
7. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持。
8. 修复 - MongoDB 知识库集合唯一索引。

View File

@@ -118,7 +118,7 @@ try {
{ {
unique: true, unique: true,
partialFilterExpression: { partialFilterExpression: {
externalFileId: { $exists: true } externalFileId: { $exists: true, $ne: '' }
} }
} }
); );

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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);
}, },

View File

@@ -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 />}

View File

@@ -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 };
}; };

View File

@@ -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 (

View File

@@ -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
};
});
};

View File

@@ -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>
) => { ) => {

View File

@@ -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
})); }));
}; };