perf: kb test

This commit is contained in:
archer
2023-06-12 21:59:30 +08:00
parent ca4cd8af9d
commit 99e47849f5
11 changed files with 213 additions and 66 deletions

View File

@@ -38,6 +38,7 @@ export const updateHistoryQuote = (params: {
chatId: string; chatId: string;
historyId: string; historyId: string;
quoteId: string; quoteId: string;
sourceText: string;
}) => GET(`/chat/history/updateHistoryQuote`, params); }) => GET(`/chat/history/updateHistoryQuote`, params);
/** /**

View File

@@ -2,6 +2,7 @@ import { GET, POST, PUT, DELETE } from '../request';
import type { KbItemType } from '@/types/plugin'; import type { KbItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index'; import { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin'; import { TrainingModeEnum } from '@/constants/plugin';
import { type QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
import { import {
Props as PushDataProps, Props as PushDataProps,
Response as PushDateResponse Response as PushDateResponse
@@ -59,7 +60,7 @@ export const getTrainingData = (data: { kbId: string; init: boolean }) =>
}>(`/plugins/kb/data/getTrainingData`, data); }>(`/plugins/kb/data/getTrainingData`, data);
export const getKbDataItemById = (dataId: string) => export const getKbDataItemById = (dataId: string) =>
GET(`/plugins/kb/data/getDataById`, { dataId }); GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
/** /**
* 直接push数据 * 直接push数据

View File

@@ -6,10 +6,16 @@ import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
let { chatId, historyId, quoteId } = req.query as { let {
chatId,
historyId,
quoteId,
sourceText = ''
} = req.query as {
chatId: string; chatId: string;
historyId: string; historyId: string;
quoteId: string; quoteId: string;
sourceText: string;
}; };
await connectToDatabase(); await connectToDatabase();
@@ -27,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}, },
{ {
$set: { $set: {
'content.$.quote.$[quoteElem].source': '手动修改' 'content.$.quote.$[quoteElem].source': sourceText
} }
}, },
{ {

View File

@@ -76,13 +76,14 @@ const QuoteModal = ({
* update kbData, update mongo status and reload quotes * update kbData, update mongo status and reload quotes
*/ */
const updateQuoteStatus = useCallback( const updateQuoteStatus = useCallback(
async (quoteId: string) => { async (quoteId: string, sourceText: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
await updateHistoryQuote({ await updateHistoryQuote({
chatId, chatId,
historyId, historyId,
quoteId quoteId,
sourceText
}); });
// reload quote // reload quote
refetch(); refetch();
@@ -163,7 +164,8 @@ const QuoteModal = ({
{editDataItem && ( {editDataItem && (
<InputDataModal <InputDataModal
onClose={() => setEditDataItem(undefined)} onClose={() => setEditDataItem(undefined)}
onSuccess={() => updateQuoteStatus(editDataItem.dataId)} onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')}
onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')}
kbId="" kbId=""
defaultValues={editDataItem} defaultValues={editDataItem}
/> />

View File

@@ -208,7 +208,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
/> />
</Flex> </Flex>
<Grid <Grid
minH={'200px'} minH={'100px'}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)']} gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={4} gridGap={4}
> >
@@ -216,7 +216,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
<Card <Card
key={item.id} key={item.id}
cursor={'pointer'} cursor={'pointer'}
pb={3} pt={3}
userSelect={'none'} userSelect={'none'}
boxShadow={'none'} boxShadow={'none'}
_hover={{ boxShadow: 'lg', '& .delete': { display: 'block' } }} _hover={{ boxShadow: 'lg', '& .delete': { display: 'block' } }}
@@ -230,7 +230,20 @@ const DataCard = ({ kbId }: { kbId: string }) => {
}) })
} }
> >
<Flex py={3} px={4} h={'40px'}> <Box
h={'100px'}
overflow={'hidden'}
wordBreak={'break-all'}
px={3}
py={1}
fontSize={'13px'}
>
<Box color={'myGray.1000'} mb={2}>
{item.q}
</Box>
<Box color={'myGray.600'}>{item.a}</Box>
</Box>
<Flex py={2} px={4} h={'36px'} alignItems={'flex-end'} fontSize={'sm'}>
<Box className={'textEllipsis'} flex={1}> <Box className={'textEllipsis'} flex={1}>
{item.source?.trim()} {item.source?.trim()}
</Box> </Box>
@@ -252,12 +265,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
}} }}
/> />
</Flex> </Flex>
<Box h={'100px'} overflow={'hidden'} wordBreak={'break-all'} p={3} fontSize={'sm'}>
<Box color={'myGray.1000'} mb={2}>
{item.q}
</Box>
<Box color={'myGray.600'}>{item.a}</Box>
</Box>
</Card> </Card>
))} ))}
</Grid> </Grid>
@@ -271,7 +278,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
kbId={kbId} kbId={kbId}
defaultValues={editInputData} defaultValues={editInputData}
onClose={() => setEditInputData(undefined)} onClose={() => setEditInputData(undefined)}
onSuccess={refetchData} onSuccess={() => refetchData()}
/> />
)} )}
{isOpenSelectFileModal && ( {isOpenSelectFileModal && (

View File

@@ -8,7 +8,7 @@ import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin'; import { KbItemType } from '@/types/plugin';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { type ComponentRef } from './Info'; import Info, { type ComponentRef } from './Info';
import Tabs from '@/components/Tabs'; import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import DataCard from './DataCard'; import DataCard from './DataCard';
@@ -16,9 +16,6 @@ import DataCard from './DataCard';
const Test = dynamic(() => import('./Test'), { const Test = dynamic(() => import('./Test'), {
ssr: false ssr: false
}); });
const Info = dynamic(() => import('./Info'), {
ssr: false
});
enum TabEnum { enum TabEnum {
data = 'data', data = 'data',

View File

@@ -214,8 +214,10 @@ const Info = (
aria-label={''} aria-label={''}
variant={'outline'} variant={'outline'}
size={'sm'} size={'sm'}
colorScheme={'red'} _hover={{
color={'red.500'} color: 'red.600',
borderColor: 'red.600'
}}
onClick={openConfirm(onclickDelKb)} onClick={openConfirm(onclickDelKb)}
/> />
</Flex> </Flex>

View File

@@ -8,19 +8,22 @@ import {
ModalContent, ModalContent,
ModalHeader, ModalHeader,
ModalCloseButton, ModalCloseButton,
Textarea Textarea,
IconButton
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { postKbDataFromList, putKbDataById } from '@/api/plugins/kb'; import { postKbDataFromList, putKbDataById, delOneKbDataByDataId } from '@/api/plugins/kb';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { TrainingModeEnum } from '@/constants/plugin'; import { TrainingModeEnum } from '@/constants/plugin';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import MyIcon from '@/components/Icon';
export type FormData = { dataId?: string; a: string; q: string }; export type FormData = { dataId?: string; a: string; q: string };
const InputDataModal = ({ const InputDataModal = ({
onClose, onClose,
onSuccess, onSuccess,
onDelete,
kbId, kbId,
defaultValues = { defaultValues = {
a: '', a: '',
@@ -28,7 +31,8 @@ const InputDataModal = ({
} }
}: { }: {
onClose: () => void; onClose: () => void;
onSuccess: () => void; onSuccess: (data: FormData) => void;
onDelete?: () => void;
kbId: string; kbId: string;
defaultValues?: FormData; defaultValues?: FormData;
}) => { }) => {
@@ -54,16 +58,15 @@ const InputDataModal = ({
setLoading(true); setLoading(true);
try { try {
const { insertLen } = await postKbDataFromList({ const data = {
kbId,
mode: TrainingModeEnum.index,
data: [
{
a: e.a, a: e.a,
q: e.q, q: e.q,
source: '手动录入' source: '手动录入'
} };
] const { insertLen } = await postKbDataFromList({
kbId,
mode: TrainingModeEnum.index,
data: [data]
}); });
if (insertLen === 0) { if (insertLen === 0) {
@@ -82,7 +85,7 @@ const InputDataModal = ({
}); });
} }
onSuccess(); onSuccess(data);
} catch (err: any) { } catch (err: any) {
toast({ toast({
title: getErrText(err, '出现了点意外~'), title: getErrText(err, '出现了点意外~'),
@@ -101,12 +104,13 @@ const InputDataModal = ({
if (e.a !== defaultValues.a || e.q !== defaultValues.q) { if (e.a !== defaultValues.a || e.q !== defaultValues.q) {
setLoading(true); setLoading(true);
try { try {
await putKbDataById({ const data = {
dataId: e.dataId, dataId: e.dataId,
a: e.a, a: e.a,
q: e.q === defaultValues.q ? '' : e.q q: e.q === defaultValues.q ? '' : e.q
}); };
onSuccess(); await putKbDataById(data);
onSuccess(data);
} catch (error) {} } catch (error) {}
setLoading(false); setLoading(false);
} }
@@ -169,9 +173,37 @@ const InputDataModal = ({
</Box> </Box>
</Box> </Box>
<Flex px={6} pt={2} pb={4}> <Flex px={6} pt={2} pb={4} alignItems={'center'}>
<Box flex={1}></Box> <Box flex={1}>
<Button variant={'base'} mr={3} onClick={onClose}> {defaultValues.dataId && onDelete && (
<IconButton
variant={'outline'}
icon={<MyIcon name={'delete'} w={'16px'} h={'16px'} />}
aria-label={''}
isLoading={loading}
size={'sm'}
_hover={{
color: 'red.600',
borderColor: 'red.600'
}}
onClick={async () => {
if (!onDelete || !defaultValues.dataId) return;
try {
await delOneKbDataByDataId(defaultValues.dataId);
onDelete();
onClose();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error)
});
console.log(error);
}
}}
/>
)}
</Box>
<Button variant={'base'} mr={3} isLoading={loading} onClick={onClose}>
</Button> </Button>
<Button <Button

View File

@@ -2,19 +2,26 @@ import React, { useEffect, useMemo, useState } from 'react';
import { Box, Textarea, Button, Flex, useTheme, Grid, Progress } from '@chakra-ui/react'; import { Box, Textarea, Button, Flex, useTheme, Grid, Progress } from '@chakra-ui/react';
import { useKbStore } from '@/store/kb'; import { useKbStore } from '@/store/kb';
import type { KbTestItemType } from '@/types/plugin'; import type { KbTestItemType } from '@/types/plugin';
import { searchText } from '@/api/plugins/kb'; import { searchText, getKbDataItemById } from '@/api/plugins/kb';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useRequest } from '@/hooks/useRequest'; import { useRequest } from '@/hooks/useRequest';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { formatTimeToChatTime } from '@/utils/tools'; import { formatTimeToChatTime } from '@/utils/tools';
import InputDataModal, { type FormData } from './InputDataModal'; import InputDataModal, { type FormData } from './InputDataModal';
import { useGlobalStore } from '@/store/global';
import { getErrText } from '@/utils/tools';
import { useToast } from '@/hooks/useToast';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
const Test = () => { const Test = () => {
const { kbId } = useRouter().query as { kbId: string }; const { kbId } = useRouter().query as { kbId: string };
const theme = useTheme(); const theme = useTheme();
const { kbTestList, pushKbTestItem } = useKbStore(); const { toast } = useToast();
const { setLoading } = useGlobalStore();
const { kbTestList, pushKbTestItem, delKbTestItemById, updateKbItemById } = useKbStore();
const [inputText, setInputText] = useState(''); const [inputText, setInputText] = useState('');
const [results, setResults] = useState<KbTestItemType['results']>([]); const [kbTestItem, setKbTestItem] = useState<KbTestItemType>();
const [editData, setEditData] = useState<FormData>(); const [editData, setEditData] = useState<FormData>();
const kbTestHistory = useMemo( const kbTestHistory = useMemo(
@@ -25,19 +32,21 @@ const Test = () => {
const { mutate, isLoading } = useRequest({ const { mutate, isLoading } = useRequest({
mutationFn: () => searchText({ kbId, text: inputText.trim() }), mutationFn: () => searchText({ kbId, text: inputText.trim() }),
onSuccess(res) { onSuccess(res) {
pushKbTestItem({ const testItem = {
id: nanoid(),
kbId, kbId,
text: inputText.trim(), text: inputText.trim(),
time: new Date(), time: new Date(),
results: res results: res
}); };
pushKbTestItem(testItem);
setInputText(''); setInputText('');
setResults(res); setKbTestItem(testItem);
} }
}); });
useEffect(() => { useEffect(() => {
setResults([]); setKbTestItem(undefined);
}, [kbId]); }, [kbId]);
return ( return (
@@ -79,28 +88,55 @@ const Test = () => {
<Flex py={1} fontWeight={'bold'} borderBottom={theme.borders.base}> <Flex py={1} fontWeight={'bold'} borderBottom={theme.borders.base}>
<Box flex={1}></Box> <Box flex={1}></Box>
<Box w={'80px'}></Box> <Box w={'80px'}></Box>
<Box w={'14px'}></Box>
</Flex> </Flex>
{kbTestHistory.map((item, i) => ( {kbTestHistory.map((item) => (
<Flex <Flex
key={i} key={item.id}
p={1} p={1}
alignItems={'center'}
borderBottom={theme.borders.base} borderBottom={theme.borders.base}
_hover={{ bg: '#f4f4f4' }} _hover={{
bg: '#f4f4f4',
'& .delete': {
display: 'block'
}
}}
cursor={'pointer'} cursor={'pointer'}
onClick={() => setResults(item.results)} onClick={() => setKbTestItem(item)}
> >
<Box flex={1} mr={2}> <Box flex={1} mr={2}>
{item.text} {item.text}
</Box> </Box>
<Box w={'80px'}>{formatTimeToChatTime(item.time)}</Box> <Box w={'80px'}>{formatTimeToChatTime(item.time)}</Box>
<Box w={'14px'} h={'14px'}>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
display={'none'}
_hover={{ color: 'red.600' }}
onClick={(e) => {
e.stopPropagation();
delKbTestItemById(item.id);
kbTestItem?.id === item.id && setKbTestItem(undefined);
}}
/>
</Box>
</Flex> </Flex>
))} ))}
</Box> </Box>
</Box> </Box>
</Box> </Box>
<Box px={4} pb={4} mt={[8, 0]} h={['auto', '100%']} overflow={'overlay'} flex={1}> <Box px={4} pb={4} mt={[8, 0]} h={['auto', '100%']} overflow={'overlay'} flex={1}>
{results.length === 0 ? ( {!kbTestItem?.results || kbTestItem.results.length === 0 ? (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}> <Flex
mt={[10, 0]}
h={'100%'}
flexDirection={'column'}
alignItems={'center'}
justifyContent={'center'}
>
<MyIcon name={'empty'} color={'transparent'} w={'54px'} /> <MyIcon name={'empty'} color={'transparent'} w={'54px'} />
<Box mt={3} color={'myGray.600'}> <Box mt={3} color={'myGray.600'}>
@@ -108,9 +144,14 @@ const Test = () => {
</Flex> </Flex>
) : ( ) : (
<> <>
<Flex alignItems={'flex-end'}>
<Box fontSize={'3xl'} color={'myGray.600'}> <Box fontSize={'3xl'} color={'myGray.600'}>
</Box> </Box>
<Box fontSize={'xs'} color={'myGray.500'} ml={1}>
QA内容可能不是最新
</Box>
</Flex>
<Grid <Grid
mt={1} mt={1}
gridTemplateColumns={[ gridTemplateColumns={[
@@ -122,7 +163,7 @@ const Test = () => {
]} ]}
gridGap={4} gridGap={4}
> >
{results.map((item) => ( {kbTestItem?.results.map((item) => (
<Box <Box
key={item.id} key={item.id}
pb={2} pb={2}
@@ -131,20 +172,35 @@ const Test = () => {
_notLast={{ mb: 2 }} _notLast={{ mb: 2 }}
cursor={'pointer'} cursor={'pointer'}
title={'编辑'} title={'编辑'}
onClick={() => onClick={async () => {
setEditData({ try {
dataId: item.id, setLoading(true);
q: item.q, const data = await getKbDataItemById(item.id);
a: item.a
}) if (!data) {
throw new Error('该数据已被删除');
} }
setEditData({
dataId: data.id,
q: data.q,
a: data.a
});
} catch (err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
setLoading(false);
}}
> >
<Flex p={3} alignItems={'center'} color={'myGray.500'}> <Flex p={3} alignItems={'center'} color={'myGray.500'}>
<MyIcon name={'kbTest'} w={'14px'} /> <MyIcon name={'kbTest'} w={'14px'} />
<Progress <Progress
mx={2} mx={2}
flex={1} flex={1}
value={50} value={item.score * 100}
size="sm" size="sm"
borderRadius={'20px'} borderRadius={'20px'}
colorScheme="gray" colorScheme="gray"
@@ -173,7 +229,37 @@ const Test = () => {
kbId={kbId} kbId={kbId}
defaultValues={editData} defaultValues={editData}
onClose={() => setEditData(undefined)} onClose={() => setEditData(undefined)}
onSuccess={() => setEditData(undefined)} onSuccess={(data) => {
if (kbTestItem && editData.dataId) {
const newTestItem = {
...kbTestItem,
results: kbTestItem.results.map((item) =>
item.id === editData.dataId
? {
...item,
q: data.q,
a: data.a
}
: item
)
};
updateKbItemById(newTestItem);
setKbTestItem(newTestItem);
}
setEditData(undefined);
}}
onDelete={() => {
if (kbTestItem && editData.dataId) {
const newTestItem = {
...kbTestItem,
results: kbTestItem.results.filter((item) => item.id !== editData.dataId)
};
updateKbItemById(newTestItem);
setKbTestItem(newTestItem);
}
setEditData(undefined);
}}
/> />
)} )}
</Box> </Box>

View File

@@ -6,6 +6,8 @@ import { type KbTestItemType } from '@/types/plugin';
type State = { type State = {
kbTestList: KbTestItemType[]; kbTestList: KbTestItemType[];
pushKbTestItem: (data: KbTestItemType) => void; pushKbTestItem: (data: KbTestItemType) => void;
delKbTestItemById: (id: string) => void;
updateKbItemById: (data: KbTestItemType) => void;
}; };
export const useKbStore = create<State>()( export const useKbStore = create<State>()(
@@ -15,7 +17,17 @@ export const useKbStore = create<State>()(
kbTestList: [], kbTestList: [],
pushKbTestItem(data) { pushKbTestItem(data) {
set((state) => { set((state) => {
state.kbTestList = [data, ...state.kbTestList].slice(0, 400); state.kbTestList = [data, ...state.kbTestList].slice(0, 500);
});
},
delKbTestItemById(id) {
set((state) => {
state.kbTestList = state.kbTestList.filter((item) => item.id !== id);
});
},
updateKbItemById(data: KbTestItemType) {
set((state) => {
state.kbTestList = state.kbTestList.map((item) => (item.id === data.id ? data : item));
}); });
} }
})), })),

View File

@@ -14,6 +14,7 @@ export interface KbDataItemType {
} }
export type KbTestItemType = { export type KbTestItemType = {
id: string;
kbId: string; kbId: string;
text: string; text: string;
time: Date; time: Date;