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;
historyId: string;
quoteId: string;
sourceText: string;
}) => 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 { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin';
import { type QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
import {
Props as PushDataProps,
Response as PushDateResponse
@@ -59,7 +60,7 @@ export const getTrainingData = (data: { kbId: string; init: boolean }) =>
}>(`/plugins/kb/data/getTrainingData`, data);
export const getKbDataItemById = (dataId: string) =>
GET(`/plugins/kb/data/getDataById`, { dataId });
GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据

View File

@@ -6,10 +6,16 @@ import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let { chatId, historyId, quoteId } = req.query as {
let {
chatId,
historyId,
quoteId,
sourceText = ''
} = req.query as {
chatId: string;
historyId: string;
quoteId: string;
sourceText: string;
};
await connectToDatabase();
@@ -27,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
{
$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
*/
const updateQuoteStatus = useCallback(
async (quoteId: string) => {
async (quoteId: string, sourceText: string) => {
setIsLoading(true);
try {
await updateHistoryQuote({
chatId,
historyId,
quoteId
quoteId,
sourceText
});
// reload quote
refetch();
@@ -163,7 +164,8 @@ const QuoteModal = ({
{editDataItem && (
<InputDataModal
onClose={() => setEditDataItem(undefined)}
onSuccess={() => updateQuoteStatus(editDataItem.dataId)}
onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')}
onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')}
kbId=""
defaultValues={editDataItem}
/>

View File

@@ -208,7 +208,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
/>
</Flex>
<Grid
minH={'200px'}
minH={'100px'}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={4}
>
@@ -216,7 +216,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
<Card
key={item.id}
cursor={'pointer'}
pb={3}
pt={3}
userSelect={'none'}
boxShadow={'none'}
_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}>
{item.source?.trim()}
</Box>
@@ -252,12 +265,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
}}
/>
</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>
))}
</Grid>
@@ -271,7 +278,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
kbId={kbId}
defaultValues={editInputData}
onClose={() => setEditInputData(undefined)}
onSuccess={refetchData}
onSuccess={() => refetchData()}
/>
)}
{isOpenSelectFileModal && (

View File

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

View File

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

View File

@@ -8,19 +8,22 @@ import {
ModalContent,
ModalHeader,
ModalCloseButton,
Textarea
Textarea,
IconButton
} from '@chakra-ui/react';
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 { TrainingModeEnum } from '@/constants/plugin';
import { getErrText } from '@/utils/tools';
import MyIcon from '@/components/Icon';
export type FormData = { dataId?: string; a: string; q: string };
const InputDataModal = ({
onClose,
onSuccess,
onDelete,
kbId,
defaultValues = {
a: '',
@@ -28,7 +31,8 @@ const InputDataModal = ({
}
}: {
onClose: () => void;
onSuccess: () => void;
onSuccess: (data: FormData) => void;
onDelete?: () => void;
kbId: string;
defaultValues?: FormData;
}) => {
@@ -54,16 +58,15 @@ const InputDataModal = ({
setLoading(true);
try {
const data = {
a: e.a,
q: e.q,
source: '手动录入'
};
const { insertLen } = await postKbDataFromList({
kbId,
mode: TrainingModeEnum.index,
data: [
{
a: e.a,
q: e.q,
source: '手动录入'
}
]
data: [data]
});
if (insertLen === 0) {
@@ -82,7 +85,7 @@ const InputDataModal = ({
});
}
onSuccess();
onSuccess(data);
} catch (err: any) {
toast({
title: getErrText(err, '出现了点意外~'),
@@ -101,12 +104,13 @@ const InputDataModal = ({
if (e.a !== defaultValues.a || e.q !== defaultValues.q) {
setLoading(true);
try {
await putKbDataById({
const data = {
dataId: e.dataId,
a: e.a,
q: e.q === defaultValues.q ? '' : e.q
});
onSuccess();
};
await putKbDataById(data);
onSuccess(data);
} catch (error) {}
setLoading(false);
}
@@ -169,9 +173,37 @@ const InputDataModal = ({
</Box>
</Box>
<Flex px={6} pt={2} pb={4}>
<Box flex={1}></Box>
<Button variant={'base'} mr={3} onClick={onClose}>
<Flex px={6} pt={2} pb={4} alignItems={'center'}>
<Box flex={1}>
{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

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

View File

@@ -6,6 +6,8 @@ import { type KbTestItemType } from '@/types/plugin';
type State = {
kbTestList: KbTestItemType[];
pushKbTestItem: (data: KbTestItemType) => void;
delKbTestItemById: (id: string) => void;
updateKbItemById: (data: KbTestItemType) => void;
};
export const useKbStore = create<State>()(
@@ -15,7 +17,17 @@ export const useKbStore = create<State>()(
kbTestList: [],
pushKbTestItem(data) {
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 = {
id: string;
kbId: string;
text: string;
time: Date;