mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 09:44:47 +00:00
4.8.5 test (#1805)
* perf: revert tip * feat: create copy app * perf: file stream read * perf: read directory over 100 files * perf: index * fix: team chat api error * lock * fix: i18n file
This commit is contained in:
@@ -4,6 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { authFileToken } from '@fastgpt/service/support/permission/controller';
|
||||
import { getDownloadStream, getFileById } from '@fastgpt/service/common/file/gridfs/controller';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { stream2Encoding } from '@fastgpt/service/common/file/gridfs/utils';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -17,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('fileId is empty');
|
||||
}
|
||||
|
||||
const [file, { fileStream, encoding }] = await Promise.all([
|
||||
const [file, fileStream] = await Promise.all([
|
||||
getFileById({ bucketName, fileId }),
|
||||
getDownloadStream({ bucketName, fileId })
|
||||
]);
|
||||
@@ -26,16 +27,26 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
return Promise.reject(CommonErrEnum.fileNotFound);
|
||||
}
|
||||
|
||||
const { stream, encoding } = await (async () => {
|
||||
if (file.metadata?.encoding) {
|
||||
return {
|
||||
stream: fileStream,
|
||||
encoding: file.metadata.encoding
|
||||
};
|
||||
}
|
||||
return stream2Encoding(fileStream);
|
||||
})();
|
||||
|
||||
res.setHeader('Content-Type', `${file.contentType}; charset=${encoding}`);
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(file.filename)}"`);
|
||||
|
||||
fileStream.pipe(res);
|
||||
stream.pipe(res);
|
||||
|
||||
fileStream.on('error', () => {
|
||||
stream.on('error', () => {
|
||||
res.status(500).end();
|
||||
});
|
||||
fileStream.on('end', () => {
|
||||
stream.on('end', () => {
|
||||
res.end();
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -47,6 +58,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
}
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '32mb'
|
||||
responseLimit: '100mb'
|
||||
}
|
||||
};
|
||||
|
50
projects/app/src/pages/api/core/app/copy.ts
Normal file
50
projects/app/src/pages/api/core/app/copy.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { onCreateApp } from './create';
|
||||
|
||||
export type copyAppQuery = {};
|
||||
|
||||
export type copyAppBody = { appId: string };
|
||||
|
||||
export type copyAppResponse = {
|
||||
appId: string;
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<copyAppBody, copyAppQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<copyAppResponse> {
|
||||
const [{ app, tmbId }] = await Promise.all([
|
||||
authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal,
|
||||
appId: req.body.appId
|
||||
}),
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal
|
||||
})
|
||||
]);
|
||||
|
||||
const appId = await onCreateApp({
|
||||
parentId: app.parentId,
|
||||
name: app.name + ' Copy',
|
||||
intro: app.intro,
|
||||
avatar: app.avatar,
|
||||
type: app.type,
|
||||
modules: app.modules,
|
||||
edges: app.edges,
|
||||
teamId: app.teamId,
|
||||
tmbId,
|
||||
pluginData: app.pluginData
|
||||
});
|
||||
|
||||
return { appId };
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
@@ -42,27 +42,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||
|
||||
// 更新模型
|
||||
await MongoApp.updateOne(
|
||||
{
|
||||
_id: appId
|
||||
},
|
||||
{
|
||||
...parseParentIdInMongo(parentId),
|
||||
name,
|
||||
type,
|
||||
avatar,
|
||||
intro,
|
||||
defaultPermission,
|
||||
...(teamTags && teamTags),
|
||||
...(formatNodes && {
|
||||
modules: formatNodes
|
||||
}),
|
||||
...(edges && {
|
||||
edges
|
||||
}),
|
||||
...(chatConfig && { chatConfig })
|
||||
}
|
||||
);
|
||||
await MongoApp.findByIdAndUpdate(appId, {
|
||||
...parseParentIdInMongo(parentId),
|
||||
...(name && { name }),
|
||||
...(type && { type }),
|
||||
...(avatar && { avatar }),
|
||||
...(intro !== undefined && { intro }),
|
||||
...(defaultPermission && { defaultPermission }),
|
||||
...(teamTags && { teamTags }),
|
||||
...(formatNodes && {
|
||||
modules: formatNodes
|
||||
}),
|
||||
...(edges && {
|
||||
edges
|
||||
}),
|
||||
...(chatConfig && { chatConfig })
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -1,50 +1,42 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { DelHistoryProps } from '@/global/core/chat/api';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/* clear chat history */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, chatId, shareId, outLinkUid } = req.query as DelHistoryProps;
|
||||
async function handler(req: ApiRequestProps<{}, DelHistoryProps>, res: NextApiResponse) {
|
||||
const { appId, chatId } = req.query;
|
||||
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
per: 'w'
|
||||
});
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.query,
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId,
|
||||
chatId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.findOneAndRemove(
|
||||
{
|
||||
appId,
|
||||
chatId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
});
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId,
|
||||
chatId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.findOneAndRemove(
|
||||
{
|
||||
appId,
|
||||
chatId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
jsonRes(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -1,42 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
/* clear chat history */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { outLinkUid, chatIds } = req.body as {
|
||||
outLinkUid: string;
|
||||
chatIds: string[];
|
||||
};
|
||||
|
||||
if (!outLinkUid) {
|
||||
throw new Error('shareId or outLinkUid is required');
|
||||
}
|
||||
|
||||
const sliceIds = chatIds.slice(0, 50);
|
||||
|
||||
await MongoChat.updateMany(
|
||||
{
|
||||
chatId: { $in: sliceIds },
|
||||
source: ChatSourceEnum.share,
|
||||
outLinkUid: { $exists: false }
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
outLinkUid
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
@@ -4,37 +4,30 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import type { DeleteChatItemProps } from '@/global/core/chat/api.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, chatId, contentId, shareId, outLinkUid } = req.query as DeleteChatItemProps;
|
||||
async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, contentId, shareId, outLinkUid } = req.query;
|
||||
|
||||
if (!contentId || !chatId) {
|
||||
return jsonRes(res);
|
||||
}
|
||||
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
await MongoChatItem.deleteOne({
|
||||
appId,
|
||||
chatId,
|
||||
dataId: contentId
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
if (!contentId || !chatId) {
|
||||
return jsonRes(res);
|
||||
}
|
||||
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.query,
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
await MongoChatItem.deleteOne({
|
||||
appId,
|
||||
chatId,
|
||||
dataId: contentId
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -1,40 +1,30 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { UpdateHistoryProps } from '@/global/core/chat/api.d';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/* update chat top, custom title */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, chatId, teamId, shareId, outLinkUid, customTitle, top } =
|
||||
req.body as UpdateHistoryProps;
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
teamId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
per: 'w'
|
||||
});
|
||||
async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, customTitle, top } = req.body;
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.body,
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
await MongoChat.findOneAndUpdate(
|
||||
{ appId, chatId },
|
||||
{
|
||||
updateTime: new Date(),
|
||||
...(customTitle !== undefined && { customTitle }),
|
||||
...(top !== undefined && { top })
|
||||
}
|
||||
);
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
await MongoChat.findOneAndUpdate(
|
||||
{ appId, chatId },
|
||||
{
|
||||
updateTime: new Date(),
|
||||
...(customTitle !== undefined && { customTitle }),
|
||||
...(top !== undefined && { top })
|
||||
}
|
||||
);
|
||||
jsonRes(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -3,18 +3,17 @@ import { getPublishList, postRevertVersion } from '@/web/core/app/api/version';
|
||||
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 { 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';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { AppContext } from './context';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||
|
||||
export type InitProps = {
|
||||
nodes: AppSchema['modules'];
|
||||
@@ -33,11 +32,11 @@ const PublishHistoriesSlider = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: t('core.workflow.publish.OnRevert version confirm')
|
||||
});
|
||||
|
||||
const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { appDetail, setAppDetail, reloadAppLatestVersion } = useContextSelector(
|
||||
AppContext,
|
||||
(v) => v
|
||||
);
|
||||
const appId = appDetail._id;
|
||||
|
||||
const [selectedHistoryId, setSelectedHistoryId] = useState<string>();
|
||||
@@ -73,8 +72,8 @@ const PublishHistoriesSlider = ({
|
||||
[initData, onClose]
|
||||
);
|
||||
|
||||
const { mutate: onRevert, isLoading: isReverting } = useRequest({
|
||||
mutationFn: async (data: AppVersionSchemaType) => {
|
||||
const { runAsync: onRevert } = useRequest2(
|
||||
async (data: AppVersionSchemaType) => {
|
||||
if (!appId) return;
|
||||
await postRevertVersion(appId, {
|
||||
versionId: data._id,
|
||||
@@ -90,10 +89,14 @@ const PublishHistoriesSlider = ({
|
||||
}));
|
||||
|
||||
onCloseSlider(data);
|
||||
reloadAppLatestVersion();
|
||||
},
|
||||
{
|
||||
successToast: appT('version.Revert success')
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const showLoading = isLoading || isReverting;
|
||||
const showLoading = isLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -173,24 +176,28 @@ const PublishHistoriesSlider = ({
|
||||
{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>
|
||||
<PopoverConfirm
|
||||
showCancel
|
||||
content={t('core.workflow.publish.OnRevert version confirm')}
|
||||
onConfirm={() => onRevert(item)}
|
||||
Trigger={
|
||||
<Box>
|
||||
<MyTooltip label={t('core.workflow.publish.OnRevert version')}>
|
||||
<MyIcon
|
||||
name={'core/workflow/revertVersion'}
|
||||
w={'20px'}
|
||||
color={'primary.600'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
</CustomRightDrawer>
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -98,7 +98,6 @@ const AppCard = () => {
|
||||
</Button>
|
||||
{appDetail.permission.hasWritePer && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
mr={3}
|
||||
size={['sm', 'md']}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<DragHandleIcon w={'16px'} />}
|
||||
|
@@ -20,6 +20,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { compareWorkflow } from '@/web/core/workflow/utils';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { publishStatusStyle } from '../constants';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const Header = ({
|
||||
appForm,
|
||||
@@ -134,7 +135,13 @@ const Header = ({
|
||||
<PopoverConfirm
|
||||
showCancel
|
||||
content={t('core.app.Publish Confirm')}
|
||||
Trigger={<Button isDisabled={isPublished}>{t('core.app.Publish')}</Button>}
|
||||
Trigger={
|
||||
<Box>
|
||||
<MyTooltip label={t('core.app.Publish app tip')}>
|
||||
<Button isDisabled={isPublished}>{t('core.app.Publish')}</Button>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
}
|
||||
onConfirm={() => onSubmitPublish(appForm)}
|
||||
/>
|
||||
</>
|
||||
|
@@ -7,15 +7,22 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext, TabEnum } from '../context';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const Logs = dynamic(() => import('../Logs/index'));
|
||||
const PublishChannel = dynamic(() => import('../Publish'));
|
||||
|
||||
const SimpleEdit = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const [appForm, setAppForm] = useState(getDefaultAppForm());
|
||||
|
||||
useBeforeunload({
|
||||
tip: t('core.common.tip.leave page')
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex h={'100%'} flexDirection={'column'} pr={3} pb={3}>
|
||||
<Header appForm={appForm} setAppForm={setAppForm} />
|
||||
|
@@ -16,6 +16,7 @@ import { useRouter } from 'next/router';
|
||||
|
||||
import AppCard from '../WorkflowComponents/AppCard';
|
||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||
|
||||
const Header = () => {
|
||||
@@ -53,6 +54,7 @@ const Header = () => {
|
||||
router.push('/app/list');
|
||||
} catch (error) {}
|
||||
}, [onSaveWorkflow, router]);
|
||||
|
||||
// effect
|
||||
useBeforeunload({
|
||||
callback: onSaveWorkflow,
|
||||
@@ -152,13 +154,17 @@ const Header = () => {
|
||||
showCancel
|
||||
content={t('core.app.Publish Confirm')}
|
||||
Trigger={
|
||||
<Button
|
||||
ml={[2, 4]}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
|
||||
>
|
||||
{t('core.app.Publish')}
|
||||
</Button>
|
||||
<Box>
|
||||
<MyTooltip label={t('core.app.Publish app tip')}>
|
||||
<Button
|
||||
ml={[2, 4]}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
|
||||
>
|
||||
{t('core.app.Publish')}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
}
|
||||
onConfirm={() => onclickPublish()}
|
||||
/>
|
||||
|
@@ -520,6 +520,7 @@ const WorkflowContextProvider = ({
|
||||
historiesDefaultData ||
|
||||
isSaving ||
|
||||
nodes.length === 0 ||
|
||||
edges.length === 0 ||
|
||||
!!workflowDebugData
|
||||
)
|
||||
return;
|
||||
|
@@ -44,6 +44,7 @@ type AppContextType = {
|
||||
chatConfig: AppChatConfigType;
|
||||
}
|
||||
| undefined;
|
||||
reloadAppLatestVersion: () => void;
|
||||
};
|
||||
|
||||
export const AppContext = createContext<AppContextType>({
|
||||
@@ -72,7 +73,10 @@ export const AppContext = createContext<AppContextType>({
|
||||
onPublish: function (data: PostPublishAppProps): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
appLatestVersion: undefined
|
||||
appLatestVersion: undefined,
|
||||
reloadAppLatestVersion: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
const AppContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
@@ -190,7 +194,8 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
onOpenTeamTagModal,
|
||||
onDelApp,
|
||||
onPublish,
|
||||
appLatestVersion
|
||||
appLatestVersion,
|
||||
reloadAppLatestVersion
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Box, Grid, Flex, IconButton, border } from '@chakra-ui/react';
|
||||
import { Box, Grid, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { delAppById, putAppById } from '@/web/core/app/api';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
@@ -34,16 +34,15 @@ const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditRe
|
||||
const ConfigPerModal = dynamic(() => import('@/components/support/permission/ConfigPerModal'));
|
||||
|
||||
import type { EditHttpPluginProps } from './HttpPluginEditModal';
|
||||
import { postCopyApp } from '@/web/core/app/api/app';
|
||||
const HttpEditModal = dynamic(() => import('./HttpPluginEditModal'));
|
||||
|
||||
const ListItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const router = useRouter();
|
||||
const { myApps, loadMyApps, onUpdateApp, setMoveAppId, folderDetail } = useContextSelector(
|
||||
AppListContext,
|
||||
(v) => v
|
||||
);
|
||||
const { myApps, loadMyApps, onUpdateApp, setMoveAppId, folderDetail, parentId } =
|
||||
useContextSelector(AppListContext, (v) => v);
|
||||
const [loadingAppId, setLoadingAppId] = useState<string>();
|
||||
|
||||
const [editedApp, setEditedApp] = useState<EditResourceInfoFormType>();
|
||||
@@ -68,27 +67,33 @@ const ListItem = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
const { openConfirm: openConfirmDel, ConfirmModal: DelConfirmModal } = useConfirm({
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { run: onclickDelApp } = useRequest2(
|
||||
const { runAsync: onclickDelApp } = useRequest2(
|
||||
(id: string) => {
|
||||
setLoadingAppId(id);
|
||||
return delAppById(id);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
loadMyApps();
|
||||
},
|
||||
onFinally() {
|
||||
setLoadingAppId(undefined);
|
||||
},
|
||||
successToast: t('common.Delete Success'),
|
||||
errorToast: t('common.Delete Failed')
|
||||
}
|
||||
);
|
||||
|
||||
const { openConfirm: openConfirmCopy, ConfirmModal: ConfirmCopyModal } = useConfirm({
|
||||
content: appT('Confirm copy app tip')
|
||||
});
|
||||
const { runAsync: onclickCopy } = useRequest2(postCopyApp, {
|
||||
onSuccess({ appId }) {
|
||||
router.push(`/app/detail?appId=${appId}`);
|
||||
loadMyApps();
|
||||
},
|
||||
successToast: appT('Create copy success')
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
@@ -218,6 +223,16 @@ const ListItem = () => {
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'copy',
|
||||
label: appT('Copy one app'),
|
||||
onClick: () =>
|
||||
openConfirmCopy(() => onclickCopy({ appId: app._id }))()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -238,7 +253,7 @@ const ListItem = () => {
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () =>
|
||||
openConfirm(
|
||||
openConfirmDel(
|
||||
() => onclickDelApp(app._id),
|
||||
undefined,
|
||||
app.type === AppTypeEnum.folder
|
||||
@@ -280,7 +295,9 @@ const ListItem = () => {
|
||||
</Grid>
|
||||
|
||||
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
|
||||
<ConfirmModal />
|
||||
|
||||
<DelConfirmModal />
|
||||
<ConfirmCopyModal />
|
||||
{!!editedApp && (
|
||||
<EditResourceModal
|
||||
{...editedApp}
|
||||
|
@@ -163,7 +163,7 @@ const ChatHistorySlider = ({
|
||||
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5} alignItems={'center'}>
|
||||
{!isPc && appId && (
|
||||
<Tabs
|
||||
w={'180px'}
|
||||
flex={'1 0 0'}
|
||||
mr={2}
|
||||
list={[
|
||||
{ label: t('core.chat.Recent use'), id: TabEnum.recently },
|
||||
@@ -176,7 +176,7 @@ const ChatHistorySlider = ({
|
||||
)}
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
flex={1}
|
||||
flex={['0', 1]}
|
||||
h={'100%'}
|
||||
color={'primary.600'}
|
||||
borderRadius={'xl'}
|
||||
|
@@ -298,7 +298,7 @@ const OutLink = ({
|
||||
onClose={onCloseSlider}
|
||||
>
|
||||
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
|
||||
<DrawerContent maxWidth={'250px'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
|
||||
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
|
||||
{children}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
@@ -83,8 +83,8 @@ const OutLink = () => {
|
||||
data: {
|
||||
messages: prompts,
|
||||
variables: {
|
||||
...customVariables,
|
||||
...variables
|
||||
...variables,
|
||||
...customVariables
|
||||
},
|
||||
appId,
|
||||
teamId,
|
||||
@@ -290,7 +290,7 @@ const OutLink = () => {
|
||||
onClose={onCloseSlider}
|
||||
>
|
||||
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
|
||||
<DrawerContent maxWidth={'250px'}>{children}</DrawerContent>
|
||||
<DrawerContent maxWidth={'75vw'}>{children}</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
})(
|
||||
|
@@ -170,12 +170,14 @@ const FileSelector = ({
|
||||
const items = e.dataTransfer.items;
|
||||
const fileList: SelectFileItemType[] = [];
|
||||
|
||||
if (e.dataTransfer.items.length <= 1) {
|
||||
const traverseFileTree = async (item: any) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (item.isFile) {
|
||||
item.file((file: File) => {
|
||||
const folderPath = (item.fullPath || '').split('/').slice(2, -1).join('/');
|
||||
const firstEntry = items[0].webkitGetAsEntry();
|
||||
|
||||
if (firstEntry?.isDirectory && items.length === 1) {
|
||||
{
|
||||
const readFile = (entry: any) => {
|
||||
return new Promise((resolve) => {
|
||||
entry.file((file: File) => {
|
||||
const folderPath = (entry.fullPath || '').split('/').slice(2, -1).join('/');
|
||||
|
||||
if (filterTypeReg.test(file.name)) {
|
||||
fileList.push({
|
||||
@@ -184,24 +186,45 @@ const FileSelector = ({
|
||||
file
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
resolve(file);
|
||||
});
|
||||
} else if (item.isDirectory) {
|
||||
const dirReader = item.createReader();
|
||||
});
|
||||
};
|
||||
const traverseFileTree = (dirReader: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let fileNum = 0;
|
||||
dirReader.readEntries(async (entries: any[]) => {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
await traverseFileTree(entries[i]);
|
||||
for await (const entry of entries) {
|
||||
if (entry.isFile) {
|
||||
await readFile(entry);
|
||||
fileNum++;
|
||||
} else if (entry.isDirectory) {
|
||||
await traverseFileTree(entry.createReader());
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
for await (const item of items) {
|
||||
await traverseFileTree(item.webkitGetAsEntry());
|
||||
// chrome: readEntries will return 100 entries at most
|
||||
if (fileNum === 100) {
|
||||
await traverseFileTree(dirReader);
|
||||
}
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
for await (const item of items) {
|
||||
const entry = item.webkitGetAsEntry();
|
||||
if (entry) {
|
||||
if (entry.isFile) {
|
||||
await readFile(entry);
|
||||
} else if (entry.isDirectory) {
|
||||
//@ts-ignore
|
||||
await traverseFileTree(entry.createReader());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (firstEntry?.isFile) {
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
let isErr = files.some((item) => item.type === '');
|
||||
if (isErr) {
|
||||
@@ -220,6 +243,11 @@ const FileSelector = ({
|
||||
file
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
return toast({
|
||||
title: fileT('upload error description'),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
selectFileCallback(fileList.slice(0, maxCount));
|
||||
|
Reference in New Issue
Block a user