mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 09:44:47 +00:00
Feat: App folder and permission (#1726)
* app folder * feat: app foldere * fix: run app param error * perf: select app ux * perf: folder rerender * fix: ts * fix: parentId * fix: permission * perf: loading ux * perf: per select ux * perf: clb context * perf: query extension tip * fix: ts * perf: app detail per * perf: default per
This commit is contained in:
@@ -17,7 +17,6 @@ import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DateRangePicker, {
|
||||
type DateRangeType
|
||||
} from '@fastgpt/web/components/common/DateRangePicker';
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { CreateAppParams } from '@/global/core/app/api.d';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
@@ -9,17 +8,24 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import type { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const {
|
||||
name = 'APP',
|
||||
avatar,
|
||||
type = AppTypeEnum.advanced,
|
||||
modules,
|
||||
edges
|
||||
} = req.body as CreateAppParams;
|
||||
export type CreateAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
type?: AppTypeEnum;
|
||||
modules: AppSchema['modules'];
|
||||
edges?: AppSchema['edges'];
|
||||
};
|
||||
|
||||
if (!name || !Array.isArray(modules)) {
|
||||
async function handler(req: ApiRequestProps<CreateAppBody>, res: NextApiResponse<any>) {
|
||||
const { parentId, name, avatar, type, modules, edges } = req.body;
|
||||
|
||||
if (!name || !type || !Array.isArray(modules)) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
@@ -34,6 +40,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const [{ _id: appId }] = await MongoApp.create(
|
||||
[
|
||||
{
|
||||
...parseParentIdInMongo(parentId),
|
||||
avatar,
|
||||
name,
|
||||
teamId,
|
||||
|
@@ -9,6 +9,7 @@ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { appId } = req.query as { appId: string };
|
||||
@@ -17,50 +18,61 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||
// Auth owner (folder owner, can delete all apps in the folder)
|
||||
const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||
|
||||
const apps = await findAppAndAllChildren({
|
||||
teamId,
|
||||
appId,
|
||||
fields: '_id'
|
||||
});
|
||||
|
||||
console.log(apps);
|
||||
|
||||
// 删除对应的聊天
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// 删除分享链接
|
||||
await MongoOutLink.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// delete version
|
||||
await MongoAppVersion.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChatInputGuide.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// delete app
|
||||
await MongoApp.deleteOne(
|
||||
{
|
||||
_id: appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
for await (const app of apps) {
|
||||
const appId = app._id;
|
||||
// Chats
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// 删除分享链接
|
||||
await MongoOutLink.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// delete version
|
||||
await MongoAppVersion.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChatInputGuide.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// delete app
|
||||
await MongoApp.deleteOne(
|
||||
{
|
||||
_id: appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
@@ -11,7 +11,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
// 凭证校验
|
||||
const { app } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
const { app } = await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
|
||||
|
||||
if (!app.permission.hasWritePer) {
|
||||
app.modules = [];
|
||||
app.edges = [];
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal file
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
|
||||
export type CreateAppFolderBody = {
|
||||
parentId?: ParentIdType;
|
||||
name: string;
|
||||
intro?: string;
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<CreateAppFolderBody>, res: NextApiResponse<any>) {
|
||||
const { name, intro, parentId } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
|
||||
// Create app
|
||||
await MongoApp.create({
|
||||
...parseParentIdInMongo(parentId),
|
||||
avatar: FolderImgUrl,
|
||||
name,
|
||||
intro,
|
||||
teamId,
|
||||
tmbId,
|
||||
type: AppTypeEnum.folder
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal file
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type {
|
||||
ParentIdType,
|
||||
ParentTreePathItemType
|
||||
} from '@fastgpt/global/common/parentFolder/type.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<ParentTreePathItemType[]> {
|
||||
const { parentId } = req.query as { parentId: string };
|
||||
|
||||
if (!parentId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await authApp({ req, authToken: true, appId: parentId, per: ReadPermissionVal });
|
||||
|
||||
return await getParents(parentId);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
async function getParents(parentId: ParentIdType): Promise<ParentTreePathItemType[]> {
|
||||
if (!parentId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parent = await MongoApp.findById(parentId, 'name parentId');
|
||||
|
||||
if (!parent) return [];
|
||||
|
||||
const paths = await getParents(parent.parentId);
|
||||
paths.push({ parentId, parentName: parent.name });
|
||||
|
||||
return paths;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
@@ -9,8 +9,21 @@ import {
|
||||
ReadPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
|
||||
export type ListAppBody = {
|
||||
parentId: ParentIdType;
|
||||
type?: AppTypeEnum;
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<ListAppBody>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<AppListItemType[]> {
|
||||
// 凭证校验
|
||||
const {
|
||||
teamId,
|
||||
@@ -22,9 +35,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const { parentId, type } = req.body;
|
||||
|
||||
/* temp: get all apps and per */
|
||||
const [myApps, rpList] = await Promise.all([
|
||||
MongoApp.find({ teamId }, '_id avatar name intro tmbId defaultPermission')
|
||||
MongoApp.find(
|
||||
{ teamId, ...(type && { type }), ...parseParentIdInMongo(parentId) },
|
||||
'_id avatar type name intro tmbId defaultPermission'
|
||||
)
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
@@ -54,10 +72,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
return filterApps.map((app) => ({
|
||||
_id: app._id,
|
||||
avatar: app.avatar,
|
||||
type: app.type,
|
||||
name: app.name,
|
||||
intro: app.intro,
|
||||
permission: app.permission,
|
||||
defaultPermission: app.defaultPermission
|
||||
defaultPermission: app.defaultPermission || AppDefaultPermissionVal
|
||||
}));
|
||||
}
|
||||
|
||||
|
@@ -9,10 +9,12 @@ import {
|
||||
WritePermissionVal,
|
||||
OwnerPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const {
|
||||
parentId,
|
||||
name,
|
||||
avatar,
|
||||
type,
|
||||
@@ -49,6 +51,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
_id: appId
|
||||
},
|
||||
{
|
||||
...parseParentIdInMongo(parentId),
|
||||
name,
|
||||
type,
|
||||
avatar,
|
||||
|
@@ -92,7 +92,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
data: {
|
||||
list: searchRes,
|
||||
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
|
||||
usingQueryExtension: !!aiExtensionResult,
|
||||
queryExtensionModel: aiExtensionResult?.model,
|
||||
...result
|
||||
}
|
||||
});
|
||||
|
@@ -20,7 +20,7 @@ import Avatar from '@/components/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import MemberManager from '@/components/support/permission/MemberManager';
|
||||
import { CollaboratorContextProvider } from '@/components/support/permission/MemberManager/context';
|
||||
import {
|
||||
postUpdateAppCollaborators,
|
||||
deleteAppCollaborators,
|
||||
@@ -29,12 +29,12 @@ import {
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/web/core/app/context/appContext';
|
||||
import {
|
||||
AppDefaultPermission,
|
||||
AppDefaultPermissionVal,
|
||||
AppPermissionList
|
||||
} from '@fastgpt/global/support/permission/app/constant';
|
||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -181,25 +181,57 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
{/* role */}
|
||||
{appDetail.permission.hasManagePer && (
|
||||
<>
|
||||
{' '}
|
||||
<Box mt="4">
|
||||
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="2"
|
||||
per={defaultPermission}
|
||||
defaultPer={AppDefaultPermission}
|
||||
readPer={ReadPermissionVal}
|
||||
writePer={WritePermissionVal}
|
||||
defaultPer={AppDefaultPermissionVal}
|
||||
onChange={(v) => setValue('defaultPermission', v)}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={6}>
|
||||
<MemberManager
|
||||
<CollaboratorContextProvider
|
||||
permission={appDetail.permission}
|
||||
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
|
||||
permissionList={AppPermissionList}
|
||||
onUpdateCollaborators={onUpdateCollaborators}
|
||||
onDelOneCollaborator={onDelCollaborator}
|
||||
/>
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<Box fontSize={'sm'}>协作者</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common.Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</CollaboratorContextProvider>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
@@ -6,7 +6,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { delModelById } from '@/web/core/app/api';
|
||||
import { delAppById } from '@/web/core/app/api';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -45,7 +45,7 @@ const AppCard = () => {
|
||||
const { mutate: handleDelModel, isLoading } = useRequest({
|
||||
mutationFn: async () => {
|
||||
if (!appDetail) return null;
|
||||
await delModelById(appDetail._id);
|
||||
await delAppById(appDetail._id);
|
||||
return 'success';
|
||||
},
|
||||
onSuccess(res) {
|
||||
|
@@ -297,7 +297,7 @@ const EditForm = ({
|
||||
similarity={getValues('dataset.similarity')}
|
||||
limit={getValues('dataset.limit')}
|
||||
usingReRank={getValues('dataset.usingReRank')}
|
||||
usingQueryExtension={getValues('dataset.datasetSearchUsingExtensionQuery')}
|
||||
queryExtensionModel={getValues('dataset.datasetSearchExtensionModel')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@@ -25,6 +25,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
@@ -32,12 +34,15 @@ type FormType = {
|
||||
templateId: string;
|
||||
};
|
||||
|
||||
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
|
||||
const CreateModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||
|
||||
const theme = useTheme();
|
||||
const { isPc, feConfigs } = useSystemStore();
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
avatar: '',
|
||||
@@ -82,6 +87,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
return Promise.reject(t('core.dataset.error.Template does not exist'));
|
||||
}
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: data.avatar || template.avatar,
|
||||
name: data.name,
|
||||
type: template.type,
|
||||
@@ -91,7 +97,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
},
|
||||
onSuccess(id: string) {
|
||||
router.push(`/app/detail?appId=${id}`);
|
||||
onSuccess();
|
||||
loadMyApps();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common.Create Success'),
|
||||
|
294
projects/app/src/pages/app/list/component/List.tsx
Normal file
294
projects/app/src/pages/app/list/component/List.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import React, { useMemo, useState } from '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';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import {
|
||||
AppDefaultPermissionVal,
|
||||
AppPermissionList
|
||||
} from '@fastgpt/global/support/permission/app/constant';
|
||||
import {
|
||||
deleteAppCollaborators,
|
||||
getCollaboratorList,
|
||||
postUpdateAppCollaborators
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
|
||||
const ConfigPerModal = dynamic(() => import('@/components/support/permission/ConfigPerModal'));
|
||||
|
||||
const ListItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const router = useRouter();
|
||||
const { myApps, loadMyApps, onUpdateApp, setMoveAppId } = useContextSelector(
|
||||
AppListContext,
|
||||
(v) => v
|
||||
);
|
||||
const [loadingAppId, setLoadingAppId] = useState<string>();
|
||||
|
||||
const [editedApp, setEditedApp] = useState<EditResourceInfoFormType>();
|
||||
const [editPerAppIndex, setEditPerAppIndex] = useState<number>();
|
||||
const editPerApp = useMemo(
|
||||
() => (editPerAppIndex !== undefined ? myApps[editPerAppIndex] : undefined),
|
||||
[editPerAppIndex, myApps]
|
||||
);
|
||||
|
||||
const { getBoxProps } = useFolderDrag({
|
||||
activeStyles: {
|
||||
borderColor: 'primary.600'
|
||||
},
|
||||
onDrop: async (dragId: string, targetId: string) => {
|
||||
setLoadingAppId(dragId);
|
||||
try {
|
||||
await putAppById(dragId, { parentId: targetId });
|
||||
loadMyApps();
|
||||
} catch (error) {}
|
||||
setLoadingAppId(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { run: onclickDelApp } = useRequest2(
|
||||
(id: string) => {
|
||||
setLoadingAppId(id);
|
||||
return delAppById(id);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
loadMyApps();
|
||||
},
|
||||
onFinally() {
|
||||
setLoadingAppId(undefined);
|
||||
},
|
||||
successToast: t('common.Delete Success'),
|
||||
errorToast: t('common.Delete Failed')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
py={[4, 6]}
|
||||
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
|
||||
gridGap={5}
|
||||
>
|
||||
{myApps.map((app, index) => (
|
||||
<MyTooltip
|
||||
key={app._id}
|
||||
label={
|
||||
app.type === AppTypeEnum.folder
|
||||
? t('common.folder.Open folder')
|
||||
: app.permission.hasWritePer
|
||||
? appT('Edit app')
|
||||
: appT('Go to chat')
|
||||
}
|
||||
>
|
||||
<MyBox
|
||||
isLoading={loadingAppId === app._id}
|
||||
lineHeight={1.5}
|
||||
h={'100%'}
|
||||
py={3}
|
||||
px={5}
|
||||
cursor={'pointer'}
|
||||
borderWidth={'1.5px'}
|
||||
borderColor={'borderColor.low'}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
userSelect={'none'}
|
||||
position={'relative'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300',
|
||||
boxShadow: '1.5',
|
||||
'& .more': {
|
||||
display: 'flex'
|
||||
},
|
||||
'& .chat': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (app.type === AppTypeEnum.folder) {
|
||||
router.push({
|
||||
query: {
|
||||
parentId: app._id
|
||||
}
|
||||
});
|
||||
} else if (app.permission.hasWritePer) {
|
||||
router.push(`/app/detail?appId=${app._id}`);
|
||||
} else {
|
||||
router.push(`/chat?appId=${app._id}`);
|
||||
}
|
||||
}}
|
||||
{...getBoxProps({
|
||||
dataId: app._id,
|
||||
isFolder: app.type === AppTypeEnum.folder
|
||||
})}
|
||||
>
|
||||
<Flex alignItems={'center'} h={'38px'}>
|
||||
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
|
||||
<Box ml={3}>{app.name}</Box>
|
||||
{app.permission.hasManagePer && (
|
||||
<Box
|
||||
className="more"
|
||||
position={'absolute'}
|
||||
top={3.5}
|
||||
right={4}
|
||||
display={['', 'none']}
|
||||
>
|
||||
<MyMenu
|
||||
Button={
|
||||
<IconButton
|
||||
size={'xsSquare'}
|
||||
variant={'transparentBase'}
|
||||
icon={<MyIcon name={'more'} w={'1rem'} />}
|
||||
aria-label={''}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: '编辑信息',
|
||||
onClick: () =>
|
||||
setEditedApp({
|
||||
id: app._id,
|
||||
avatar: app.avatar,
|
||||
name: app.name,
|
||||
intro: app.intro
|
||||
})
|
||||
},
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
label: t('common.folder.Move to'),
|
||||
onClick: () => setMoveAppId(app._id)
|
||||
},
|
||||
...(app.permission.hasManagePer
|
||||
? [
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
label: t('permission.Permission'),
|
||||
onClick: () => setEditPerAppIndex(index)
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
...(app.permission.isOwner
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
type: 'danger' as 'danger',
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () =>
|
||||
openConfirm(
|
||||
() => onclickDelApp(app._id),
|
||||
undefined,
|
||||
app.type === AppTypeEnum.folder
|
||||
? appT('Confirm delete folder tip')
|
||||
: appT('Confirm Del App Tip')
|
||||
)()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
flex={1}
|
||||
className={'textEllipsis3'}
|
||||
py={2}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{app.intro || '还没写介绍~'}
|
||||
</Box>
|
||||
<Flex h={'34px'} alignItems={'flex-end'}>
|
||||
<Box flex={1}>
|
||||
<PermissionIconText
|
||||
defaultPermission={app.defaultPermission}
|
||||
color={'myGray.600'}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
|
||||
<ConfirmModal />
|
||||
{!!editedApp && (
|
||||
<EditResourceModal
|
||||
{...editedApp}
|
||||
title="应用信息编辑"
|
||||
onClose={() => {
|
||||
setEditedApp(undefined);
|
||||
}}
|
||||
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
|
||||
/>
|
||||
)}
|
||||
{!!editPerApp && (
|
||||
<ConfigPerModal
|
||||
avatar={editPerApp.avatar}
|
||||
name={editPerApp.name}
|
||||
defaultPer={{
|
||||
value: editPerApp.defaultPermission,
|
||||
defaultValue: AppDefaultPermissionVal,
|
||||
onChange: (e) => {
|
||||
return onUpdateApp(editPerApp._id, { defaultPermission: e });
|
||||
}
|
||||
}}
|
||||
managePer={{
|
||||
permission: editPerApp.permission,
|
||||
onGetCollaboratorList: () => getCollaboratorList(editPerApp._id),
|
||||
permissionList: AppPermissionList,
|
||||
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
|
||||
return postUpdateAppCollaborators({
|
||||
tmbIds,
|
||||
permission,
|
||||
appId: editPerApp._id
|
||||
});
|
||||
},
|
||||
onDelOneCollaborator: (tmbId: string) =>
|
||||
deleteAppCollaborators({
|
||||
appId: editPerApp._id,
|
||||
tmbId
|
||||
})
|
||||
}}
|
||||
onClose={() => setEditPerAppIndex(undefined)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListItem;
|
138
projects/app/src/pages/app/list/component/context.tsx
Normal file
138
projects/app/src/pages/app/list/component/context.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React, { ReactNode, useCallback, useState } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getAppDetailById, getMyApps, putAppById } from '@/web/core/app/api';
|
||||
import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import {
|
||||
GetResourceFolderListProps,
|
||||
ParentIdType,
|
||||
ParentTreePathItemType
|
||||
} from '@fastgpt/global/common/parentFolder/type';
|
||||
import { AppUpdateParams } from '@/global/core/app/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
|
||||
type AppListContextType = {
|
||||
parentId?: string | null;
|
||||
myApps: AppListItemType[];
|
||||
loadMyApps: () => void;
|
||||
isFetchingApps: boolean;
|
||||
folderDetail: AppDetailType | undefined | null;
|
||||
paths: ParentTreePathItemType[];
|
||||
onUpdateApp: (id: string, data: AppUpdateParams) => Promise<any>;
|
||||
setMoveAppId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
};
|
||||
|
||||
export const AppListContext = createContext<AppListContextType>({
|
||||
parentId: undefined,
|
||||
myApps: [],
|
||||
loadMyApps: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isFetchingApps: false,
|
||||
folderDetail: undefined,
|
||||
paths: [],
|
||||
onUpdateApp: function (id: string, data: AppUpdateParams): Promise<any> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
setMoveAppId: function (value: React.SetStateAction<string | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
|
||||
|
||||
const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { appT } = useI18n();
|
||||
const router = useRouter();
|
||||
const { parentId = null } = router.query as { parentId?: string | null };
|
||||
|
||||
const {
|
||||
data = [],
|
||||
runAsync: loadMyApps,
|
||||
loading: isFetchingApps
|
||||
} = useRequest2(() => getMyApps({ parentId }), {
|
||||
manual: false,
|
||||
refreshOnWindowFocus: true,
|
||||
refreshDeps: [parentId]
|
||||
});
|
||||
|
||||
const { data: paths = [], runAsync: refetchPaths } = useRequest2(
|
||||
() => getAppFolderPath(parentId),
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [parentId]
|
||||
}
|
||||
);
|
||||
|
||||
const { data: folderDetail, runAsync: refetchFolderDetail } = useRequest2(
|
||||
() => {
|
||||
if (parentId) return getAppDetailById(parentId);
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [parentId]
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onUpdateApp } = useRequest2((id: string, data: AppUpdateParams) =>
|
||||
putAppById(id, data).then(async (res) => {
|
||||
await Promise.all([refetchFolderDetail(), refetchPaths(), loadMyApps()]);
|
||||
return res;
|
||||
})
|
||||
);
|
||||
|
||||
const [moveAppId, setMoveAppId] = useState<string>();
|
||||
const onMoveApp = useCallback(
|
||||
async (parentId: ParentIdType) => {
|
||||
if (!moveAppId) return;
|
||||
await onUpdateApp(moveAppId, { parentId });
|
||||
},
|
||||
[moveAppId, onUpdateApp]
|
||||
);
|
||||
|
||||
const getAppFolderList = useCallback(({ parentId }: GetResourceFolderListProps) => {
|
||||
return getMyApps({
|
||||
parentId,
|
||||
type: AppTypeEnum.folder
|
||||
}).then((res) =>
|
||||
res.map((item) => ({
|
||||
id: item._id,
|
||||
name: item.name
|
||||
}))
|
||||
);
|
||||
}, []);
|
||||
|
||||
const contextValue: AppListContextType = {
|
||||
parentId,
|
||||
myApps: data,
|
||||
loadMyApps,
|
||||
isFetchingApps,
|
||||
folderDetail,
|
||||
paths,
|
||||
onUpdateApp,
|
||||
setMoveAppId
|
||||
};
|
||||
return (
|
||||
<AppListContext.Provider value={contextValue}>
|
||||
{children}
|
||||
{!!moveAppId && (
|
||||
<MoveModal
|
||||
moveResourceId={moveAppId}
|
||||
server={getAppFolderList}
|
||||
title={appT('Move app')}
|
||||
onClose={() => setMoveAppId(undefined)}
|
||||
onConfirm={onMoveApp}
|
||||
/>
|
||||
)}
|
||||
</AppListContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppListContextProvider;
|
@@ -1,192 +1,209 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { delModelById } from '@/web/core/app/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import CreateModal from './component/CreateModal';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import List from './component/List';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postCreateAppFolder } from '@/web/core/app/api/app';
|
||||
import type { EditFolderFormType } from '@fastgpt/web/components/common/MyModal/EditFolderModal';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import AppListContextProvider, { AppListContext } from './component/context';
|
||||
import FolderPath from '@/components/common/folder/Path';
|
||||
import { useRouter } from 'next/router';
|
||||
import FolderSlideCard from '@/components/common/folder/SlideCard';
|
||||
import { delAppById } from '@/web/core/app/api';
|
||||
import {
|
||||
AppDefaultPermissionVal,
|
||||
AppPermissionList
|
||||
} from '@fastgpt/global/support/permission/app/constant';
|
||||
import {
|
||||
deleteAppCollaborators,
|
||||
getCollaboratorList,
|
||||
postUpdateAppCollaborators
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const EditFolderModal = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
|
||||
);
|
||||
|
||||
const MyApps = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { appT, commonT } = useI18n();
|
||||
|
||||
const { appT } = useI18n();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const {
|
||||
paths,
|
||||
parentId,
|
||||
myApps,
|
||||
loadMyApps,
|
||||
onUpdateApp,
|
||||
setMoveAppId,
|
||||
isFetchingApps,
|
||||
folderDetail
|
||||
} = useContextSelector(AppListContext, (v) => v);
|
||||
const { userInfo } = useUserStore();
|
||||
const { myApps, loadMyApps } = useAppStore();
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: '确认删除该应用所有信息?'
|
||||
});
|
||||
|
||||
const {
|
||||
isOpen: isOpenCreateModal,
|
||||
onOpen: onOpenCreateModal,
|
||||
onClose: onCloseCreateModal
|
||||
} = useDisclosure();
|
||||
const [editFolder, setEditFolder] = useState<EditFolderFormType>();
|
||||
|
||||
/* 点击删除 */
|
||||
const onclickDelApp = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
await delModelById(id);
|
||||
toast({
|
||||
title: '删除成功',
|
||||
status: 'success'
|
||||
});
|
||||
loadMyApps();
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: err?.message || t('common.Delete Failed'),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
const { runAsync: onCreateFolder } = useRequest2(postCreateAppFolder, {
|
||||
onSuccess() {
|
||||
loadMyApps();
|
||||
},
|
||||
[toast, loadMyApps, t]
|
||||
);
|
||||
|
||||
/* 加载模型 */
|
||||
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(), {
|
||||
refetchOnMount: true
|
||||
errorToast: 'Error'
|
||||
});
|
||||
const { runAsync: onDeleFolder } = useRequest2(delAppById, {
|
||||
onSuccess() {
|
||||
router.replace({
|
||||
query: {
|
||||
parentId: folderDetail?.parentId
|
||||
}
|
||||
});
|
||||
},
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer isLoading={isFetching} insertProps={{ px: [5, '48px'] }}>
|
||||
<Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
|
||||
{appT('My Apps')}
|
||||
<PageContainer
|
||||
isLoading={myApps.length === 0 && isFetchingApps}
|
||||
insertProps={{ px: folderDetail ? [4, 6] : [4, 10] }}
|
||||
>
|
||||
<Flex gap={5}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Flex pt={[4, 6]} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<FolderPath
|
||||
paths={paths}
|
||||
FirstPathDom={
|
||||
<Box letterSpacing={1} fontSize={['md', 'lg']} color={'myGray.900'}>
|
||||
{appT('My Apps')}
|
||||
</Box>
|
||||
}
|
||||
onClick={(parentId) => {
|
||||
router.push({
|
||||
query: {
|
||||
parentId
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{userInfo?.team.permission.hasWritePer && (
|
||||
<MyMenu
|
||||
width={150}
|
||||
iconSize="1.5rem"
|
||||
Button={
|
||||
<Button variant={'primary'} leftIcon={<AddIcon />}>
|
||||
<Box>{t('common.Create New')}</Box>
|
||||
</Button>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'core/app/simpleBot',
|
||||
label: appT('Create bot'),
|
||||
description: appT('Create one ai app'),
|
||||
onClick: onOpenCreateModal
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: FolderIcon,
|
||||
label: t('Folder'),
|
||||
onClick: () => setEditFolder({})
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<List />
|
||||
</Box>
|
||||
{userInfo?.team.permission.hasWritePer && (
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{commonT('New Create')}
|
||||
</Button>
|
||||
{!!folderDetail && isPc && (
|
||||
<Box pt={[4, 6]}>
|
||||
<FolderSlideCard
|
||||
name={folderDetail.name}
|
||||
intro={folderDetail.intro}
|
||||
onEdit={() => {
|
||||
setEditFolder({
|
||||
id: folderDetail._id,
|
||||
name: folderDetail.name,
|
||||
intro: folderDetail.intro
|
||||
});
|
||||
}}
|
||||
onMove={() => setMoveAppId(folderDetail._id)}
|
||||
deleteTip={appT('Confirm delete folder tip')}
|
||||
onDelete={() => onDeleFolder(folderDetail._id)}
|
||||
defaultPer={{
|
||||
value: folderDetail.defaultPermission,
|
||||
defaultValue: AppDefaultPermissionVal,
|
||||
onChange: (e) => {
|
||||
return onUpdateApp(folderDetail._id, { defaultPermission: e });
|
||||
}
|
||||
}}
|
||||
managePer={{
|
||||
permission: folderDetail.permission,
|
||||
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
|
||||
permissionList: AppPermissionList,
|
||||
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
|
||||
return postUpdateAppCollaborators({
|
||||
tmbIds,
|
||||
permission,
|
||||
appId: folderDetail._id
|
||||
});
|
||||
},
|
||||
onDelOneCollaborator: (tmbId: string) =>
|
||||
deleteAppCollaborators({
|
||||
appId: folderDetail._id,
|
||||
tmbId
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
<Grid
|
||||
py={[4, 6]}
|
||||
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
|
||||
gridGap={5}
|
||||
>
|
||||
{myApps.map((app) => (
|
||||
<MyTooltip
|
||||
key={app._id}
|
||||
label={app.permission.hasWritePer ? appT('To Settings') : appT('To Chat')}
|
||||
>
|
||||
<Box
|
||||
lineHeight={1.5}
|
||||
h={'100%'}
|
||||
py={3}
|
||||
px={5}
|
||||
cursor={'pointer'}
|
||||
borderWidth={'1.5px'}
|
||||
borderColor={'borderColor.low'}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
userSelect={'none'}
|
||||
position={'relative'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300',
|
||||
boxShadow: '1.5',
|
||||
'& .delete': {
|
||||
display: 'flex'
|
||||
},
|
||||
'& .chat': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (app.permission.hasWritePer) {
|
||||
router.push(`/app/detail?appId=${app._id}`);
|
||||
} else {
|
||||
router.push(`/chat?appId=${app._id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex alignItems={'center'} h={'38px'}>
|
||||
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
|
||||
<Box ml={3}>{app.name}</Box>
|
||||
{app.permission.isOwner && (
|
||||
<IconButton
|
||||
className="delete"
|
||||
position={'absolute'}
|
||||
top={4}
|
||||
right={4}
|
||||
size={'xsSquare'}
|
||||
variant={'whiteDanger'}
|
||||
icon={<MyIcon name={'delete'} w={'14px'} />}
|
||||
aria-label={'delete'}
|
||||
display={['', 'none']}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openConfirm(() => onclickDelApp(app._id))();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
flex={1}
|
||||
className={'textEllipsis3'}
|
||||
py={2}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'xs'}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{app.intro || '这个应用还没写介绍~'}
|
||||
</Box>
|
||||
<Flex h={'34px'} alignItems={'flex-end'}>
|
||||
<Box flex={1}>
|
||||
<PermissionIconText
|
||||
defaultPermission={app.defaultPermission}
|
||||
color={'myGray.600'}
|
||||
/>
|
||||
</Box>
|
||||
{app.permission.hasWritePer && (
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'xsSquare'}
|
||||
variant={'whitePrimary'}
|
||||
icon={
|
||||
<MyTooltip label={'去聊天'}>
|
||||
<MyIcon name={'core/chat/chatLight'} w={'14px'} />
|
||||
</MyTooltip>
|
||||
}
|
||||
aria-label={'chat'}
|
||||
display={['', 'none']}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/chat?appId=${app._id}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
|
||||
<ConfirmModal />
|
||||
{isOpenCreateModal && (
|
||||
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps()} />
|
||||
{!!editFolder && (
|
||||
<EditFolderModal
|
||||
{...editFolder}
|
||||
onClose={() => setEditFolder(undefined)}
|
||||
onCreate={(data) => onCreateFolder({ ...data, parentId })}
|
||||
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
|
||||
/>
|
||||
)}
|
||||
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} />}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
function ContextRender() {
|
||||
return (
|
||||
<AppListContextProvider>
|
||||
<MyApps />
|
||||
</AppListContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextRender;
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
@@ -194,5 +211,3 @@ export async function getServerSideProps(content: any) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MyApps;
|
||||
|
@@ -108,7 +108,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
usingReRank: res.usingReRank,
|
||||
limit: res.limit,
|
||||
similarity: res.similarity,
|
||||
usingQueryExtension: res.usingQueryExtension
|
||||
queryExtensionModel: res.queryExtensionModel
|
||||
};
|
||||
pushDatasetTestItem(testItem);
|
||||
setDatasetTestItem(testItem);
|
||||
@@ -430,7 +430,7 @@ const TestResults = React.memo(function TestResults({
|
||||
similarity={datasetTestItem.similarity}
|
||||
limit={datasetTestItem.limit}
|
||||
usingReRank={datasetTestItem.usingReRank}
|
||||
usingQueryExtension={datasetTestItem.usingQueryExtension}
|
||||
queryExtensionModel={datasetTestItem.queryExtensionModel}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
Reference in New Issue
Block a user