Publish histories (#1331)

* fix http plugin edge (#95)

* fix http plugin edge

* use getHandleId

* perf: i18n file

* feat: histories list

* perf: request lock

* fix: ts

* move box components

* fix: edit form refresh

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-30 12:42:13 +08:00
committed by GitHub
parent a0c1320d47
commit db6fc53840
46 changed files with 741 additions and 129 deletions

View File

@@ -26,7 +26,7 @@ import {
billTypeMap
} from '@fastgpt/global/support/wallet/bill/constants';
// import { usePagination } from '@/web/common/hooks/usePagination';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import MySelect from '@fastgpt/web/components/common/MySelect';

View File

@@ -0,0 +1,34 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middle/entry';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
type Props = PaginationProps<{
appId: string;
}>;
type Response = PaginationResponse<AppVersionSchemaType>;
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<Response> {
const { current, pageSize, appId } = req.body as Props;
const [result, total] = await Promise.all([
MongoAppVersion.find({
appId
})
.sort({
time: -1
})
.skip((current - 1) * pageSize)
.limit(pageSize),
MongoAppVersion.countDocuments({ appId })
]);
return {
total,
list: result
};
}
export default NextAPI(handler);

View File

@@ -0,0 +1,73 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middle/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { PostRevertAppProps } from '@/global/core/app/api';
type Response = {};
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
const { appId } = req.query as { appId: string };
const { editNodes = [], editEdges = [], versionId } = req.body as PostRevertAppProps;
await authApp({ appId, req, per: 'w', authToken: true });
const version = await MongoAppVersion.findOne({
_id: versionId,
appId
});
if (!version) {
throw new Error('version not found');
}
const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes });
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(version.nodes));
await mongoSessionRun(async (session) => {
// 为编辑中的数据创建一个版本
await MongoAppVersion.create(
[
{
appId,
nodes: formatEditNodes,
edges: editEdges
}
],
{ session }
);
// 为历史版本再创建一个版本
await MongoAppVersion.create(
[
{
appId,
nodes: version.nodes,
edges: version.edges
}
],
{ session }
);
// update app
await MongoApp.findByIdAndUpdate(appId, {
modules: version.nodes,
edges: version.edges,
updateTime: new Date(),
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null
});
});
return {};
}
export default NextAPI(handler);

View File

@@ -22,12 +22,15 @@ import {
} from '@/web/core/workflow/utils';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useQuery } from '@tanstack/react-query';
import { formatTime2HM } from '@fastgpt/global/common/string/time';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useInterval } from 'ahooks';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
const PublishHistories = dynamic(
() => import('@/components/core/workflow/components/PublishHistoriesSlider')
);
type Props = { app: AppSchema; onClose: () => void };
@@ -55,7 +58,6 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
content: t('core.app.Publish Confirm')
});
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { publishApp, updateAppDetail } = useAppStore();
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
@@ -63,6 +65,17 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save'));
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const isShowVersionHistories = useContextSelector(
WorkflowContext,
(v) => v.isShowVersionHistories
);
const setIsShowVersionHistories = useContextSelector(
WorkflowContext,
(v) => v.setIsShowVersionHistories
);
const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
@@ -81,6 +94,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
}, [edges, onUpdateNodeError, t, toast]);
const onclickSave = useCallback(async () => {
if (isShowVersionHistories) return;
const { nodes } = await getWorkflowStore();
if (nodes.length === 0) return null;
@@ -107,7 +121,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
setIsSaving(false);
return null;
}, [updateAppDetail, app._id, edges, t]);
}, [isShowVersionHistories, edges, updateAppDetail, app._id, t]);
const onclickPublish = useCallback(async () => {
setIsSaving(true);
@@ -160,15 +174,16 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
}
}, [copyData, flowData2StoreDataAndCheck, t]);
// effect
useBeforeunload({
callback: onclickSave,
tip: t('core.common.tip.leave page')
});
useQuery(['autoSave'], onclickSave, {
refetchInterval: 20 * 1000,
enabled: !!app._id
});
useInterval(() => {
if (!app._id) return;
onclickSave();
}, 20000);
const Render = useMemo(() => {
return (
@@ -180,6 +195,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
alignItems={'center'}
userSelect={'none'}
bg={'myGray.25'}
h={'67px'}
>
<IconButton
size={'smSquare'}
@@ -193,23 +209,25 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
isLoading={isSaving}
onClick={saveAndBack}
/>
<Box ml={[3, 5]}>
<Box ml={[2, 4]}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
{app.name}
</Box>
<MyTooltip label={t('core.app.Onclick to save')}>
<Box
fontSize={'sm'}
mt={1}
display={'inline-block'}
borderRadius={'xs'}
cursor={'pointer'}
onClick={onclickSave}
color={'myGray.500'}
>
{saveLabel}
</Box>
</MyTooltip>
{!isShowVersionHistories && (
<MyTooltip label={t('core.app.Onclick to save')}>
<Box
fontSize={'sm'}
mt={1}
display={'inline-block'}
borderRadius={'xs'}
cursor={'pointer'}
onClick={onclickSave}
color={'myGray.500'}
>
{saveLabel}
</Box>
</MyTooltip>
)}
</Box>
<Box flex={1} />
@@ -217,7 +235,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<MyMenu
Button={
<IconButton
mr={[3, 5]}
mr={[2, 4]}
icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
@@ -238,10 +256,19 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
]}
/>
<Button
mr={[3, 5]}
<IconButton
mr={[2, 4]}
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
w={'30px'}
variant={'whitePrimary'}
onClick={() => setIsShowVersionHistories(true)}
/>
<Button
size={'sm'}
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
variant={'whitePrimary'}
onClick={async () => {
const data = await flowData2StoreDataAndCheck();
@@ -250,17 +277,20 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
}
}}
>
{t('core.Chat test')}
{t('core.workflow.Debug')}
</Button>
<Button
size={'sm'}
isLoading={isSaving}
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
onClick={openConfigPublish(onclickPublish)}
>
{t('core.app.Publish')}
</Button>
{!isShowVersionHistories && (
<Button
ml={[2, 4]}
size={'sm'}
isLoading={isSaving}
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
onClick={openConfigPublish(onclickPublish)}
>
{t('core.app.Publish')}
</Button>
)}
</Flex>
<ConfirmModal confirmText={t('core.app.Publish')} />
</>
@@ -275,8 +305,10 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
onclickPublish,
onclickSave,
openConfigPublish,
isShowVersionHistories,
saveAndBack,
saveLabel,
setIsShowVersionHistories,
setWorkflowTestData,
t,
theme.borders.base
@@ -286,6 +318,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<>
{Render}
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
{isShowVersionHistories && <PublishHistories />}
</>
);
});

