mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-15 15:41:05 +00:00
462 lines
17 KiB
TypeScript
462 lines
17 KiB
TypeScript
import React, { useMemo, useRef, useState } from 'react';
|
|
import { postChangeOwner, resumeInheritPer } from '@/web/core/dataset/api';
|
|
import { Box, Flex, Grid, HStack } from '@chakra-ui/react';
|
|
import { DatasetTypeEnum, DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
|
|
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
import { useRouter } from 'next/router';
|
|
import PermissionIconText from '@/components/support/permission/IconText';
|
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|
import { type DatasetItemType } from '@fastgpt/global/core/dataset/type';
|
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|
import { checkTeamExportDatasetLimit } from '@/web/support/user/team/api';
|
|
import { downloadFetch } from '@/web/common/system/utils';
|
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
|
import dynamic from 'next/dynamic';
|
|
import { useContextSelector } from 'use-context-selector';
|
|
import { DatasetsContext } from '../../../pages/dataset/list/context';
|
|
import { DatasetPermissionList } from '@fastgpt/global/support/permission/dataset/constant';
|
|
import ConfigPerModal from '@/components/support/permission/ConfigPerModal';
|
|
import {
|
|
deleteDatasetCollaborators,
|
|
getCollaboratorList,
|
|
postUpdateDatasetCollaborators
|
|
} from '@/web/core/dataset/api/collaborator';
|
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
|
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
|
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
|
import { useTranslation } from 'next-i18next';
|
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
|
import SideTag from './SideTag';
|
|
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
|
import UserBox from '@fastgpt/web/components/common/UserBox';
|
|
|
|
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
|
|
|
|
function List() {
|
|
const { setLoading } = useSystemStore();
|
|
const { isPc } = useSystem();
|
|
const { t } = useTranslation();
|
|
const {
|
|
loadMyDatasets,
|
|
setMoveDatasetId,
|
|
refetchPaths,
|
|
editedDataset,
|
|
setEditedDataset,
|
|
onDelDataset,
|
|
onUpdateDataset,
|
|
myDatasets,
|
|
folderDetail,
|
|
setSearchKey
|
|
} = useContextSelector(DatasetsContext, (v) => v);
|
|
const [editPerDatasetId, setEditPerDatasetId] = useState<string>();
|
|
const router = useRouter();
|
|
const { parentId = null } = router.query as { parentId?: string | null };
|
|
const parentDataset = useMemo(
|
|
() => myDatasets.find((item) => String(item._id) === parentId),
|
|
[parentId, myDatasets]
|
|
);
|
|
|
|
const { openConfirm: openMoveConfirm, ConfirmModal: MoveConfirmModal } = useConfirm({
|
|
type: 'common',
|
|
title: t('common:move.confirm'),
|
|
content: t('dataset:move.hint')
|
|
});
|
|
|
|
const { runAsync: updateDataset } = useRequest2(onUpdateDataset);
|
|
|
|
const { getBoxProps } = useFolderDrag({
|
|
activeStyles: {
|
|
borderColor: 'primary.600'
|
|
},
|
|
onDrop: (dragId: string, targetId: string) => {
|
|
openMoveConfirm(() =>
|
|
updateDataset({
|
|
id: dragId,
|
|
parentId: targetId
|
|
})
|
|
)();
|
|
}
|
|
});
|
|
|
|
const editPerDataset = useMemo(
|
|
() => myDatasets.find((item) => String(item._id) === String(editPerDatasetId)),
|
|
[editPerDatasetId, myDatasets]
|
|
);
|
|
|
|
const { mutate: exportDataset } = useRequest({
|
|
mutationFn: async (dataset: DatasetItemType) => {
|
|
setLoading(true);
|
|
await checkTeamExportDatasetLimit(dataset._id);
|
|
|
|
await downloadFetch({
|
|
url: `/api/core/dataset/exportAll?datasetId=${dataset._id}`,
|
|
filename: `${dataset.name}.csv`
|
|
});
|
|
},
|
|
onSettled() {
|
|
setLoading(false);
|
|
},
|
|
successToast: t('common:core.dataset.Start export'),
|
|
errorToast: t('common:dataset.Export Dataset Limit Error')
|
|
});
|
|
|
|
const DeleteTipsMap = useRef({
|
|
[DatasetTypeEnum.folder]: t('common:dataset.deleteFolderTips'),
|
|
[DatasetTypeEnum.dataset]: t('common:core.dataset.Delete Confirm'),
|
|
[DatasetTypeEnum.websiteDataset]: t('common:core.dataset.Delete Confirm'),
|
|
[DatasetTypeEnum.externalFile]: t('common:core.dataset.Delete Confirm')
|
|
});
|
|
|
|
const formatDatasets = useMemo(
|
|
() =>
|
|
myDatasets.map((item) => {
|
|
return {
|
|
...item,
|
|
label: DatasetTypeMap[item.type]?.label,
|
|
icon: DatasetTypeMap[item.type]?.icon
|
|
};
|
|
}),
|
|
[myDatasets]
|
|
);
|
|
|
|
const { openConfirm, ConfirmModal } = useConfirm({
|
|
type: 'delete'
|
|
});
|
|
|
|
const onClickDeleteDataset = (id: string) => {
|
|
openConfirm(
|
|
() =>
|
|
onDelDataset(id).then(() => {
|
|
refetchPaths();
|
|
loadMyDatasets();
|
|
}),
|
|
undefined,
|
|
DeleteTipsMap.current[DatasetTypeEnum.dataset]
|
|
)();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{formatDatasets.length > 0 && (
|
|
<Grid
|
|
py={4}
|
|
gridTemplateColumns={
|
|
folderDetail
|
|
? ['1fr', 'repeat(2,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']
|
|
: ['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']
|
|
}
|
|
gridGap={5}
|
|
alignItems={'stretch'}
|
|
>
|
|
{formatDatasets.map((dataset, index) => {
|
|
const vectorModelAvatar = getModelProvider(dataset.vectorModel.provider)?.avatar;
|
|
|
|
return (
|
|
<MyTooltip
|
|
key={dataset._id}
|
|
label={
|
|
<Flex flexDirection={'column'} alignItems={'center'}>
|
|
<Box fontSize={'xs'} color={'myGray.500'}>
|
|
{dataset.type === DatasetTypeEnum.folder
|
|
? t('common:open_folder')
|
|
: t('common:folder.open_dataset')}
|
|
</Box>
|
|
</Flex>
|
|
}
|
|
>
|
|
<MyBox
|
|
display={'flex'}
|
|
flexDirection={'column'}
|
|
lineHeight={1.5}
|
|
h="100%"
|
|
pt={5}
|
|
pb={3}
|
|
px={5}
|
|
cursor={'pointer'}
|
|
borderWidth={1.5}
|
|
border={'base'}
|
|
boxShadow={'2'}
|
|
bg={'white'}
|
|
borderRadius={'lg'}
|
|
position={'relative'}
|
|
minH={'150px'}
|
|
{...getBoxProps({
|
|
dataId: dataset._id,
|
|
isFolder: dataset.type === DatasetTypeEnum.folder
|
|
})}
|
|
_hover={{
|
|
borderColor: 'primary.300',
|
|
boxShadow: '1.5',
|
|
'& .delete': {
|
|
display: 'block'
|
|
},
|
|
'& .more': {
|
|
display: 'flex'
|
|
},
|
|
'& .time': {
|
|
display: ['flex', 'none']
|
|
}
|
|
}}
|
|
onClick={() => {
|
|
if (dataset.type === DatasetTypeEnum.folder) {
|
|
setSearchKey('');
|
|
router.push({
|
|
pathname: '/dataset/list',
|
|
query: {
|
|
parentId: dataset._id
|
|
}
|
|
});
|
|
} else {
|
|
router.push({
|
|
pathname: '/dataset/detail',
|
|
query: {
|
|
datasetId: dataset._id
|
|
}
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
<Flex w="100%">
|
|
<Avatar src={dataset.avatar} borderRadius={6} w={'28px'} flexShrink={0} />
|
|
<Box width="0" flex="1" className="textEllipsis" color={'myGray.900'} ml={2}>
|
|
{dataset.name}
|
|
</Box>
|
|
|
|
{dataset.type !== DatasetTypeEnum.folder && (
|
|
<Box flexShrink={0} ml={2}>
|
|
<SideTag
|
|
type={dataset.type}
|
|
py={0.5}
|
|
px={2}
|
|
borderLeftRadius={'sm'}
|
|
borderRightRadius={0}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Flex>
|
|
|
|
<Box
|
|
flex={1}
|
|
className={'textEllipsis3'}
|
|
whiteSpace={'pre-wrap'}
|
|
py={3}
|
|
fontSize={'xs'}
|
|
color={'myGray.500'}
|
|
>
|
|
{dataset.intro ||
|
|
(dataset.type === DatasetTypeEnum.folder
|
|
? t('common:core.dataset.Folder placeholder')
|
|
: t('common:core.dataset.Intro Placeholder'))}
|
|
</Box>
|
|
|
|
<Flex
|
|
h={'24px'}
|
|
alignItems={'center'}
|
|
justifyContent={'space-between'}
|
|
fontSize={'sm'}
|
|
fontWeight={500}
|
|
color={'myGray.500'}
|
|
>
|
|
<HStack spacing={3.5}>
|
|
<UserBox
|
|
sourceMember={dataset.sourceMember}
|
|
fontSize="xs"
|
|
avatarSize="1rem"
|
|
spacing={0.5}
|
|
/>
|
|
<PermissionIconText
|
|
flexShrink={0}
|
|
private={dataset.private}
|
|
iconColor="myGray.400"
|
|
color={'myGray.500'}
|
|
/>
|
|
</HStack>
|
|
|
|
<HStack>
|
|
{isPc && dataset.type !== DatasetTypeEnum.folder && (
|
|
<HStack spacing={1} className="time">
|
|
<Avatar src={vectorModelAvatar} w={'0.85rem'} />
|
|
<Box color={'myGray.500'} fontSize={'mini'}>
|
|
{dataset.vectorModel.name}
|
|
</Box>
|
|
</HStack>
|
|
)}
|
|
{(dataset.type === DatasetTypeEnum.folder
|
|
? dataset.permission.hasManagePer
|
|
: dataset.permission.hasWritePer) && (
|
|
<Box
|
|
className="more"
|
|
display={['', 'none']}
|
|
borderRadius={'md'}
|
|
_hover={{
|
|
'& .icon': {
|
|
bg: 'myGray.100'
|
|
}
|
|
}}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
}}
|
|
>
|
|
<MyMenu
|
|
Button={
|
|
<Box w={'22px'} h={'22px'}>
|
|
<MyIcon
|
|
className="icon"
|
|
name={'more'}
|
|
h={'16px'}
|
|
w={'16px'}
|
|
px={1}
|
|
py={1}
|
|
borderRadius={'md'}
|
|
cursor={'pointer'}
|
|
/>
|
|
</Box>
|
|
}
|
|
menuList={[
|
|
{
|
|
children: [
|
|
{
|
|
icon: 'edit',
|
|
label: t('common:dataset.Edit Info'),
|
|
onClick: () =>
|
|
setEditedDataset({
|
|
id: dataset._id,
|
|
name: dataset.name,
|
|
intro: dataset.intro,
|
|
avatar: dataset.avatar
|
|
})
|
|
},
|
|
...((parentDataset ? parentDataset : dataset)?.permission
|
|
.hasManagePer
|
|
? [
|
|
{
|
|
icon: 'common/file/move',
|
|
label: t('common:Move'),
|
|
onClick: () => {
|
|
setMoveDatasetId(dataset._id);
|
|
}
|
|
}
|
|
]
|
|
: []),
|
|
...(dataset.permission.hasManagePer
|
|
? [
|
|
{
|
|
icon: 'key',
|
|
label: t('common:permission.Permission'),
|
|
onClick: () => setEditPerDatasetId(dataset._id)
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
},
|
|
...(dataset.type != DatasetTypeEnum.folder
|
|
? [
|
|
{
|
|
children: [
|
|
{
|
|
icon: 'export',
|
|
label: t('common:Export'),
|
|
onClick: () => {
|
|
exportDataset(dataset);
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
: []),
|
|
...(dataset.permission.hasManagePer
|
|
? [
|
|
{
|
|
children: [
|
|
{
|
|
icon: 'delete',
|
|
label: t('common:Delete'),
|
|
type: 'danger' as 'danger',
|
|
onClick: () => onClickDeleteDataset(dataset._id)
|
|
}
|
|
]
|
|
}
|
|
]
|
|
: [])
|
|
]}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</HStack>
|
|
</Flex>
|
|
</MyBox>
|
|
</MyTooltip>
|
|
);
|
|
})}
|
|
</Grid>
|
|
)}
|
|
{myDatasets.length === 0 && (
|
|
<EmptyTip
|
|
pt={'35vh'}
|
|
text={t('common:core.dataset.Empty Dataset Tips')}
|
|
flexGrow="1"
|
|
></EmptyTip>
|
|
)}
|
|
|
|
{editedDataset && (
|
|
<EditResourceModal
|
|
{...editedDataset}
|
|
title={t('common:dataset.Edit Info')}
|
|
onClose={() => setEditedDataset(undefined)}
|
|
onEdit={async (data) => {
|
|
await onUpdateDataset({
|
|
id: editedDataset.id,
|
|
name: data.name,
|
|
intro: data.intro,
|
|
avatar: data.avatar
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{!!editPerDataset && (
|
|
<ConfigPerModal
|
|
onChangeOwner={(tmbId: string) =>
|
|
postChangeOwner({
|
|
datasetId: editPerDataset._id,
|
|
ownerId: tmbId
|
|
}).then(() => loadMyDatasets())
|
|
}
|
|
hasParent={!!parentId}
|
|
refetchResource={loadMyDatasets}
|
|
isInheritPermission={editPerDataset.inheritPermission}
|
|
resumeInheritPermission={() =>
|
|
resumeInheritPer(editPerDataset._id).then(() => Promise.all([loadMyDatasets()]))
|
|
}
|
|
avatar={editPerDataset.avatar}
|
|
name={editPerDataset.name}
|
|
managePer={{
|
|
permission: editPerDataset.permission,
|
|
onGetCollaboratorList: () => getCollaboratorList(editPerDataset._id),
|
|
permissionList: DatasetPermissionList,
|
|
onUpdateCollaborators: (props) =>
|
|
postUpdateDatasetCollaborators({
|
|
...props,
|
|
datasetId: editPerDataset._id
|
|
}),
|
|
onDelOneCollaborator: async (props) =>
|
|
deleteDatasetCollaborators({
|
|
...props,
|
|
datasetId: editPerDataset._id
|
|
}),
|
|
refreshDeps: [editPerDataset._id, editPerDataset.inheritPermission]
|
|
}}
|
|
onClose={() => setEditPerDatasetId(undefined)}
|
|
/>
|
|
)}
|
|
<ConfirmModal />
|
|
<MoveConfirmModal />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default List;
|