feat: move dataset (#277)
This commit is contained in:
parent
ae2887e956
commit
b46048609c
@ -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",
|
||||||
|
|||||||
@ -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": "进入文件夹",
|
||||||
|
|||||||
@ -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) =>
|
||||||
|
|||||||
1
client/src/api/request/kb.d.ts
vendored
1
client/src/api/request/kb.d.ts
vendored
@ -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;
|
||||||
|
|||||||
1
client/src/components/Icon/icons/light/move.svg
Normal file
1
client/src/components/Icon/icons/light/move.svg
Normal 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 |
@ -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 |
@ -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;
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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 }),
|
||||||
|
...(typeof tags === 'string' && {
|
||||||
tags: tags.split(' ').filter((item) => item)
|
tags: tags.split(' ').filter((item) => item)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
181
client/src/pages/kb/list/component/MoveModal.tsx
Normal file
181
client/src/pages/kb/list/component/MoveModal.tsx
Normal 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;
|
||||||
@ -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 = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex alignItems={'center'} h={'38px'}>
|
<MyMenu
|
||||||
<Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} />
|
offset={[-30, 10]}
|
||||||
<Box ml={3}>{kb.name}</Box>
|
width={120}
|
||||||
|
Button={
|
||||||
<IconButton
|
<MenuButton
|
||||||
className="delete"
|
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
top={4}
|
top={3}
|
||||||
right={4}
|
right={3}
|
||||||
size={'sm'}
|
w={'22px'}
|
||||||
icon={<MyIcon name={'delete'} w={'14px'} />}
|
h={'22px'}
|
||||||
variant={'base'}
|
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
aria-label={'delete'}
|
|
||||||
display={['', 'none']}
|
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: 'red.100'
|
color: 'myBlue.600',
|
||||||
|
'& .icon': {
|
||||||
|
bg: 'myGray.100'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
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(
|
openConfirm(
|
||||||
() => onclickDelKb(kb._id),
|
() => onclickDelKb(kb._id),
|
||||||
undefined,
|
undefined,
|
||||||
DeleteTipsMap.current[kb.type]
|
DeleteTipsMap.current[kb.type]
|
||||||
)();
|
)();
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
|
<Flex alignItems={'center'} h={'38px'}>
|
||||||
|
<Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} />
|
||||||
|
<Box ml={3}>{kb.name}</Box>
|
||||||
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user