View File

@@ -28,7 +28,7 @@ import { getInitChatInfo } from '@/web/core/chat/api';
import Tag from '@fastgpt/web/components/common/Tag/index';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { addDays } from 'date-fns';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
import { formatChatValue2InputType } from '@/components/ChatBox/utils';

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useTransition } from 'react';
import React, { useEffect, useMemo, useTransition } from 'react';
import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useFieldArray, UseFormReturn } from 'react-hook-form';
@@ -28,6 +28,7 @@ import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
import { TTSTypeEnum } from '@/constants/app';
import { getSystemVariables } from '@/web/core/app/utils';
import { useUpdate } from 'ahooks';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
@@ -65,6 +66,7 @@ const EditForm = ({
const { allDatasets } = useDatasetStore();
const { llmModelList } = useSystemStore();
const [, startTst] = useTransition();
const refresh = useUpdate();
const { setValue, getValues, handleSubmit, control, watch } = editForm;
@@ -72,7 +74,6 @@ const EditForm = ({
control,
name: 'dataset.datasets'
});
const selectedTools = watch('selectedTools');
const {
isOpen: isOpenDatasetSelect,
@@ -106,6 +107,7 @@ const EditForm = ({
const tts = getValues('userGuide.tts');
const whisperConfig = getValues('userGuide.whisper');
const postQuestionGuide = getValues('userGuide.questionGuide');
const selectedTools = watch('selectedTools');
const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
@@ -131,6 +133,16 @@ const EditForm = ({
errorToast: t('common.Save Failed')
});
useEffect(() => {
const wat = watch((data) => {
refresh();
});
return () => {
wat.unsubscribe();
};
}, []);
return (
<Box>
{/* title */}
@@ -459,7 +471,9 @@ const EditForm = ({
{isOpenToolsSelect && (
<ToolSelectModal
selectedTools={selectedTools}
onAddTool={(e) => setValue('selectedTools', [...selectedTools, e])}
onAddTool={(e) => {
setValue('selectedTools', [...selectedTools, e]);
}}
onRemoveTool={(e) => {
setValue(
'selectedTools',

View File

@@ -29,7 +29,7 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { AddIcon } from '@chakra-ui/icons';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import ParentPaths from '@/components/common/ParentPaths';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';

View File

@@ -23,7 +23,6 @@ const MyApps = () => {
const router = useRouter();
const { userInfo } = useUserStore();
const { myApps, loadMyApps } = useAppStore();
const [teamsTags, setTeamTags] = useState([]);
const { openConfirm, ConfirmModal } = useConfirm({
title: '删除提示',
content: '确认删除该应用所有信息?'

View File

@@ -26,7 +26,7 @@ import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log';

View File

@@ -32,7 +32,7 @@ import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';

View File

@@ -60,7 +60,7 @@ import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants';

View File

@@ -1,4 +1,4 @@
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { Box, FlexProps } from '@chakra-ui/react';

View File

@@ -23,7 +23,7 @@ import DeleteIcon from '@fastgpt/web/components/common/Icon/delete';
import { defaultCollectionDetail } from '@/constants/dataset';
import { getDocPath } from '@/web/common/system/doc';
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';

View File

@@ -27,7 +27,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
import Head from 'next/head';
import MyBox from '@/components/common/MyBox';
import MyBox from '@fastgpt/web/components/common/MyBox';
const DataCard = dynamic(() => import('./components/DataCard'));
const Test = dynamic(() => import('./components/Test'));