feat: move dataset (#277)

This commit is contained in:
Archer 2023-09-11 18:23:51 +08:00 committed by GitHub
parent ae2887e956
commit b46048609c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 422 additions and 60 deletions

View File

@ -5,7 +5,9 @@
"Create New": "Create", "Create New": "Create",
"Dataset": "Dataset", "Dataset": "Dataset",
"Folder": "Folder", "Folder": "Folder",
"Move": "Move",
"Name": "Name", "Name": "Name",
"Rename": "Rename",
"Running": "Running", "Running": "Running",
"Select value is empty": "Select value is empty", "Select value is empty": "Select value is empty",
"UnKnow": "UnKnow", "UnKnow": "UnKnow",
@ -163,6 +165,7 @@
}, },
"kb": { "kb": {
"Chunk Length": "Chunk Length", "Chunk Length": "Chunk Length",
"Confirm move the folder": "Confirm Move",
"Confirm to delete the file": "Are you sure to delete the file and all its data?", "Confirm to delete the file": "Are you sure to delete the file and all its data?",
"Create Folder": "Create Folder", "Create Folder": "Create Folder",
"Delete Dataset Error": "Delete dataset failed", "Delete Dataset Error": "Delete dataset failed",
@ -171,7 +174,9 @@
"Filename": "Filename", "Filename": "Filename",
"Files": "{{total}} Files", "Files": "{{total}} Files",
"Folder Name": "Input folder name", "Folder Name": "Input folder name",
"Move Failed": "Move Failed",
"My Dataset": "My Dataset", "My Dataset": "My Dataset",
"No Folder": "No Folder",
"Other Data": "Other Data", "Other Data": "Other Data",
"Select Dataset": "Select Dataset", "Select Dataset": "Select Dataset",
"Select Folder": "Enter folder", "Select Folder": "Enter folder",

View File

@ -5,7 +5,9 @@
"Create New": "新建", "Create New": "新建",
"Dataset": "知识库", "Dataset": "知识库",
"Folder": "文件夹", "Folder": "文件夹",
"Move": "移动",
"Name": "名称", "Name": "名称",
"Rename": "重命名",
"Running": "运行中", "Running": "运行中",
"Select value is empty": "选择的内容为空", "Select value is empty": "选择的内容为空",
"UnKnow": "未知", "UnKnow": "未知",
@ -163,6 +165,7 @@
}, },
"kb": { "kb": {
"Chunk Length": "数据总量", "Chunk Length": "数据总量",
"Confirm move the folder": "确认移动到该目录",
"Confirm to delete the file": "确认删除该文件及其所有数据?", "Confirm to delete the file": "确认删除该文件及其所有数据?",
"Create Folder": "创建文件夹", "Create Folder": "创建文件夹",
"Delete Dataset Error": "删除知识库异常", "Delete Dataset Error": "删除知识库异常",
@ -171,7 +174,9 @@
"Filename": "文件名", "Filename": "文件名",
"Files": "文件: {{total}}个", "Files": "文件: {{total}}个",
"Folder Name": "输入文件夹名称", "Folder Name": "输入文件夹名称",
"Move Failed": "移动出现错误~",
"My Dataset": "我的知识库", "My Dataset": "我的知识库",
"No Folder": "没有子目录了~",
"Other Data": "其他数据", "Other Data": "其他数据",
"Select Dataset": "选择该知识库", "Select Dataset": "选择该知识库",
"Select Folder": "进入文件夹", "Select Folder": "进入文件夹",

View File

@ -19,10 +19,11 @@ import {
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData'; import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb'; import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb';
import { QuoteItemType } from '@/types/chat'; import { QuoteItemType } from '@/types/chat';
import { KbTypeEnum } from '@/constants/kb';
/* knowledge base */ /* knowledge base */
export const getKbList = (parentId?: string) => export const getKbList = (data: { parentId?: string; type?: `${KbTypeEnum}` }) =>
GET<KbListItemType[]>(`/plugins/kb/list`, { parentId }); GET<KbListItemType[]>(`/plugins/kb/list`, data);
export const getAllDataset = () => GET<KbListItemType[]>(`/plugins/kb/allDataset`); export const getAllDataset = () => GET<KbListItemType[]>(`/plugins/kb/allDataset`);
export const getKbPaths = (parentId?: string) => export const getKbPaths = (parentId?: string) =>

View File

@ -3,6 +3,7 @@ import type { RequestPaging } from '@/types';
export type KbUpdateParams = { export type KbUpdateParams = {
id: string; id: string;
parentId?: string;
tags?: string; tags?: string;
name?: string; name?: string;
avatar?: string; avatar?: string;

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694403033666" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4053" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M1016.32 494.08l-143.872-143.36c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84L936.96 486.4h-399.36V87.04l99.84 100.352c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84l-143.36-143.872c-9.728-9.728-26.112-9.728-35.84 0l-143.36 143.872c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0L486.4 87.04v399.36H87.04l100.352-99.84c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0l-143.872 143.36c-9.728 9.728-9.728 26.112 0 35.84l143.872 143.36c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84L87.04 537.6h399.36v399.36l-99.84-100.352c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84l143.36 143.872c9.728 9.728 26.112 9.728 35.84 0l143.36-143.872c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0L537.6 936.96v-399.36h399.36l-100.352 99.84c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0l143.872-143.36c10.24-9.728 10.24-26.112 0-35.84z" p-id="4054"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24"> <svg viewBox="0 0 24 24">
<path d="M8.3 5.7a1 1 0 011.4-1.4l7.71 7.7-7.7 7.7a1 1 0 11-1.42-1.4l6.3-6.3-6.3-6.3z" fill-rule="nonzero"></path> <path d="M8.3 5.7a1 1 0 011.4-1.4l7.71 7.7-7.7 7.7a1 1 0 11-1.42-1.4l6.3-6.3-6.3-6.3z" fill-rule="nonzero"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 151 B

View File

@ -83,7 +83,8 @@ const map = {
retryLight: require('./icons/light/retry.svg').default, retryLight: require('./icons/light/retry.svg').default,
rightArrowLight: require('./icons/light/rightArrow.svg').default, rightArrowLight: require('./icons/light/rightArrow.svg').default,
searchLight: require('./icons/light/search.svg').default, searchLight: require('./icons/light/search.svg').default,
plusFill: require('./icons/fill/plus.svg').default plusFill: require('./icons/fill/plus.svg').default,
moveLight: require('./icons/light/move.svg').default
}; };
export type IconName = keyof typeof map; export type IconName = keyof typeof map;

View File

@ -8,7 +8,7 @@ interface Props {
menuList: { menuList: {
isActive?: boolean; isActive?: boolean;
child: React.ReactNode; child: React.ReactNode;
onClick: () => void; onClick: () => any;
}[]; }[];
} }
@ -37,7 +37,10 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
<MenuItem <MenuItem
key={i} key={i}
{...menuItemStyles} {...menuItemStyles}
onClick={item.onClick} onClick={(e) => {
e.stopPropagation();
item.onClick && item.onClick();
}}
color={item.isActive ? 'hover.blue' : ''} color={item.isActive ? 'hover.blue' : ''}
> >
{item.child} {item.child}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef } from 'react';
import { ModalFooter, ModalBody, Input, useDisclosure, Button } from '@chakra-ui/react'; import { ModalFooter, ModalBody, Input, useDisclosure, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
@ -39,6 +39,7 @@ export const useEditInfo = ({
try { try {
const val = inputRef.current.value; const val = inputRef.current.value;
await onSuccessCb.current?.(val); await onSuccessCb.current?.(val);
onClose(); onClose();
} catch (err) { } catch (err) {
onErrorCb.current?.(err); onErrorCb.current?.(err);

View File

@ -4,10 +4,11 @@ import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import { getVectorModel } from '@/service/utils/data'; import { getVectorModel } from '@/service/utils/data';
import { KbListItemType } from '@/types/plugin'; import { KbListItemType } from '@/types/plugin';
import { KbTypeEnum } from '@/constants/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
const { parentId } = req.query as { parentId: string }; const { parentId, type } = req.query as { parentId?: string; type?: `${KbTypeEnum}` };
// 凭证校验 // 凭证校验
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req, authToken: true });
@ -15,7 +16,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const kbList = await KB.find({ const kbList = await KB.find({
userId, userId,
parentId: parentId || null ...(parentId !== undefined && { parentId: parentId || null }),
...(type && { type })
}).sort({ updateTime: -1 }); }).sort({ updateTime: -1 });
const data = await Promise.all( const data = await Promise.all(

View File

@ -6,9 +6,9 @@ import type { KbUpdateParams } from '@/api/request/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
const { id, name, avatar, tags = '' } = req.body as KbUpdateParams; const { id, parentId, name, avatar, tags } = req.body as KbUpdateParams;
if (!id || !name) { if (!id) {
throw new Error('缺少参数'); throw new Error('缺少参数');
} }
@ -23,9 +23,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
userId userId
}, },
{ {
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name }), ...(name && { name }),
...(avatar && { avatar }), ...(avatar && { avatar }),
tags: tags.split(' ').filter((item) => item) ...(typeof tags === 'string' && {
tags: tags.split(' ').filter((item) => item)
})
} }
); );

View File

@ -83,7 +83,13 @@ export const KBSelectModal = ({
<Flex flexDirection={'column'} h={['90vh', 'auto']}> <Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader> <ModalHeader>
{!!parentId ? ( {!!parentId ? (
<Flex flex={1}> <Flex
flex={1}
userSelect={'none'}
fontSize={['sm', 'lg']}
fontWeight={'normal'}
color={'myGray.900'}
>
{paths.map((item, i) => ( {paths.map((item, i) => (
<Flex key={item.parentId} mr={2} alignItems={'center'}> <Flex key={item.parentId} mr={2} alignItems={'center'}>
<Box <Box
@ -106,7 +112,7 @@ export const KBSelectModal = ({
{item.parentName} {item.parentName}
</Box> </Box>
{i !== paths.length - 1 && ( {i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} /> <MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)} )}
</Flex> </Flex>
))} ))}

View File

@ -1,17 +1,16 @@
import { Box, Flex, Button, Image } from '@chakra-ui/react'; import { Box, Flex, Button, Image } from '@chakra-ui/react';
import React from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { feConfigs } from '@/store/static'; import { feConfigs } from '@/store/static';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast';
const Hero = () => { const Hero = () => {
const router = useRouter(); const router = useRouter();
const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { isPc, gitStar } = useGlobalStore(); const { isPc, gitStar } = useGlobalStore();
const [showVideo, setShowVide] = useState(false);
return ( return (
<Flex flexDirection={'column'} pt={['24px', '50px']} alignItems={'center'} userSelect={'none'}> <Flex flexDirection={'column'} pt={['24px', '50px']} alignItems={'center'} userSelect={'none'}>
@ -62,6 +61,7 @@ const Hero = () => {
mx={['-10%', 'auto']} mx={['-10%', 'auto']}
maxW={['120%', '1000px']} maxW={['120%', '1000px']}
alt="" alt=""
draggable={false}
/> />
<MyIcon <MyIcon
name={'playFill'} name={'playFill'}
@ -72,13 +72,42 @@ const Hero = () => {
top={'50%'} top={'50%'}
color={'#363c42b8'} color={'#363c42b8'}
transform={['translate(-50%,5px)', 'translate(-50%,40px)']} transform={['translate(-50%,5px)', 'translate(-50%,40px)']}
onClick={() => { onClick={() => setShowVide(true)}
toast({
title: '录制中~'
});
}}
/> />
</Box> </Box>
{showVideo && (
<Flex
position={'fixed'}
zIndex={99}
top={0}
left={0}
right={0}
bottom={0}
alignItems={'center'}
justifyContent={'center'}
bg={'rgba(255,255,255,0.4)'}
onClick={() => setShowVide(false)}
>
<Box
w={['100vw', '50%']}
borderRadius={'lg'}
overflow={'hidden'}
onClick={(e) => e.preventDefault()}
>
<video
style={{
margin: 'auto'
}}
onClick={(e) => {
e.stopPropagation();
}}
src={'https://otnvvf-imgs.oss.laf.run/fastgpt.mp4'}
controls
autoPlay
/>
</Box>
</Flex>
)}
</Flex> </Flex>
); );
}; };

View File

@ -145,7 +145,7 @@ const FileCard = ({ kbId }: { kbId: string }) => {
cursor={'pointer'} cursor={'pointer'}
title={'点击查看数据详情'} title={'点击查看数据详情'}
onClick={() => onClick={() =>
router.push({ router.replace({
query: { query: {
kbId, kbId,
fileId: file.id, fileId: file.id,

View File

@ -0,0 +1,181 @@
import React, { useMemo, useState } from 'react';
import {
Card,
Flex,
Box,
Button,
ModalBody,
ModalHeader,
ModalFooter,
useTheme,
Grid
} from '@chakra-ui/react';
import { getKbPaths } from '@/api/plugins/kb';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon';
import { KbTypeEnum } from '@/constants/kb';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { getKbList, putKbById } from '@/api/plugins/kb';
import { useRequest } from '@/hooks/useRequest';
const MoveModal = ({
onClose,
onSuccess,
moveDataId
}: {
onClose: () => void;
onSuccess: () => void;
moveDataId: string;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const [parentId, setParentId] = useState<string>('');
const { data } = useQuery(['getKbList', parentId], () => {
return Promise.all([getKbList({ parentId, type: 'folder' }), getKbPaths(parentId)]);
});
const paths = useMemo(
() => [
{
parentId: '',
parentName: t('kb.My Dataset')
},
...(data?.[1] || [])
],
[data, t]
);
const folderList = useMemo(
() => (data?.[0] || []).filter((item) => item._id !== moveDataId),
[moveDataId, data]
);
const { mutate, isLoading } = useRequest({
mutationFn: () => putKbById({ id: moveDataId, parentId }),
onSuccess,
errorToast: t('kb.Move Failed')
});
return (
<MyModal isOpen={true} maxW={['90vw', '800px']} w={'800px'} onClose={onClose}>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
{!!parentId ? (
<Flex flex={1} userSelect={'none'} fontSize={['sm', 'lg']} fontWeight={'normal'}>
{paths.map((item, i) => (
<Flex key={item.parentId} mr={2} alignItems={'center'}>
<Box
borderRadius={'md'}
{...(i === paths.length - 1
? {
cursor: 'default'
}
: {
cursor: 'pointer',
_hover: {
color: 'myBlue.600'
},
onClick: () => {
setParentId(item.parentId);
}
})}
>
{item.parentName}
</Box>
{i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)}
</Flex>
))}
</Flex>
) : (
<Box></Box>
)}
</ModalHeader>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
userSelect={'none'}
>
<Grid
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
>
{folderList.map((item) =>
(() => {
return (
<MyTooltip
key={item._id}
label={
item.type === KbTypeEnum.dataset
? t('kb.Select Dataset')
: t('kb.Select Folder')
}
>
<Card
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
_hover={{
boxShadow: 'md'
}}
onClick={() => {
setParentId(item._id);
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px']}></Avatar>
<Box
className="textEllipsis"
ml={3}
fontWeight={'bold'}
fontSize={['md', 'lg', 'xl']}
>
{item.name}
</Box>
</Flex>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
{item.type === KbTypeEnum.folder ? (
<Box color={'myGray.500'}>{t('Folder')}</Box>
) : (
<>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{item.vectorModel.name}</Box>
</>
)}
</Flex>
</Card>
</MyTooltip>
);
})()
)}
</Grid>
{folderList.length === 0 && (
<Flex mt={5} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{t('kb.No Folder')}
</Box>
</Flex>
)}
</ModalBody>
<ModalFooter>
<Button isLoading={isLoading} onClick={mutate}>
{t('kb.Confirm move the folder')}
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default MoveModal;

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { import {
Box, Box,
Flex, Flex,
@ -6,7 +6,6 @@ import {
useTheme, useTheme,
useDisclosure, useDisclosure,
Card, Card,
IconButton,
MenuButton, MenuButton,
Image Image
} from '@chakra-ui/react'; } from '@chakra-ui/react';
@ -16,8 +15,7 @@ import PageContainer from '@/components/PageContainer';
import { useConfirm } from '@/hooks/useConfirm'; import { useConfirm } from '@/hooks/useConfirm';
import { AddIcon } from '@chakra-ui/icons'; import { AddIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useToast } from '@/hooks/useToast'; import { delKbById, getKbPaths, putKbById } from '@/api/plugins/kb';
import { delKbById, getKbPaths } from '@/api/plugins/kb';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
@ -28,16 +26,17 @@ import Tag from '@/components/Tag';
import MyMenu from '@/components/MyMenu'; import MyMenu from '@/components/MyMenu';
import { useRequest } from '@/hooks/useRequest'; import { useRequest } from '@/hooks/useRequest';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import { useEditInfo } from '@/hooks/useEditInfo';
const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false }); const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false });
const EditFolderModal = dynamic(() => import('./component/EditFolderModal'), { ssr: false }); const EditFolderModal = dynamic(() => import('./component/EditFolderModal'), { ssr: false });
const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false });
const Kb = () => { const Kb = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { parentId } = router.query as { parentId: string }; const { parentId } = router.query as { parentId: string };
const { toast } = useToast();
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const DeleteTipsMap = useRef({ const DeleteTipsMap = useRef({
@ -49,7 +48,13 @@ const Kb = () => {
title: t('common.Delete Warning'), title: t('common.Delete Warning'),
content: '' content: ''
}); });
const { myKbList, loadKbList, setKbList } = useDatasetStore(); const { myKbList, loadKbList, setKbList, updateDataset } = useDatasetStore();
const { onOpenModal: onOpenTitleModal, EditModal: EditTitleModal } = useEditInfo({
title: t('Rename')
});
const [moveDataId, setMoveDataId] = useState<string>();
const [dragStartId, setDragStartId] = useState<string>();
const [dragTargetId, setDragTargetId] = useState<string>();
const { const {
isOpen: isOpenCreateModal, isOpen: isOpenCreateModal,
@ -102,8 +107,8 @@ const Kb = () => {
{paths.map((item, i) => ( {paths.map((item, i) => (
<Flex key={item.parentId} mr={2} alignItems={'center'}> <Flex key={item.parentId} mr={2} alignItems={'center'}>
<Box <Box
fontSize={'lg'} fontSize={['sm', 'lg']}
px={2} px={[0, 2]}
py={1} py={1}
borderRadius={'md'} borderRadius={'md'}
{...(i === paths.length - 1 {...(i === paths.length - 1
@ -126,7 +131,9 @@ const Kb = () => {
> >
{item.parentName} {item.parentName}
</Box> </Box>
{i !== paths.length - 1 && <MyIcon name={'rightArrowLight'} color={'myGray.500'} />} {i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)}
</Flex> </Flex>
))} ))}
</Flex> </Flex>
@ -184,6 +191,7 @@ const Kb = () => {
p={5} p={5}
gridTemplateColumns={['1fr', 'repeat(3,1fr)', 'repeat(4,1fr)', 'repeat(5,1fr)']} gridTemplateColumns={['1fr', 'repeat(3,1fr)', 'repeat(4,1fr)', 'repeat(5,1fr)']}
gridGap={5} gridGap={5}
userSelect={'none'}
> >
{myKbList.map((kb) => ( {myKbList.map((kb) => (
<Card <Card
@ -196,8 +204,36 @@ const Kb = () => {
h={'130px'} h={'130px'}
border={theme.borders.md} border={theme.borders.md}
boxShadow={'none'} boxShadow={'none'}
userSelect={'none'}
position={'relative'} position={'relative'}
data-drag-id={kb.type === KbTypeEnum.folder ? kb._id : undefined}
borderColor={dragTargetId === kb._id ? 'myBlue.600' : ''}
draggable
onDragStart={(e) => {
setDragStartId(kb._id);
}}
onDragOver={(e) => {
e.preventDefault();
const targetId = e.currentTarget.getAttribute('data-drag-id');
if (!targetId) return;
KbTypeEnum.folder && setDragTargetId(targetId);
}}
onDragLeave={(e) => {
e.preventDefault();
setDragTargetId(undefined);
}}
onDrop={async (e) => {
e.preventDefault();
if (!dragTargetId || !dragStartId || dragTargetId === dragStartId) return;
// update parentId
try {
await putKbById({
id: dragStartId,
parentId: dragTargetId
});
refetch();
} catch (error) {}
setDragTargetId(undefined);
}}
_hover={{ _hover={{
boxShadow: '1px 1px 10px rgba(0,0,0,0.2)', boxShadow: '1px 1px 10px rgba(0,0,0,0.2)',
borderColor: 'transparent', borderColor: 'transparent',
@ -223,33 +259,85 @@ const Kb = () => {
} }
}} }}
> >
<MyMenu
offset={[-30, 10]}
width={120}
Button={
<MenuButton
position={'absolute'}
top={3}
right={3}
w={'22px'}
h={'22px'}
borderRadius={'md'}
_hover={{
color: 'myBlue.600',
'& .icon': {
bg: 'myGray.100'
}
}}
onClick={(e) => {
e.stopPropagation();
}}
>
<MyIcon
className="icon"
name={'more'}
h={'16px'}
w={'16px'}
px={1}
py={1}
borderRadius={'md'}
cursor={'pointer'}
/>
</MenuButton>
}
menuList={[
{
child: (
<Flex alignItems={'center'}>
<MyIcon name={'edit'} w={'14px'} mr={2} />
{t('Rename')}
</Flex>
),
onClick: () =>
onOpenTitleModal({
defaultVal: kb.name,
onSuccess: (val) => {
if (val === kb.name || !val) return;
updateDataset({ id: kb._id, name: val });
}
})
},
{
child: (
<Flex alignItems={'center'}>
<MyIcon name={'moveLight'} w={'14px'} mr={2} />
{t('Move')}
</Flex>
),
onClick: () => setMoveDataId(kb._id)
},
{
child: (
<Flex alignItems={'center'}>
<MyIcon name={'delete'} w={'14px'} mr={2} />
{t('common.Delete')}
</Flex>
),
onClick: () => {
openConfirm(
() => onclickDelKb(kb._id),
undefined,
DeleteTipsMap.current[kb.type]
)();
}
}
]}
/>
<Flex alignItems={'center'} h={'38px'}> <Flex alignItems={'center'} h={'38px'}>
<Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} /> <Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} />
<Box ml={3}>{kb.name}</Box> <Box ml={3}>{kb.name}</Box>
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
display={['', 'none']}
_hover={{
bg: 'red.100'
}}
onClick={(e) => {
e.stopPropagation();
openConfirm(
() => onclickDelKb(kb._id),
undefined,
DeleteTipsMap.current[kb.type]
)();
}}
/>
</Flex> </Flex>
<Box flex={'1 0 0'} overflow={'hidden'} pt={2}> <Box flex={'1 0 0'} overflow={'hidden'} pt={2}>
<Flex> <Flex>
@ -282,6 +370,7 @@ const Kb = () => {
</Flex> </Flex>
)} )}
<ConfirmModal /> <ConfirmModal />
<EditTitleModal />
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} parentId={parentId} />} {isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} parentId={parentId} />}
{!!editFolderData && ( {!!editFolderData && (
<EditFolderModal <EditFolderModal
@ -291,6 +380,16 @@ const Kb = () => {
{...editFolderData} {...editFolderData}
/> />
)} )}
{!!moveDataId && (
<MoveModal
moveDataId={moveDataId}
onClose={() => setMoveDataId('')}
onSuccess={() => {
refetch();
setMoveDataId('');
}}
/>
)}
</PageContainer> </PageContainer>
); );
}; };

View File

@ -3,8 +3,9 @@ import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { type KbTestItemType } from '@/types/plugin'; import { type KbTestItemType } from '@/types/plugin';
import type { KbItemType, KbListItemType } from '@/types/plugin'; import type { KbItemType, KbListItemType } from '@/types/plugin';
import { getKbList, getKbById, getAllDataset } from '@/api/plugins/kb'; import { getKbList, getKbById, getAllDataset, putKbById } from '@/api/plugins/kb';
import { defaultKbDetail } from '@/constants/kb'; import { defaultKbDetail } from '@/constants/kb';
import { KbUpdateParams } from '@/api/request/kb';
type State = { type State = {
datasets: KbListItemType[]; datasets: KbListItemType[];
@ -14,6 +15,7 @@ type State = {
setKbList(val: KbListItemType[]): void; setKbList(val: KbListItemType[]): void;
kbDetail: KbItemType; kbDetail: KbItemType;
getKbDetail: (id: string, init?: boolean) => Promise<KbItemType>; getKbDetail: (id: string, init?: boolean) => Promise<KbItemType>;
updateDataset: (data: KbUpdateParams) => Promise<any>;
kbTestList: KbTestItemType[]; kbTestList: KbTestItemType[];
pushKbTestItem: (data: KbTestItemType) => void; pushKbTestItem: (data: KbTestItemType) => void;
@ -34,8 +36,8 @@ export const useDatasetStore = create<State>()(
return res; return res;
}, },
myKbList: [], myKbList: [],
async loadKbList(parentId) { async loadKbList(parentId = '') {
const res = await getKbList(parentId); const res = await getKbList({ parentId });
set((state) => { set((state) => {
state.myKbList = res; state.myKbList = res;
}); });
@ -58,6 +60,28 @@ export const useDatasetStore = create<State>()(
return data; return data;
}, },
async updateDataset(data) {
if (get().kbDetail._id === data.id) {
set((state) => {
state.kbDetail = {
...state.kbDetail,
...data
};
});
}
set((state) => {
state.myKbList = state.myKbList = state.myKbList.map((item) =>
item._id === data.id
? {
...item,
...data,
tags: data.tags?.split(' ') || []
}
: item
);
});
await putKbById(data);
},
kbTestList: [], kbTestList: [],
pushKbTestItem(data) { pushKbTestItem(data) {
set((state) => { set((state) => {