mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-25 06:14:06 +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:
@@ -117,6 +117,7 @@
|
||||
"Name is empty": "Name is empty",
|
||||
"New Create": "Create",
|
||||
"Next Step": "Next",
|
||||
"No more data": "No more",
|
||||
"Not open": "Close",
|
||||
"Number of words": "{{amount}} words",
|
||||
"OK": "OK",
|
||||
@@ -1101,6 +1102,7 @@
|
||||
"Check Failed": "Workflow verification fails. Check whether the node or connection is normal",
|
||||
"Confirm stop debug": "Do you want to terminate debugging? Debugging information is not retained.",
|
||||
"Copy node": "Copy node",
|
||||
"Current workflow": "Current workflow",
|
||||
"Custom inputs": "Inputs",
|
||||
"Custom outputs": "Outputs",
|
||||
"Custom variable": "Custom variable",
|
||||
@@ -1148,6 +1150,11 @@
|
||||
"target": "Target Data",
|
||||
"textarea": "Textarea"
|
||||
},
|
||||
"publish": {
|
||||
"OnRevert version": "Click back to that version",
|
||||
"OnRevert version confirm": "Are you sure to roll back the version?",
|
||||
"histories": "Publiish histories"
|
||||
},
|
||||
"tool": {
|
||||
"Handle": "Tool handle",
|
||||
"Select Tool": "Select Tool"
|
@@ -117,6 +117,7 @@
|
||||
"Name is empty": "名称不能为空",
|
||||
"New Create": "新建",
|
||||
"Next Step": "下一步",
|
||||
"No more data": "没有更多了~",
|
||||
"Not open": "未开启",
|
||||
"Number of words": "{{amount}}字",
|
||||
"OK": "好的",
|
||||
@@ -1103,6 +1104,7 @@
|
||||
"Check Failed": "工作流校验失败,请检查节点是否正确填值,以及连线是否正常",
|
||||
"Confirm stop debug": "确认终止调试?调试信息将会不保留。",
|
||||
"Copy node": "已复制节点",
|
||||
"Current workflow": "当前工作流",
|
||||
"Custom inputs": "自定义输入",
|
||||
"Custom outputs": "自定义输出",
|
||||
"Custom variable": "自定义变量",
|
||||
@@ -1150,6 +1152,11 @@
|
||||
"target": "外部数据",
|
||||
"textarea": "多行输入框"
|
||||
},
|
||||
"publish": {
|
||||
"OnRevert version": "点击回退到该版本",
|
||||
"OnRevert version confirm": "确认回退该版本?",
|
||||
"histories": "发布记录"
|
||||
},
|
||||
"tool": {
|
||||
"Handle": "工具连接器",
|
||||
"Select Tool": "选择工具"
|
@@ -9,7 +9,6 @@ module.exports = {
|
||||
locales: ['en', 'zh'],
|
||||
localeDetection: false
|
||||
},
|
||||
localePath:
|
||||
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/public/locales',
|
||||
localePath: typeof window === 'undefined' ? require('path').resolve('./i18n') : '/i18n',
|
||||
reloadOnPrerender: process.env.NODE_ENV === 'development'
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useTheme, type BoxProps } from '@chakra-ui/react';
|
||||
import MyBox from '../common/MyBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const PageContainer = ({
|
||||
children,
|
||||
|
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||
|
||||
type Props = BoxProps & {
|
||||
isLoading?: boolean;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
|
||||
return (
|
||||
<Box position={'relative'} {...props}>
|
||||
{isLoading && <Loading fixed={false} text={text} />}
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyBox;
|
@@ -7,7 +7,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
|
||||
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
|
||||
|
@@ -5,7 +5,7 @@ import React, { Dispatch, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
type PathItemType = {
|
||||
parentId: string;
|
||||
|
@@ -14,7 +14,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { moduleTemplatesList } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
|
||||
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
|
||||
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
@@ -259,7 +259,7 @@ const RenderList = React.memo(function RenderList({
|
||||
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
|
||||
|
||||
const formatTemplates = useMemo<nodeTemplateListType>(() => {
|
||||
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));
|
||||
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(workflowNodeTemplateList(t)));
|
||||
templates.forEach((item) => {
|
||||
const index = copy.findIndex((template) => template.type === item.templateType);
|
||||
if (index === -1) return;
|
||||
|
@@ -0,0 +1,179 @@
|
||||
import React, { useState } from 'react';
|
||||
import { getPublishList, postRevertVersion } from '@/web/core/app/versionApi';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../context';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const PublishHistoriesSlider = () => {
|
||||
const { t } = useTranslation();
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: t('core.workflow.publish.OnRevert version confirm')
|
||||
});
|
||||
|
||||
const { appDetail, setAppDetail } = useAppStore();
|
||||
const appId = useContextSelector(WorkflowContext, (e) => e.appId);
|
||||
const setIsShowVersionHistories = useContextSelector(
|
||||
WorkflowContext,
|
||||
(e) => e.setIsShowVersionHistories
|
||||
);
|
||||
const initData = useContextSelector(WorkflowContext, (e) => e.initData);
|
||||
|
||||
const [selectedHistoryId, setSelectedHistoryId] = useState<string>();
|
||||
|
||||
const { list, ScrollList, isLoading } = useScrollPagination(getPublishList, {
|
||||
itemHeight: 49,
|
||||
overscan: 20,
|
||||
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
appId
|
||||
}
|
||||
});
|
||||
|
||||
const onClose = useMemoizedFn(() => {
|
||||
setIsShowVersionHistories(false);
|
||||
});
|
||||
|
||||
const onPreview = useMemoizedFn((data: AppVersionSchemaType) => {
|
||||
setSelectedHistoryId(data._id);
|
||||
|
||||
initData({
|
||||
nodes: data.nodes,
|
||||
edges: data.edges
|
||||
});
|
||||
});
|
||||
const onCloseSlider = useMemoizedFn(() => {
|
||||
setSelectedHistoryId(undefined);
|
||||
initData({
|
||||
nodes: appDetail.modules,
|
||||
edges: appDetail.edges
|
||||
});
|
||||
onClose();
|
||||
});
|
||||
|
||||
const { mutate: onRevert, isLoading: isReverting } = useRequest({
|
||||
mutationFn: async (data: AppVersionSchemaType) => {
|
||||
if (!appId) return;
|
||||
await postRevertVersion(appId, {
|
||||
versionId: data._id,
|
||||
editNodes: appDetail.modules,
|
||||
editEdges: appDetail.edges
|
||||
});
|
||||
|
||||
setAppDetail({
|
||||
...appDetail,
|
||||
modules: data.nodes,
|
||||
edges: data.edges
|
||||
});
|
||||
|
||||
onCloseSlider();
|
||||
}
|
||||
});
|
||||
|
||||
const showLoading = isLoading || isReverting;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomRightDrawer
|
||||
onClose={onCloseSlider}
|
||||
iconSrc="core/workflow/versionHistories"
|
||||
title={t('core.workflow.publish.histories')}
|
||||
maxW={'300px'}
|
||||
px={0}
|
||||
showMask={false}
|
||||
mt={'60px'}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<Button
|
||||
mx={'20px'}
|
||||
variant={'whitePrimary'}
|
||||
mb={1}
|
||||
isDisabled={!selectedHistoryId}
|
||||
onClick={() => {
|
||||
setSelectedHistoryId(undefined);
|
||||
initData({
|
||||
nodes: appDetail.modules,
|
||||
edges: appDetail.edges
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('core.workflow.Current workflow')}
|
||||
</Button>
|
||||
<ScrollList isLoading={showLoading} flex={'1 0 0'} px={5}>
|
||||
{list.map((data, index) => {
|
||||
const item = data.data;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={data.index}
|
||||
alignItems={'center'}
|
||||
py={4}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
fontWeight={500}
|
||||
_hover={{
|
||||
bg: 'primary.50'
|
||||
}}
|
||||
{...(selectedHistoryId === item._id && {
|
||||
color: 'primary.600'
|
||||
})}
|
||||
onClick={() => onPreview(item)}
|
||||
>
|
||||
<Box
|
||||
w={'12px'}
|
||||
h={'12px'}
|
||||
borderWidth={'2px'}
|
||||
borderColor={'primary.600'}
|
||||
borderRadius={'50%'}
|
||||
position={'relative'}
|
||||
{...(index !== list.length - 1 && {
|
||||
_after: {
|
||||
content: '""',
|
||||
height: '40px',
|
||||
width: '2px',
|
||||
bgColor: 'myGray.250',
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
left: '3px'
|
||||
}
|
||||
})}
|
||||
></Box>
|
||||
<Box ml={3} flex={'1 0 0'}>
|
||||
{formatTime2YMDHM(item.time)}
|
||||
</Box>
|
||||
{item._id === selectedHistoryId && (
|
||||
<MyTooltip label={t('core.workflow.publish.OnRevert version')}>
|
||||
<MyIcon
|
||||
name={'core/workflow/revertVersion'}
|
||||
w={'20px'}
|
||||
color={'primary.600'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openConfirm(() => onRevert(item))();
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
</CustomRightDrawer>
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(PublishHistoriesSlider);
|
@@ -36,10 +36,12 @@ import { createContext } from 'use-context-selector';
|
||||
import { defaultRunningStatus } from './constants';
|
||||
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
|
||||
type WorkflowContextType = {
|
||||
appId?: string;
|
||||
mode: 'app' | 'plugin';
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
filterAppIds?: string[];
|
||||
@@ -83,7 +85,6 @@ type WorkflowContextType = {
|
||||
};
|
||||
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
|
||||
|
||||
// debug
|
||||
// debug
|
||||
workflowDebugData:
|
||||
| {
|
||||
@@ -103,6 +104,10 @@ type WorkflowContextType = {
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
}) => Promise<void>;
|
||||
onStopNodeDebug: () => void;
|
||||
|
||||
// version history
|
||||
isShowVersionHistories: boolean;
|
||||
setIsShowVersionHistories: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
type ContextValueProps = Pick<
|
||||
@@ -201,6 +206,10 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
},
|
||||
onChangeNode: function (e: FlowNodeChangeProps): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isShowVersionHistories: false,
|
||||
setIsShowVersionHistories: function (value: React.SetStateAction<boolean>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -616,6 +625,10 @@ const WorkflowContextProvider = ({
|
||||
[onNextNodeDebug, onStopNodeDebug]
|
||||
);
|
||||
|
||||
/* Version histories */
|
||||
const [isShowVersionHistories, setIsShowVersionHistories] = useState(false);
|
||||
|
||||
/* event bus */
|
||||
useEffect(() => {
|
||||
eventBus.on(EventNameEnum.requestWorkflowStore, () => {
|
||||
eventBus.emit(EventNameEnum.receiveWorkflowStore, {
|
||||
@@ -630,6 +643,7 @@ const WorkflowContextProvider = ({
|
||||
return (
|
||||
<WorkflowContext.Provider
|
||||
value={{
|
||||
appId,
|
||||
reactFlowWrapper,
|
||||
...value,
|
||||
// node
|
||||
@@ -661,7 +675,11 @@ const WorkflowContextProvider = ({
|
||||
workflowDebugData,
|
||||
onNextNodeDebug,
|
||||
onStartNodeDebug,
|
||||
onStopNodeDebug
|
||||
onStopNodeDebug,
|
||||
|
||||
// version history
|
||||
isShowVersionHistories,
|
||||
setIsShowVersionHistories
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
7
projects/app/src/global/core/app/api.d.ts
vendored
7
projects/app/src/global/core/app/api.d.ts
vendored
@@ -25,3 +25,10 @@ export type PostPublishAppProps = {
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
};
|
||||
|
||||
export type PostRevertAppProps = {
|
||||
versionId: string;
|
||||
// edit workflow
|
||||
editNodes: AppSchema['modules'];
|
||||
editEdges: AppSchema['edges'];
|
||||
};
|
||||
|
@@ -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'));
|
||||
|
2
projects/app/src/types/i18n.d.ts
vendored
2
projects/app/src/types/i18n.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import 'i18next';
|
||||
//import common from '../../public/locales/en/common.json';
|
||||
//import common from '../../i18n/en/common.json';
|
||||
|
||||
interface I18nNamespaces {
|
||||
common: any;
|
||||
|
@@ -16,6 +16,7 @@ type State = {
|
||||
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
|
||||
publishApp(appId: string, data: PostPublishAppProps): Promise<void>;
|
||||
clearAppModules(): void;
|
||||
setAppDetail(data: AppDetailType): void;
|
||||
};
|
||||
|
||||
export const useAppStore = create<State>()(
|
||||
@@ -61,6 +62,11 @@ export const useAppStore = create<State>()(
|
||||
};
|
||||
});
|
||||
},
|
||||
setAppDetail(data: AppDetailType) {
|
||||
set((state) => {
|
||||
state.appDetail = data;
|
||||
});
|
||||
},
|
||||
|
||||
clearAppModules() {
|
||||
set((state) => {
|
||||
|
@@ -1,5 +1,13 @@
|
||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { PostPublishAppProps, PostRevertAppProps } from '@/global/core/app/api';
|
||||
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
|
||||
export const postPublishApp = (appId: string, data: PostPublishAppProps) =>
|
||||
POST(`/core/app/version/publish?appId=${appId}`, data);
|
||||
|
||||
export const getPublishList = (data: PaginationProps<{ appId: string }>) =>
|
||||
POST<PaginationResponse<AppVersionSchemaType>>('/core/app/version/list', data);
|
||||
|
||||
export const postRevertVersion = (appId: string, data: PostRevertAppProps) =>
|
||||
POST(`/core/app/version/revert?appId=${appId}`, data);
|
||||
|
Reference in New Issue
Block a user