mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-30 02:12:38 +00:00
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:
@@ -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';
|
||||
|
34
projects/app/src/pages/api/core/app/version/list.ts
Normal file
34
projects/app/src/pages/api/core/app/version/list.ts
Normal 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);
|
73
projects/app/src/pages/api/core/app/version/revert.ts
Normal file
73
projects/app/src/pages/api/core/app/version/revert.ts
Normal 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);
|
@@ -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 />}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@@ -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';
|
||||
|
@@ -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',
|
||||
|
@@ -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';
|
||||
|
@@ -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: '确认删除该应用所有信息?'
|
||||
|
@@ -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';
|
||||
|
@@ -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';
|
||||
|
||||
|
@@ -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';
|
||||
|
||||
|
@@ -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';
|
||||
|
@@ -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';
|
||||
|
@@ -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'));
|
||||
|
Reference in New Issue
Block a user