Commit 86436d55 by Archer Committed by GitHub

4.8.11 test (#2794)

* perf: version list type

* perf: add node default value

* perf: snapshot status

* fix: version detail auth

* fix: export defalt
parent e31d6ec2
......@@ -50,5 +50,73 @@
"export default ContextProvider"
],
"description": "FastGPT usecontext template"
},
"Jest test template": {
"scope": "typescriptreact",
"prefix": "jesttest",
"body": [
"import '@/pages/api/__mocks__/base';",
"import { root } from '@/pages/api/__mocks__/db/init';",
"import { getTestRequest } from '@/test/utils';",
"import { AppErrEnum } from '@fastgpt/global/common/error/code/app';",
"import handler from './demo';",
"",
"// Import the schema",
"import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';",
"",
"beforeAll(async () => {",
" // await MongoOutLink.create({",
" // shareId: 'aaa',",
" // appId: root.appId,",
" // tmbId: root.tmbId,",
" // teamId: root.teamId,",
" // type: 'share',",
" // name: 'aaa'",
" // })",
"});",
"",
"test('Should return a list of outLink', async () => {",
" // Mock request",
" const res = (await handler(",
" ...getTestRequest({",
" query: {",
" appId: root.appId,",
" type: 'share'",
" },",
" user: root",
" })",
" )) as any;",
"",
" expect(res.code).toBe(200);",
" expect(res.data.length).toBe(2);",
"});",
"",
"test('appId is required', async () => {",
" const res = (await handler(",
" ...getTestRequest({",
" query: {",
" type: 'share'",
" },",
" user: root",
" })",
" )) as any;",
" expect(res.code).toBe(500);",
" expect(res.error).toBe(AppErrEnum.unExist);",
"});",
"",
"test('if type is not provided, return nothing', async () => {",
" const res = (await handler(",
" ...getTestRequest({",
" query: {",
" appId: root.appId",
" },",
" user: root",
" })",
" )) as any;",
" expect(res.code).toBe(200);",
" expect(res.data.length).toBe(0);",
"});"
]
}
}
\ No newline at end of file
......@@ -29,6 +29,10 @@ Note: If the Node version is >= 20, you need to pass the `--no-node-snapshot` pa
NODE_OPTIONS=--no-node-snapshot pnpm i
```
### Jest
https://fael3z0zfze.feishu.cn/docx/ZOI1dABpxoGhS7xzhkXcKPxZnDL
## I18N
### Install i18n-ally Plugin
......
......@@ -12,3 +12,12 @@ export type AppVersionSchemaType = {
versionName: string;
tmbId: string;
};
export type VersionListItemType = {
_id: string;
appId: string;
versionName: string;
time: Date;
isPublish: boolean | undefined;
tmbId: string;
};
......@@ -5,11 +5,11 @@
"app.Version name": "Version Name",
"app.modules.click to update": "Click to Refresh",
"app.modules.has new version": "New Version Available",
"app.version_back": "Revert to Original State",
"app.version_copy": "Duplicate",
"version_back": "Revert to Original State",
"version_copy": "Duplicate",
"app.version_current": "Current Version",
"app.version_initial": "Initial Version",
"app.version_initial_copy": "Duplicate - Original State",
"version_initial_copy": "Duplicate - Original State",
"app.version_name_tips": "Version name cannot be empty",
"app.version_past": "Previously Published",
"app.version_publish_tips": "This version will be saved to the team cloud, synchronized with the entire team, and update the app version on all release channels.",
......@@ -51,6 +51,7 @@
"import_configs": "Import Configurations",
"import_configs_failed": "Import configuration failed, please ensure the configuration is correct!",
"import_configs_success": "Import Successful",
"initial_form": "initial state",
"interval.12_hours": "Every 12 Hours",
"interval.2_hours": "Every 2 Hours",
"interval.3_hours": "Every 3 Hours",
......@@ -87,11 +88,11 @@
"search_app": "Search Application",
"setting_app": "Application Settings",
"setting_plugin": "Plugin Settings",
"template.simple_robot": "Simple robot",
"template.hard_strict": "Strict Q&A template",
"template.hard_strict_des": "Based on the question and answer template, stricter requirements are imposed on the model's answers.",
"template.qa_template": "Q&A template",
"template.qa_template_des": "A knowledge base suitable for QA question and answer structure, which allows AI to answer strictly according to preset content",
"template.simple_robot": "Simple robot",
"template.standard_strict": "Standard strict template",
"template.standard_strict_des": "Based on the standard template, stricter requirements are imposed on the model's answers.",
"template.standard_template": "Standard template",
......@@ -154,4 +155,4 @@
"workflow.user_file_input_desc": "Links to documents and images uploaded by users.",
"workflow.user_select": "User Selection",
"workflow.user_select_tip": "This module can configure multiple options for selection during the dialogue. Different options can lead to different workflow branches."
}
\ No newline at end of file
}
......@@ -5,11 +5,11 @@
"app.Version name": "版本名称",
"app.modules.click to update": "点击更新",
"app.modules.has new version": "有新版本",
"app.version_back": "回到初始状态",
"app.version_copy": "副本",
"version_back": "回到初始状态",
"version_copy": "副本",
"app.version_current": "当前版本",
"app.version_initial": "初始版本",
"app.version_initial_copy": "副本-初始状态",
"version_initial_copy": "副本-初始状态",
"app.version_name_tips": "版本名称不能为空",
"app.version_past": "发布过",
"app.version_publish_tips": "该版本将被保存至团队云端,同步给整个团队,同时更新所有发布渠道的应用版本",
......@@ -51,6 +51,7 @@
"import_configs": "导入配置",
"import_configs_failed": "导入配置失败,请确保配置正常!",
"import_configs_success": "导入成功",
"initial_form": "初始状态",
"interval.12_hours": "每12小时",
"interval.2_hours": "每2小时",
"interval.3_hours": "每3小时",
......
......@@ -43,6 +43,4 @@ export const initMockData = async () => {
root.tmbId = rootTeamMember._id;
root.teamId = rootTeam._id;
root.appId = rootApp._id;
await Promise.all([rootUser.save(), rootTeam.save(), rootTeamMember.save(), rootApp.save()]);
};
import '@/pages/api/__mocks__/base';
import { root } from '@/pages/api/__mocks__/db/init';
import { getTestRequest } from '@/test/utils';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import handler from './demo';
// Import the schema
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
beforeAll(async () => {
// await MongoOutLink.create({
// shareId: 'aaa',
// appId: root.appId,
// tmbId: root.tmbId,
// teamId: root.teamId,
// type: 'share',
// name: 'aaa'
// })
});
test('Should return a list of outLink', async () => {
// Mock request
const res = (await handler(
...getTestRequest({
query: {
appId: root.appId,
type: 'share'
},
user: root
})
)) as any;
expect(res.code).toBe(200);
expect(res.data.length).toBe(2);
});
test('appId is required', async () => {
const res = (await handler(
...getTestRequest({
query: {
type: 'share'
},
user: root
})
)) as any;
expect(res.code).toBe(500);
expect(res.error).toBe(AppErrEnum.unExist);
});
test('if type is not provided, return nothing', async () => {
const res = (await handler(
...getTestRequest({
query: {
appId: root.appId
},
user: root
})
)) as any;
expect(res.code).toBe(200);
expect(res.data.length).toBe(0);
});
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
export type demoQuery = {};
export type demoBody = {};
export type demoResponse = {};
async function handler(
req: ApiRequestProps<demoBody, demoQuery>,
res: ApiResponseType<any>
): Promise<demoResponse> {
return {};
}
export default NextAPI(handler);
......@@ -2,20 +2,32 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
type Props = {
versionId: string;
appId: string;
};
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
async function handler(
req: NextApiRequest,
res: NextApiResponse<any>
): Promise<AppVersionSchemaType> {
const { versionId, appId } = req.query as Props;
await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
const result = await MongoAppVersion.findById(versionId);
await authApp({ req, authToken: true, appId, per: WritePermissionVal });
const result = await MongoAppVersion.findById(versionId).lean();
return result;
if (!result) {
return Promise.reject('version not found');
}
return {
...result,
versionName: result?.versionName || formatTime2YMDHM(result?.time)
};
}
export default NextAPI(handler);
import '@/pages/api/__mocks__/base';
import { root } from '@/pages/api/__mocks__/db/init';
import { getTestRequest } from '@/test/utils';
import handler, { versionListBody, versionListResponse } from './list';
// Import the schema
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
const total = 22;
beforeAll(async () => {
const arr = new Array(total).fill(0);
await MongoAppVersion.insertMany(
arr.map((_, index) => ({
appId: root.appId,
nodes: [],
edges: [],
chatConfig: {},
isPublish: index % 2 === 0,
versionName: `v` + index,
tmbId: root.tmbId,
time: new Date(index * 1000)
}))
);
});
test('Get version list and check', async () => {
const offset = 0;
const pageSize = 10;
const _res = (await handler(
...getTestRequest<{}, versionListBody>({
body: {
offset,
pageSize,
appId: root.appId
},
user: root
})
)) as any;
const res = _res.data as versionListResponse;
expect(res.total).toBe(total);
expect(res.list.length).toBe(pageSize);
expect(res.list[0].versionName).toBe('v21');
expect(res.list[9].versionName).toBe('v12');
});
test('Get version list with offset 20', async () => {
const offset = 20;
const pageSize = 10;
const _res = (await handler(
...getTestRequest<{}, versionListBody>({
body: {
offset,
pageSize,
appId: root.appId
},
user: root
})
)) as any;
const res = _res.data as versionListResponse;
expect(res.total).toBe(total);
expect(res.list.length).toBe(2);
expect(res.list[0].versionName).toBe('v1');
expect(res.list[1].versionName).toBe('v0');
});
import type { NextApiRequest, NextApiResponse } from 'next';
import type { 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 { ApiRequestProps } from '@fastgpt/service/type/next';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { VersionListItemType } from '@fastgpt/global/core/app/version';
type Props = PaginationProps<{
export type versionListBody = PaginationProps<{
appId: string;
}>;
export type versionListResponse = {
_id: string;
appId: string;
versionName: string;
time: Date;
isPublish: boolean | undefined;
tmbId: string;
};
type Response = PaginationResponse<versionListResponse>;
export type versionListResponse = PaginationResponse<VersionListItemType>;
async function handler(req: ApiRequestProps<Props>, res: NextApiResponse<any>): Promise<Response> {
async function handler(
req: ApiRequestProps<versionListBody>,
res: NextApiResponse<any>
): Promise<versionListResponse> {
const { offset, pageSize, appId } = req.body;
await authApp({ appId, req, per: WritePermissionVal, authToken: true });
const [result, total] = await Promise.all([
MongoAppVersion.find(
{
......
......@@ -10,17 +10,15 @@ import {
useDisclosure
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context';
import { WorkflowContext, WorkflowSnapshotsType } from '../WorkflowComponents/context';
import { AppContext, TabEnum } from '../context';
import RouteTab from '../RouteTab';
import { useRouter } from 'next/router';
import AppCard from '../WorkflowComponents/AppCard';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyModal from '@fastgpt/web/components/common/MyModal';
......@@ -30,8 +28,7 @@ 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('../PublishHistoriesSlider'));
import PublishHistories from '../PublishHistoriesSlider';
const Header = () => {
const { t } = useTranslation();
......@@ -51,15 +48,15 @@ const Header = () => {
flowData2StoreData,
flowData2StoreDataAndCheck,
setWorkflowTestData,
setHistoriesDefaultData,
historiesDefaultData,
setShowHistoryModal,
showHistoryModal,
nodes,
edges,
past,
future,
setPast,
saveSnapshot,
resetSnapshot
onSwitchTmpVersion,
onSwitchCloudVersion
} = useContextSelector(WorkflowContext, (v) => v);
const { lastAppListRouteType } = useSystemStore();
......@@ -187,21 +184,15 @@ const Header = () => {
{currentTab === TabEnum.appEdit && (
<HStack flexDirection={['column', 'row']} spacing={[2, 3]}>
{!historiesDefaultData && (
{!showHistoryModal && (
<IconButton
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'}
w={'30px'}
variant={'whitePrimary'}
onClick={async () => {
const { nodes, edges } = uiWorkflow2StoreWorkflow(await getWorkflowStore());
setHistoriesDefaultData({
nodes,
edges,
chatConfig: appDetail.chatConfig
});
onClick={() => {
setShowHistoryModal(true);
}}
/>
)}
......@@ -218,7 +209,7 @@ const Header = () => {
>
{t('common:core.workflow.Run')}
</Button>
{!historiesDefaultData && (
{!showHistoryModal && (
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
......@@ -228,47 +219,6 @@ const Header = () => {
</HStack>
)}
</Flex>
{historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit && (
<PublishHistories
onClose={() => {
setHistoriesDefaultData(undefined);
}}
past={past}
saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
/>
)}
<MyModal
isOpen={isOpenBackConfirm}
onClose={onCloseBackConfirm}
iconSrc="common/warn"
title={t('common:common.Exit')}
w={'400px'}
>
<ModalBody>
<Box>{t('workflow:workflow.exit_tips')}</Box>
</ModalBody>
<ModalFooter gap={3}>
<Button variant={'whiteDanger'} onClick={onBack}>
{t('common:common.Exit Directly')}
</Button>
<Button
isLoading={loading}
onClick={async () => {
await onClickSave({});
onCloseBackConfirm();
onBack();
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
}}
>
{t('common:common.Save_and_exit')}
</Button>
</ModalFooter>
</MyModal>
</>
);
}, [
......@@ -276,22 +226,63 @@ const Header = () => {
currentTab,
isPublished,
onBack,
isOpenBackConfirm,
onOpenBackConfirm,
onCloseBackConfirm,
isV2Workflow,
showHistoryModal,
t,
loading,
isV2Workflow,
historiesDefaultData,
onClickSave,
setHistoriesDefaultData,
appDetail.chatConfig,
flowData2StoreDataAndCheck,
setWorkflowTestData,
toast
setShowHistoryModal,
setWorkflowTestData
]);
return Render;
return (
<>
{Render}
{showHistoryModal && isV2Workflow && currentTab === TabEnum.appEdit && (
<PublishHistories<WorkflowSnapshotsType>
onClose={() => {
setShowHistoryModal(false);
}}
past={past}
onSwitchTmpVersion={onSwitchTmpVersion}
onSwitchCloudVersion={onSwitchCloudVersion}
/>
)}
<MyModal
isOpen={isOpenBackConfirm}
onClose={onCloseBackConfirm}
iconSrc="common/warn"
title={t('common:common.Exit')}
w={'400px'}
>
<ModalBody>
<Box>{t('workflow:workflow.exit_tips')}</Box>
</ModalBody>
<ModalFooter gap={3}>
<Button variant={'whiteDanger'} onClick={onBack}>
{t('common:common.Exit Directly')}
</Button>
<Button
isLoading={loading}
onClick={async () => {
await onClickSave({});
onCloseBackConfirm();
onBack();
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
}}
>
{t('common:common.Save_and_exit')}
</Button>
</ModalFooter>
</MyModal>
</>
);
};
export default React.memo(Header);
......@@ -7,38 +7,36 @@ import {
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 { Box, BoxProps, 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 { SaveSnapshotParams, SnapshotsType } from './WorkflowComponents/context';
import type { WorkflowSnapshotsType } 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 type { versionListResponse } from '@/pages/api/core/app/version/list';
import type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version';
import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots';
const PublishHistoriesSlider = ({
const PublishHistoriesSlider = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
onClose,
past,
saveSnapshot,
resetSnapshot,
top,
bottom
onSwitchTmpVersion,
onSwitchCloudVersion,
positionStyles
}: {
onClose: () => void;
past: SnapshotsType[];
saveSnapshot: (params: SaveSnapshotParams) => Promise<boolean>;
resetSnapshot: (state: SnapshotsType) => void;
top?: string | number;
bottom?: string | number;
past: T[];
onSwitchTmpVersion: (params: T, customTitle: string) => void;
onSwitchCloudVersion: (appVersion: AppVersionSchemaType) => void;
positionStyles?: BoxProps;
}) => {
const { t } = useTranslation();
const [currentTab, setCurrentTab] = useState<'myEdit' | 'teamCloud'>('myEdit');
......@@ -69,29 +67,26 @@ const PublishHistoriesSlider = ({
px={0}
showMask={false}
overflow={'unset'}
top={top}
bottom={bottom}
{...positionStyles}
>
{currentTab === 'myEdit' ? (
<MyEdit past={past} saveSnapshot={saveSnapshot} resetSnapshot={resetSnapshot} />
<MyEdit past={past} onSwitchTmpVersion={onSwitchTmpVersion} />
) : (
<TeamCloud saveSnapshot={saveSnapshot} resetSnapshot={resetSnapshot} />
<TeamCloud onSwitchCloudVersion={onSwitchCloudVersion} />
)}
</CustomRightDrawer>
</>
);
};
export default React.memo(PublishHistoriesSlider);
export default PublishHistoriesSlider;
const MyEdit = ({
const MyEdit = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
past,
saveSnapshot,
resetSnapshot
onSwitchTmpVersion
}: {
past: SnapshotsType[];
saveSnapshot: (params: SaveSnapshotParams) => Promise<boolean>;
resetSnapshot: (state: SnapshotsType) => void;
past: T[];
onSwitchTmpVersion: (params: T, customTitle: string) => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
......@@ -107,24 +102,14 @@ const MyEdit = ({
onClick={async () => {
const initialSnapshot = past[past.length - 1];
const res = await saveSnapshot({
pastNodes: initialSnapshot.nodes,
pastEdges: initialSnapshot.edges,
chatConfig: initialSnapshot.chatConfig,
customTitle: t(`app:app.version_initial_copy`)
});
if (res) {
resetSnapshot(initialSnapshot);
}
onSwitchTmpVersion(initialSnapshot, t(`app:version_initial_copy`));
toast({
title: t('workflow:workflow.Switch_success'),
status: 'success'
});
}}
>
{t('app:app.version_back')}
{t('app:version_back')}
</Button>
</Box>
)}
......@@ -142,17 +127,8 @@ const MyEdit = ({
_hover={{
bg: 'primary.50'
}}
onClick={async () => {
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);
}
onClick={() => {
onSwitchTmpVersion(item, `${t('app:version_copy')}-${item.title}`);
toast({
title: t('workflow:workflow.Switch_success'),
status: 'success'
......@@ -201,18 +177,16 @@ const MyEdit = ({
};
const TeamCloud = ({
saveSnapshot,
resetSnapshot
onSwitchCloudVersion
}: {
saveSnapshot: (params: SaveSnapshotParams) => Promise<boolean>;
resetSnapshot: (state: SnapshotsType) => void;
onSwitchCloudVersion: (appVersion: AppVersionSchemaType) => void;
}) => {
const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const { loadAndGetTeamMembers } = useUserStore();
const { feConfigs } = useSystemStore();
const { scrollDataList, ScrollList, isLoading, fetchData } = useVirtualScrollPagination(
const { scrollDataList, ScrollList, isLoading, fetchData, setData } = useVirtualScrollPagination(
getWorkflowVersionList,
{
itemHeight: 40,
......@@ -230,30 +204,15 @@ const TeamCloud = ({
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
const [isEditing, setIsEditing] = useState(false);
const { toast } = useToast();
const { runAsync: onChangeVersion, loading: isLoadingVersion } = useRequest2(
async (versionItem: versionListResponse) => {
async (versionItem: VersionListItemType) => {
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);
onSwitchCloudVersion(versionDetail);
toast({
title: t('workflow:workflow.Switch_success'),
status: 'success'
......@@ -261,6 +220,22 @@ const TeamCloud = ({
}
);
const { runAsync: onUpdateVersion, loading: isEditing } = useRequest2(
async (item: VersionListItemType, name: string) => {
await updateAppVersion({
appId: item.appId,
versionName: name,
versionId: item._id
});
setData((state) =>
state.map((version) =>
version._id === item._id ? { ...version, versionName: name } : version
)
);
setEditIndex(undefined);
}
);
return (
<ScrollList isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
{scrollDataList.map((data, index) => {
......@@ -361,16 +336,12 @@ const TeamCloud = ({
h={8}
defaultValue={item.versionName || formatTime2YMDHMS(item.time)}
onClick={(e) => 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);
onBlur={(e) => onUpdateVersion(item, e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
// @ts-ignore
onUpdateVersion(item, e.target.value);
}
}}
/>
</MyBox>
......
......@@ -15,11 +15,8 @@ import { cardStyles } from '../constants';
import styles from './styles.module.scss';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
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';
import { onSaveSnapshotFnType, SimpleAppSnapshotType } from './useSnapshots';
const Edit = ({
appForm,
......@@ -29,8 +26,8 @@ const Edit = ({
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
past: SnapshotsType[];
saveSnapshot: SaveSnapshotFnType;
past: SimpleAppSnapshotType[];
saveSnapshot: onSaveSnapshotFnType;
}) => {
const { isPc } = useSystem();
const { loadAllDatasets } = useDatasetStore();
......@@ -43,26 +40,25 @@ const Edit = ({
loadAllDatasets();
// Get the latest snapshot
if (past.length > 0) {
const storeWorkflow = uiWorkflow2StoreWorkflow(past[0]);
const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: past[0].chatConfig });
return setAppForm(currentAppForm);
if (past?.[0]?.appForm) {
return setAppForm(past[0].appForm);
}
// Set the first snapshot
saveSnapshot({
pastNodes: appDetail.modules?.map((item) => storeNode2FlowNode({ item, t })),
chatConfig: appDetail.chatConfig,
isSaved: true
const appForm = appWorkflow2Form({
nodes: appDetail.modules,
chatConfig: appDetail.chatConfig
});
setAppForm(
appWorkflow2Form({
nodes: appDetail.modules,
chatConfig: appDetail.chatConfig
})
);
// Set the first snapshot
if (past.length === 0) {
saveSnapshot({
appForm,
title: t('app:initial_form'),
isSaved: true
});
}
setAppForm(appForm);
if (appDetail.version !== 'v2') {
setAppForm(
......
......@@ -20,32 +20,30 @@ 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 { useBoolean, useDebounceEffect } from 'ahooks';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import {
compareSnapshot,
storeEdgesRenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { SaveSnapshotFnType } from './useSnapshots';
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
compareSimpleAppSnapshot,
onSaveSnapshotFnType,
SimpleAppSnapshotType
} from './useSnapshots';
import PublishHistories from '../PublishHistoriesSlider';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
const Header = ({
forbiddenSaveSnapshot,
appForm,
setAppForm,
past,
setPast,
saveSnapshot
}: {
forbiddenSaveSnapshot: React.MutableRefObject<boolean>;
appForm: AppSimpleEditFormType;
setAppForm: (form: AppSimpleEditFormType) => void;
past: SnapshotsType[];
setPast: (value: React.SetStateAction<SnapshotsType[]>) => void;
saveSnapshot: SaveSnapshotFnType;
past: SimpleAppSnapshotType[];
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
saveSnapshot: onSaveSnapshotFnType;
}) => {
const { t } = useTranslation();
const { isPc } = useSystem();
......@@ -101,30 +99,43 @@ const Header = ({
}
);
const [historiesDefaultData, setHistoriesDefaultData] = useState<InitProps>();
const [isShowHistories, { setTrue: setIsShowHistories, setFalse: closeHistories }] =
useBoolean(false);
const onSwitchTmpVersion = useCallback(
(data: SimpleAppSnapshotType, customTitle: string) => {
setAppForm(data.appForm);
const resetSnapshot = useCallback(
(data: SnapshotsType) => {
const storeWorkflow = uiWorkflow2StoreWorkflow(data);
const currentAppForm = appWorkflow2Form({ ...storeWorkflow, chatConfig: data.chatConfig });
// Remove multiple "copy-"
const copyText = t('app:version_copy');
const regex = new RegExp(`(${copyText}-)\\1+`, 'g');
const title = customTitle.replace(regex, `$1`);
setAppForm(currentAppForm);
return saveSnapshot({
appForm: data.appForm,
title
});
},
[setAppForm]
[saveSnapshot, setAppForm, t]
);
const onSwitchCloudVersion = useCallback(
(appVersion: AppVersionSchemaType) => {
const appForm = appWorkflow2Form({
nodes: appVersion.nodes,
chatConfig: appVersion.chatConfig
});
// Save snapshot to local
useDebounceEffect(
() => {
const data = form2AppWorkflow(appForm, t);
saveSnapshot({
pastNodes: data.nodes?.map((item) => storeNode2FlowNode({ item, t })),
chatConfig: data.chatConfig
const res = saveSnapshot({
appForm,
title: `${t('app:version_copy')}-${appVersion.versionName}`
});
forbiddenSaveSnapshot.current = true;
setAppForm(appForm);
return res;
},
[appForm],
{ wait: 500 }
[forbiddenSaveSnapshot, saveSnapshot, setAppForm, t]
);
// Check if the workflow is published
......@@ -132,20 +143,7 @@ const Header = ({
useDebounceEffect(
() => {
const savedSnapshot = past.find((snapshot) => snapshot.isSaved);
const editFormData = form2AppWorkflow(appForm, t);
console.log(savedSnapshot?.nodes, editFormData.chatConfig);
const val = compareSnapshot(
{
nodes: savedSnapshot?.nodes,
edges: [],
chatConfig: savedSnapshot?.chatConfig
},
{
nodes: editFormData.nodes?.map((item) => storeNode2FlowNode({ item, t })),
edges: [],
chatConfig: editFormData.chatConfig
}
);
const val = compareSimpleAppSnapshot(savedSnapshot?.appForm, appForm);
setIsPublished(val);
},
[past, allDatasets],
......@@ -176,7 +174,7 @@ const Header = ({
)}
{currentTab === TabEnum.appEdit && (
<Flex alignItems={'center'}>
{!historiesDefaultData && (
{!isShowHistories && (
<>
{isPc && (
<MyTag
......@@ -204,14 +202,7 @@ const Header = ({
size={'sm'}
w={'30px'}
variant={'whitePrimary'}
onClick={() => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
setHistoriesDefaultData({
nodes,
edges,
chatConfig: appForm.chatConfig
});
}}
onClick={setIsShowHistories}
/>
<SaveButton isLoading={loading} onClickSave={onClickSave} />
</>
......@@ -220,16 +211,16 @@ const Header = ({
)}
</Flex>
{historiesDefaultData && currentTab === TabEnum.appEdit && (
<PublishHistories
onClose={() => {
setHistoriesDefaultData(undefined);
}}
{isShowHistories && currentTab === TabEnum.appEdit && (
<PublishHistories<SimpleAppSnapshotType>
onClose={closeHistories}
past={past}
saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
top={14}
bottom={3}
onSwitchTmpVersion={onSwitchTmpVersion}
onSwitchCloudVersion={onSwitchCloudVersion}
positionStyles={{
top: 14,
bottom: 3
}}
/>
)}
</Box>
......
......@@ -9,7 +9,8 @@ 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';
import { useSimpleAppSnapshots } from './useSnapshots';
import { useDebounceEffect } from 'ahooks';
const Logs = dynamic(() => import('../Logs/index'));
const PublishChannel = dynamic(() => import('../Publish'));
......@@ -17,9 +18,21 @@ const PublishChannel = dynamic(() => import('../Publish'));
const SimpleEdit = () => {
const { t } = useTranslation();
const { currentTab, appDetail } = useContextSelector(AppContext, (v) => v);
const { past, setPast, saveSnapshot } = useSnapshots(appDetail._id);
const { forbiddenSaveSnapshot, past, setPast, saveSnapshot } = useSimpleAppSnapshots(
appDetail._id
);
const [appForm, setAppForm] = useState(getDefaultAppForm());
// Save snapshot to local
useDebounceEffect(
() => {
saveSnapshot({
appForm
});
},
[appForm],
{ wait: 500 }
);
useBeforeunload({
tip: t('common:core.common.tip.leave page')
......@@ -29,6 +42,7 @@ const SimpleEdit = () => {
<Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}>
<Header
appForm={appForm}
forbiddenSaveSnapshot={forbiddenSaveSnapshot}
setAppForm={setAppForm}
past={past}
setPast={setPast}
......
import { useLocalStorageState, useMemoizedFn } from 'ahooks';
import { SaveSnapshotParams, SnapshotsType } from '../WorkflowComponents/context';
import { SetStateAction, useEffect } from 'react';
import { compareSnapshot } from '@/web/core/workflow/utils';
import { SetStateAction, useEffect, useRef } from 'react';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { Node } from 'reactflow';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { isEqual } from 'lodash';
export type SaveSnapshotFnType = (
props: SaveSnapshotParams & {
isSaved?: boolean;
export type SimpleAppSnapshotType = {
appForm: AppSimpleEditFormType;
title: string;
isSaved?: boolean;
};
export type onSaveSnapshotFnType = (props: {
appForm: AppSimpleEditFormType;
title?: string;
isSaved?: boolean;
}) => Promise<boolean>;
export const compareSimpleAppSnapshot = (
appForm1?: AppSimpleEditFormType,
appForm2?: AppSimpleEditFormType
) => {
if (
appForm1?.chatConfig &&
appForm2?.chatConfig &&
!isEqual(
{
welcomeText: appForm1.chatConfig?.welcomeText || '',
variables: appForm1.chatConfig?.variables || [],
questionGuide: appForm1.chatConfig?.questionGuide || false,
ttsConfig: appForm1.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm1.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: appForm1.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm1.chatConfig?.fileSelectConfig || undefined,
instruction: appForm1.chatConfig?.instruction || ''
},
{
welcomeText: appForm2.chatConfig?.welcomeText || '',
variables: appForm2.chatConfig?.variables || [],
questionGuide: appForm2.chatConfig?.questionGuide || false,
ttsConfig: appForm2.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm2.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: appForm2.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm2.chatConfig?.fileSelectConfig || undefined,
instruction: appForm2.chatConfig?.instruction || ''
}
)
) {
console.log('chatConfig not equal');
return false;
}
) => Promise<boolean>;
const useSnapshots = (appId: string) => {
const [past, setPast] = useLocalStorageState<SnapshotsType[]>(`${appId}-past-simple`, {
defaultValue: [],
listenStorageChange: true
}) as [SnapshotsType[], (value: SetStateAction<SnapshotsType[]>) => void];
return isEqual(appForm1, appForm2);
};
const saveSnapshot: SaveSnapshotFnType = useMemoizedFn(
async ({ pastNodes, chatConfig, customTitle, isSaved }) => {
if (!pastNodes) return false;
export const useSimpleAppSnapshots = (appId: string) => {
const forbiddenSaveSnapshot = useRef(false);
const [past, setPast] = useLocalStorageState<SimpleAppSnapshotType[]>(`${appId}-past-simple`, {
defaultValue: []
}) as [SimpleAppSnapshotType[], (value: SetStateAction<SimpleAppSnapshotType[]>) => void];
const pastState = past[0];
const saveSnapshot: onSaveSnapshotFnType = useMemoizedFn(async ({ appForm, title, isSaved }) => {
if (forbiddenSaveSnapshot.current) {
forbiddenSaveSnapshot.current = false;
return false;
}
const isPastEqual = compareSnapshot(
{
nodes: pastNodes,
edges: [],
chatConfig: chatConfig
},
{
nodes: pastState?.nodes,
edges: pastState?.edges,
chatConfig: pastState?.chatConfig
}
);
if (isPastEqual) return false;
const pastState = past[0];
setPast((past) => [
{
nodes: pastNodes,
edges: [],
title: customTitle || formatTime2YMDHMS(new Date()),
chatConfig,
isSaved
},
...past.slice(0, 199)
]);
return true;
}
);
const isPastEqual = compareSimpleAppSnapshot(pastState?.appForm, appForm);
if (isPastEqual) return false;
setPast((past) => [
{
appForm,
title: title || formatTime2YMDHMS(new Date()),
isSaved
},
...past.slice(0, 199)
]);
return true;
});
// remove other app's snapshot
useEffect(() => {
......@@ -64,7 +94,7 @@ const useSnapshots = (appId: string) => {
});
}, [appId]);
return { past, setPast, saveSnapshot };
return { forbiddenSaveSnapshot, past, setPast, saveSnapshot };
};
export default useSnapshots;
export default <></>;
......@@ -10,17 +10,15 @@ import {
useDisclosure
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context';
import { WorkflowContext } from '../WorkflowComponents/context';
import { AppContext, TabEnum } from '../context';
import RouteTab from '../RouteTab';
import { useRouter } from 'next/router';
import AppCard from '../WorkflowComponents/AppCard';
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyModal from '@fastgpt/web/components/common/MyModal';
......@@ -30,8 +28,7 @@ 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('../PublishHistoriesSlider'));
import PublishHistories from '../PublishHistoriesSlider';
const Header = () => {
const { t } = useTranslation();
......@@ -51,15 +48,15 @@ const Header = () => {
flowData2StoreData,
flowData2StoreDataAndCheck,
setWorkflowTestData,
setHistoriesDefaultData,
historiesDefaultData,
setShowHistoryModal,
showHistoryModal,
nodes,
edges,
past,
future,
setPast,
saveSnapshot,
resetSnapshot
onSwitchTmpVersion,
onSwitchCloudVersion
} = useContextSelector(WorkflowContext, (v) => v);
const { lastAppListRouteType } = useSystemStore();
......@@ -189,21 +186,15 @@ const Header = () => {
{currentTab === TabEnum.appEdit && (
<HStack flexDirection={['column', 'row']} spacing={[2, 3]}>
{!historiesDefaultData && (
{!showHistoryModal && (
<IconButton
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'}
w={'30px'}
variant={'whitePrimary'}
onClick={async () => {
const { nodes, edges } = uiWorkflow2StoreWorkflow(await getWorkflowStore());
setHistoriesDefaultData({
nodes,
edges,
chatConfig: appDetail.chatConfig
});
onClick={() => {
setShowHistoryModal(true);
}}
/>
)}
......@@ -220,7 +211,7 @@ const Header = () => {
>
{t('common:core.workflow.Run')}
</Button>
{!historiesDefaultData && (
{!showHistoryModal && (
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
......@@ -230,48 +221,6 @@ const Header = () => {
</HStack>
)}
</Flex>
{historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit && (
<PublishHistories
onClose={() => {
setHistoriesDefaultData(undefined);
}}
past={past}
saveSnapshot={saveSnapshot}
resetSnapshot={resetSnapshot}
/>
)}
<MyModal
isOpen={isOpenBackConfirm}
onClose={onCloseBackConfirm}
iconSrc="common/warn"
title={t('common:common.Exit')}
w={'400px'}
>
<ModalBody>
<Box>{t('workflow:workflow.exit_tips')}</Box>
</ModalBody>
<ModalFooter gap={3}>
<Button variant={'whiteDanger'} onClick={onBack}>
{t('common:common.Exit Directly')}
</Button>
<Button
isLoading={loading}
onClick={async () => {
await onClickSave({});
onCloseBackConfirm();
onBack();
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
}}
>
{t('common:common.Save_and_exit')}
</Button>
</ModalFooter>
</MyModal>
</>
);
}, [
......@@ -281,23 +230,62 @@ const Header = () => {
onBack,
onOpenBackConfirm,
isV2Workflow,
historiesDefaultData,
showHistoryModal,
t,
loading,
onClickSave,
flowData2StoreDataAndCheck,
past,
saveSnapshot,
resetSnapshot,
isOpenBackConfirm,
onCloseBackConfirm,
setHistoriesDefaultData,
appDetail.chatConfig,
setWorkflowTestData,
toast
setShowHistoryModal,
setWorkflowTestData
]);
return Render;
return (
<>
{Render}
{showHistoryModal && isV2Workflow && currentTab === TabEnum.appEdit && (
<PublishHistories
onClose={() => {
setShowHistoryModal(false);
}}
past={past}
onSwitchCloudVersion={onSwitchCloudVersion}
onSwitchTmpVersion={onSwitchTmpVersion}
/>
)}
<MyModal
isOpen={isOpenBackConfirm}
onClose={onCloseBackConfirm}
iconSrc="common/warn"
title={t('common:common.Exit')}
w={'400px'}
>
<ModalBody>
<Box>{t('workflow:workflow.exit_tips')}</Box>
</ModalBody>
<ModalFooter gap={3}>
<Button variant={'whiteDanger'} onClick={onBack}>
{t('common:common.Exit Directly')}
</Button>
<Button
isLoading={loading}
onClick={async () => {
await onClickSave({});
onCloseBackConfirm();
onBack();
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
});
}}
>
{t('common:common.Save_and_exit')}
</Button>
</ModalFooter>
</MyModal>
</>
);
};
export default React.memo(Header);
......@@ -31,7 +31,7 @@ const AppCard = ({
const { appDetail, onOpenInfoEdit, onOpenTeamTagModal, onDelApp, currentTab } =
useContextSelector(AppContext, (v) => v);
const { historiesDefaultData } = useContextSelector(WorkflowContext, (v) => v);
const { showHistoryModal } = useContextSelector(WorkflowContext, (v) => v);
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
......@@ -56,7 +56,7 @@ const AppCard = ({
}
]
},
...(!historiesDefaultData && currentTab === TabEnum.appEdit
...(!showHistoryModal && currentTab === TabEnum.appEdit
? [
{
children: [
......@@ -117,7 +117,7 @@ const AppCard = ({
appDetail.permission.isOwner,
currentTab,
feConfigs?.show_team_chat,
historiesDefaultData,
showHistoryModal,
onDelApp,
onOpenImport,
onOpenInfoEdit,
......
......@@ -49,6 +49,7 @@ import CostTooltip from '@/components/core/app/plugin/CostTooltip';
import { useUserStore } from '@/web/support/user/useUserStore';
import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart';
import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
type ModuleTemplateListProps = {
isOpen: boolean;
......@@ -399,8 +400,7 @@ const RenderList = React.memo(function RenderList({
const { screenToFlowPosition } = useReactFlow();
const { toast } = useToast();
const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const { reactFlowWrapper, setNodes, nodeList } = useContextSelector(WorkflowContext, (v) => v);
const { computedNewNodeName } = useWorkflowUtils();
const formatTemplates = useMemo<NodeTemplateListType>(() => {
......@@ -424,6 +424,7 @@ const RenderList = React.memo(function RenderList({
}) => {
if (!reactFlowWrapper?.current) return;
// Load template node
const templateNode = await (async () => {
try {
// get plugin preview module
......@@ -458,6 +459,19 @@ const RenderList = React.memo(function RenderList({
const mouseX = nodePosition.x - 100;
const mouseY = nodePosition.y - 20;
// Add default values to some inputs
const defaultValueMap: Record<string, any> = {
[NodeInputKeyEnum.userChatInput]: undefined
};
nodeList.forEach((node) => {
if (node.flowNodeType === FlowNodeTypeEnum.workflowStart) {
defaultValueMap[NodeInputKeyEnum.userChatInput] = [
node.nodeId,
NodeOutputKeyEnum.userChatInput
];
}
});
const newNode = nodeTemplate2FlowNode({
template: {
...templateNode,
......@@ -469,6 +483,7 @@ const RenderList = React.memo(function RenderList({
intro: t(templateNode.intro as any),
inputs: templateNode.inputs.map((input) => ({
...input,
value: defaultValueMap[input.key] ?? input.value,
valueDesc: t(input.valueDesc as any),
label: t(input.label as any),
description: t(input.description as any),
......@@ -516,7 +531,16 @@ const RenderList = React.memo(function RenderList({
return newState;
});
},
[computedNewNodeName, reactFlowWrapper, setLoading, setNodes, t, toast, screenToFlowPosition]
[
reactFlowWrapper,
screenToFlowPosition,
nodeList,
computedNewNodeName,
t,
setNodes,
setLoading,
toast
]
);
const gridStyle = useMemo(() => {
......
......@@ -10,7 +10,7 @@ import MyTextarea from '@/components/common/Textarea/MyTextarea';
import { AppContext } from '../../../../context';
import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type';
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { useCreation } from 'ahooks';
import { useCreation, useMount } from 'ahooks';
import ChatFunctionTip from '@/components/core/app/Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { WorkflowContext } from '../../../context';
......@@ -35,7 +35,7 @@ const NodePluginConfig = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
});
}, [data, appDetail]);
useCreation(() => {
useMount(() => {
setAppDetail((state) => ({
...state,
chatConfig: {
......@@ -43,7 +43,7 @@ const NodePluginConfig = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
...chatConfig
}
}));
}, []);
});
const componentsProps = useMemo(
() => ({
......
......@@ -5,19 +5,17 @@ import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
import { FastGPTProUrl, isProduction } from '@fastgpt/service/common/system/constants';
import { initFastGPTConfig } from '@fastgpt/service/common/system/tools';
import json5 from 'json5';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
export const readConfigData = (name: string) => {
const isDev = process.env.NODE_ENV === 'development';
const splitName = name.split('.');
const devName = `${splitName[0]}.local.${splitName[1]}`;
const filename = (() => {
if (isDev) {
if (!isProduction) {
// check local file exists
const hasLocalFile = existsSync(`data/${devName}`);
if (hasLocalFile) {
......
import { PostPublishAppProps, PostRevertAppProps } from '@/global/core/app/api';
import { PostPublishAppProps } from '@/global/core/app/api';
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
import type {
getLatestVersionQuery,
getLatestVersionResponse
......@@ -16,7 +16,7 @@ export const postPublishApp = (appId: string, data: PostPublishAppProps) =>
POST(`/core/app/version/publish?appId=${appId}`, data);
export const getWorkflowVersionList = (data: PaginationProps<{ appId: string }>) =>
POST<PaginationResponse<versionListResponse>>('/core/app/version/list', data);
POST<versionListResponse>('/core/app/version/list', data);
export const getAppVersionDetail = (versionId: string, appId: string) =>
GET<AppVersionSchemaType>(`/core/app/version/detail?versionId=${versionId}&appId=${appId}`);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment