Files
FastGPT/projects/app/src/pageComponents/dataset/list/List.tsx
heheer 7a6a396f2a optimize ParentPaths component (#5179)
* optimize ParentPaths component

* fix dataset list diaplay

* component
2025-07-10 18:16:03 +08:00

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;