From 6ac7119edf0bcc471c7240bba6858e1be2232b2d Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Mon, 12 Jun 2023 15:11:29 +0800 Subject: [PATCH] feat: kb UI --- README.md | 2 +- client/src/components/Tag/index.tsx | 6 +- client/src/constants/theme.ts | 4 +- client/src/hooks/usePagination.tsx | 4 +- .../pages/api/plugins/kb/data/getDataList.ts | 1 + client/src/pages/kb/components/DataCard.tsx | 262 ++++++++---------- client/src/pages/kb/components/Detail.tsx | 39 ++- client/src/pages/kb/components/Info.tsx | 147 +++++----- client/src/pages/kb/components/KbList.tsx | 4 +- client/src/pages/number/index.tsx | 3 +- 10 files changed, 237 insertions(+), 235 deletions(-) diff --git a/README.md b/README.md index aaf431fad..e2a48fdd7 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接 ## 👀 其他 - [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh) +- [docker 部署教程](https://www.bilibili.com/video/BV1jo4y147fT/) - [公众号接入](https://www.bilibili.com/video/BV1xh4y1t7fy/) -- [FastGpt + Laf 最佳实践,将知识库装入公众号,点击去 Laf 公众号体验效果](https://b4jky7-fastgpt.oss.laf.run/lafercode.png) - [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4) - [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/) diff --git a/client/src/components/Tag/index.tsx b/client/src/components/Tag/index.tsx index 32733eaec..572dd47c0 100644 --- a/client/src/components/Tag/index.tsx +++ b/client/src/components/Tag/index.tsx @@ -29,11 +29,11 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => { }, [colorSchema]); return ( ({ size={'sm'} w={'28px'} h={'28px'} + isLoading={isLoading} onClick={() => mutate(pageNum - 1)} /> @@ -84,13 +85,14 @@ export const usePagination = ({ icon={} aria-label={'left'} size={'sm'} + isLoading={isLoading} w={'28px'} h={'28px'} onClick={() => mutate(pageNum + 1)} /> ); - }, [maxPage, mutate, pageNum]); + }, [isLoading, maxPage, mutate, pageNum]); useEffect(() => { defaultRequest && mutate(1); diff --git a/client/src/pages/api/plugins/kb/data/getDataList.ts b/client/src/pages/api/plugins/kb/data/getDataList.ts index 921c0b598..acbe902a7 100644 --- a/client/src/pages/api/plugins/kb/data/getDataList.ts +++ b/client/src/pages/api/plugins/kb/data/getDataList.ts @@ -26,6 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { userId } = await authUser({ req, authToken: true }); await connectToDatabase(); + searchText = searchText.replace(/'/g, ''); const where: any = [ ['user_id', userId], diff --git a/client/src/pages/kb/components/DataCard.tsx b/client/src/pages/kb/components/DataCard.tsx index 19eab177e..ebd645d83 100644 --- a/client/src/pages/kb/components/DataCard.tsx +++ b/client/src/pages/kb/components/DataCard.tsx @@ -1,13 +1,7 @@ -import React, { useCallback, useState, useRef, useEffect } from 'react'; +import React, { useCallback, useState, useRef } from 'react'; import { Box, - TableContainer, - Table, - Thead, - Tbody, - Tr, - Th, - Td, + Card, IconButton, Flex, Button, @@ -17,10 +11,8 @@ import { MenuList, MenuItem, Input, - Tooltip + Grid } from '@chakra-ui/react'; -import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import type { BoxProps } from '@chakra-ui/react'; import type { KbDataItemType } from '@/types/plugin'; import { usePagination } from '@/hooks/usePagination'; import { @@ -29,30 +21,21 @@ import { delOneKbDataByDataId, getTrainingData } from '@/api/plugins/kb'; -import { DeleteIcon, RepeatIcon, EditIcon } from '@chakra-ui/icons'; -import { useLoading } from '@/hooks/useLoading'; +import { DeleteIcon } from '@chakra-ui/icons'; import { fileDownload } from '@/utils/file'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useToast } from '@/hooks/useToast'; import Papa from 'papaparse'; import dynamic from 'next/dynamic'; import InputModal, { FormData as InputDataType } from './InputDataModal'; +import { debounce } from 'lodash'; const SelectFileModal = dynamic(() => import('./SelectFileModal')); const SelectCsvModal = dynamic(() => import('./SelectCsvModal')); const DataCard = ({ kbId }: { kbId: string }) => { const lastSearch = useRef(''); - const tdStyles = useRef({ - fontSize: 'xs', - minW: '150px', - maxW: '500px', - maxH: '250px', - whiteSpace: 'pre-wrap', - overflowY: 'auto' - }); const [searchText, setSearchText] = useState(''); - const { Loading, setIsLoading } = useLoading(); const { toast } = useToast(); const { @@ -64,7 +47,7 @@ const DataCard = ({ kbId }: { kbId: string }) => { pageNum } = usePagination({ api: getKbDataList, - pageSize: 10, + pageSize: 24, params: { kbId, searchText @@ -109,7 +92,6 @@ const DataCard = ({ kbId }: { kbId: string }) => { mutationFn: () => getExportDataList(kbId), onSuccess(res) { try { - setIsLoading(true); const text = Papa.unparse({ fields: ['question', 'answer'], data: res @@ -126,7 +108,6 @@ const DataCard = ({ kbId }: { kbId: string }) => { } catch (error) { error; } - setIsLoading(false); }, onError(err: any) { toast({ @@ -137,6 +118,14 @@ const DataCard = ({ kbId }: { kbId: string }) => { } }); + const getFirstData = useCallback( + debounce(() => { + getData(1); + lastSearch.current = searchText; + }, 300), + [] + ); + // interval get data useQuery(['refetchData'], () => refetchData(1), { refetchInterval: 5000, @@ -150,148 +139,137 @@ const DataCard = ({ kbId }: { kbId: string }) => { return ( - - + + 知识库数据: {total}组 - } - aria-label={'refresh'} - variant={'base'} - mr={[2, 4]} - size={'sm'} - onClick={() => { - refetchData(pageNum); - getTrainingData({ kbId, init: true }); - }} - /> - - - - 导入 - - - - setEditInputData({ - a: '', - q: '' - }) - } - > - 手动输入 - - 文本/文件拆分 - csv 问答对导入 - - + + + + + 导入 + + + + setEditInputData({ + a: '', + q: '' + }) + } + > + 手动输入 + + 文本/文件拆分 + csv 问答对导入 + + + - - {(qaListLen > 0 || vectorListLen > 0) && ( + + {qaListLen > 0 || vectorListLen > 0 ? ( {qaListLen > 0 ? `${qaListLen}条数据正在拆分,` : ''} {vectorListLen > 0 ? `${vectorListLen}条数据正在生成索引,` : ''} 请耐心等待... + ) : ( + 所有数据已就绪~ )} - + setSearchText(e.target.value)} + placeholder="根据匹配知识,补充知识和来源搜索" + onChange={(e) => { + setSearchText(e.target.value); + getFirstData(); + }} onBlur={() => { if (searchText === lastSearch.current) return; - getData(1); - lastSearch.current = searchText; + getFirstData(); }} onKeyDown={(e) => { if (searchText === lastSearch.current) return; if (e.key === 'Enter') { - getData(1); - lastSearch.current = searchText; + getFirstData(); } }} /> - - - - - - - - - - - - {kbDataList.map((item) => ( - - - - - - - ))} - -
- 匹配的知识点 - - - - 补充知识来源操作
- {item.q} - - {item.a || '-'} - - {item.source?.trim() || '-'} - - } - variant={'base'} - aria-label={'delete'} - size={'sm'} - onClick={() => - setEditInputData({ - dataId: item.id, - q: item.q, - a: item.a - }) - } - /> - } - variant={'base'} - colorScheme={'gray'} - aria-label={'delete'} - size={'sm'} - onClick={async () => { - await delOneKbDataByDataId(item.id); - refetchData(pageNum); - }} - /> -
-
- + + {kbDataList.map((item) => ( + + setEditInputData({ + dataId: item.id, + q: item.q, + a: item.a + }) + } + > + + + {item.source?.trim()} + + } + variant={'base'} + colorScheme={'gray'} + aria-label={'delete'} + size={'xs'} + borderRadius={'md'} + lineHeight={1} + _hover={{ color: 'red.600' }} + onClick={async (e) => { + e.stopPropagation(); + await delOneKbDataByDataId(item.id); + refetchData(pageNum); + }} + /> + + + {item.q} + {item.a} + + + ))} + + + - {editInputData !== undefined && ( { const { toast } = useToast(); const router = useRouter(); + const { isPc } = useScreen(); const BasicInfo = useRef(null); const { setLastKbId, kbDetail, getKbDetail, loadKbList, myKbList } = useUserStore(); + const [currentTab, setCurrentTab] = useState(TabEnum.data); const form = useForm({ defaultValues: kbDetail @@ -42,13 +51,23 @@ const Detail = ({ kbId }: { kbId: string }) => { }); return ( - - - - - - - + + + setCurrentTab(e)} + /> + + {currentTab === TabEnum.data && } + {currentTab === TabEnum.info && } ); }; diff --git a/client/src/pages/kb/components/Info.tsx b/client/src/pages/kb/components/Info.tsx index 37df77ce7..4ba96203a 100644 --- a/client/src/pages/kb/components/Info.tsx +++ b/client/src/pages/kb/components/Info.tsx @@ -140,88 +140,89 @@ const Info = ( })); return ( - - - - 知识库信息 + + + + 知识库头像 + + + - {kbDetail._id && ( - <> - - } - aria-label={''} - variant={'solid'} - colorScheme={'red'} - onClick={openConfirm(onclickDelKb)} - /> - - )} - - - 头像 + + + 知识库名称 - - - - - - 名称 - - - - - - - 标签 - - - - - { - setValue('tags', e.target.value); - setRefresh(!refresh); - }} + + + 分类标签 + + + + + { + setValue('tags', e.target.value); + setRefresh(!refresh); + }} + /> + + {getValues('tags') + .split(' ') + .filter((item) => item) + .map((item, i) => ( + + {item} + + ))} + + + {kbDetail._id && ( + + + + } + aria-label={''} + variant={'outline'} + size={'sm'} + colorScheme={'red'} + color={'red.500'} + onClick={openConfirm(onclickDelKb)} /> - - {getValues('tags') - .split(' ') - .filter((item) => item) - .map((item, i) => ( - - {item} - - ))} - - + )} - + ); }; diff --git a/client/src/pages/kb/components/KbList.tsx b/client/src/pages/kb/components/KbList.tsx index 399db0962..5f3e64b04 100644 --- a/client/src/pages/kb/components/KbList.tsx +++ b/client/src/pages/kb/components/KbList.tsx @@ -119,12 +119,12 @@ const KbList = ({ kbId }: { kbId: string }) => { {item.name} {/* tags */} - + {!item.tags ? ( <>{item.tags || '你还没设置标签~'} ) : ( item.tags.split(' ').map((item, i) => ( - + {item} )) diff --git a/client/src/pages/number/index.tsx b/client/src/pages/number/index.tsx index 0f9874eb0..8801dace7 100644 --- a/client/src/pages/number/index.tsx +++ b/client/src/pages/number/index.tsx @@ -245,13 +245,14 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => { setCurrentTab(id)} /> - + {(() => { const item = tableList.current.find((item) => item.id === currentTab);