feat: search test

This commit is contained in:
archer
2023-06-12 18:18:08 +08:00
parent 6ac7119edf
commit 71dd7f3e6c
16 changed files with 747 additions and 515 deletions

View File

@@ -103,7 +103,7 @@ export async function appKbSearch({
.map((item) => `'${item}'`)
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
promptVector[0]
}]' limit 8;
}]' limit 15;
COMMIT;`
);

View File

@@ -0,0 +1,55 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import { openaiEmbedding } from '../plugin/openaiEmbedding';
import type { KbTestItemType } from '@/types/plugin';
export type Props = {
kbId: string;
text: string;
};
export type Response = KbTestItemType['results'];
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { kbId, text } = req.body as Props;
if (!kbId || !text) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req });
if (!userId) {
throw new Error('缺少用户ID');
}
const vector = await openaiEmbedding({
userId,
input: [text],
type: 'training'
});
const response: any = await PgClient.query(
`BEGIN;
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
select id,q,a,source,(vector <#> '[${
vector[0]
}]') * -1 AS score from modelData where kb_id='${kbId}' AND user_id='${userId}' order by vector <#> '[${
vector[0]
}]' limit 12;
COMMIT;`
);
jsonRes<Response>(res, { data: response?.[2]?.rows || [] });
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
}
});

View File

@@ -138,7 +138,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
});
return (
<Box position={'relative'}>
<Box position={'relative'} px={5} pb={[1, 5]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
: {total}
@@ -209,7 +209,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
</Flex>
<Grid
minH={'200px'}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={4}
>
{kbDataList.map((item) => (
@@ -230,8 +230,8 @@ const DataCard = ({ kbId }: { kbId: string }) => {
})
}
>
<Flex py={3} px={4} h={'36px'}>
<Box className={'textEllipsis'} flex={1} fontSize={'sm'}>
<Flex py={3} px={4} h={'40px'}>
<Box className={'textEllipsis'} flex={1}>
{item.source?.trim()}
</Box>
<IconButton
@@ -252,14 +252,10 @@ const DataCard = ({ kbId }: { kbId: string }) => {
}}
/>
</Flex>
<Box
h={'100px'}
overflow={'hidden'}
wordBreak={'break-all'}
px={3}
color={'myGray.600'}
>
<Box color={'myGray.1000'}>{item.q}</Box>
<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>

View File

@@ -1,16 +1,24 @@
import React, { useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin';
import { useScreen } from '@/hooks/useScreen';
import DataCard from './DataCard';
import { getErrText } from '@/utils/tools';
import Info, { type ComponentRef } from './Info';
import { type ComponentRef } from './Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
import DataCard from './DataCard';
const Test = dynamic(() => import('./Test'), {
ssr: false
});
const Info = dynamic(() => import('./Info'), {
ssr: false
});
enum TabEnum {
data = 'data',
@@ -51,8 +59,15 @@ const Detail = ({ kbId }: { kbId: string }) => {
});
return (
<Box bg={'#fcfcfc'} h={'100%'} p={5} overflow={'overlay'} position={'relative'}>
<Box mb={5}>
<Flex
flexDirection={'column'}
bg={'#fcfcfc'}
h={'100%'}
pt={5}
overflow={'overlay'}
position={'relative'}
>
<Box mb={3}>
<Tabs
m={'auto'}
w={'260px'}
@@ -66,9 +81,12 @@ const Detail = ({ kbId }: { kbId: string }) => {
onChange={(e: any) => setCurrentTab(e)}
/>
</Box>
{currentTab === TabEnum.data && <DataCard kbId={kbId} />}
{currentTab === TabEnum.info && <Info ref={BasicInfo} kbId={kbId} form={form} />}
</Box>
<Box flex={'1 0 0'} overflow={'overlay'}>
{currentTab === TabEnum.data && <DataCard kbId={kbId} />}
{currentTab === TabEnum.test && <Test />}
{currentTab === TabEnum.info && <Info ref={BasicInfo} kbId={kbId} form={form} />}
</Box>
</Flex>
);
};

View File

@@ -7,7 +7,7 @@ import React, {
ForwardedRef
} from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, Button, FormControl, IconButton, Tooltip, Input } from '@chakra-ui/react';
import { Box, Flex, Button, FormControl, IconButton, Tooltip, Input, Card } from '@chakra-ui/react';
import { QuestionOutlineIcon, DeleteIcon } from '@chakra-ui/icons';
import { delKbById, putKbById } from '@/api/plugins/kb';
import { useSelectFile } from '@/hooks/useSelectFile';
@@ -140,7 +140,7 @@ const Info = (
}));
return (
<Flex flexDirection={'column'} alignItems={'center'}>
<Flex px={5} flexDirection={'column'} alignItems={'center'}>
<Flex mt={5} w={'100%'} maxW={'350px'} alignItems={'center'}>
<Box flex={'0 0 90px'} w={0}>
@@ -186,7 +186,7 @@ const Info = (
setRefresh(!refresh);
}}
/>
<Box mt={2} w="100%">
<Box pl={'90px'} mt={2} w="100%">
{getValues('tags')
.split(' ')
.filter((item) => item)

View File

@@ -0,0 +1,183 @@
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 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';
const Test = () => {
const { kbId } = useRouter().query as { kbId: string };
const theme = useTheme();
const { kbTestList, pushKbTestItem } = useKbStore();
const [inputText, setInputText] = useState('');
const [results, setResults] = useState<KbTestItemType['results']>([]);
const [editData, setEditData] = useState<FormData>();
const kbTestHistory = useMemo(
() => kbTestList.filter((item) => item.kbId === kbId),
[kbId, kbTestList]
);
const { mutate, isLoading } = useRequest({
mutationFn: () => searchText({ kbId, text: inputText.trim() }),
onSuccess(res) {
pushKbTestItem({
kbId,
text: inputText.trim(),
time: new Date(),
results: res
});
setInputText('');
setResults(res);
}
});
useEffect(() => {
setResults([]);
}, [kbId]);
return (
<Box h={'100%'} display={['block', 'flex']}>
<Box
h={['auto', '100%']}
overflow={'overlay'}
flex={1}
maxW={'500px'}
px={4}
borderRight={['none', theme.borders.base]}
>
<Box border={'2px solid'} borderColor={'myBlue.600'} p={3} borderRadius={'md'}>
<Box fontSize={'sm'} fontWeight={'bold'}>
<MyIcon mr={2} name={'text'} w={'18px'} h={'18px'} color={'myBlue.700'} />
</Box>
<Textarea
rows={6}
resize={'none'}
variant={'unstyled'}
maxLength={1000}
placeholder="输入需要测试的文本"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
/>
<Flex justifyContent={'flex-end'}>
<Button isDisabled={inputText === ''} isLoading={isLoading} onClick={mutate}>
</Button>
</Flex>
</Box>
<Box mt={5} display={['none', 'block']}>
<Flex alignItems={'center'} color={'myGray.600'}>
<MyIcon mr={2} name={'history'} w={'16px'} h={'16px'} />
<Box fontSize={'2xl'}></Box>
</Flex>
<Box mt={2}>
<Flex py={1} fontWeight={'bold'} borderBottom={theme.borders.base}>
<Box flex={1}></Box>
<Box w={'80px'}></Box>
</Flex>
{kbTestHistory.map((item, i) => (
<Flex
key={i}
p={1}
borderBottom={theme.borders.base}
_hover={{ bg: '#f4f4f4' }}
cursor={'pointer'}
onClick={() => setResults(item.results)}
>
<Box flex={1} mr={2}>
{item.text}
</Box>
<Box w={'80px'}>{formatTimeToChatTime(item.time)}</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'}>
<MyIcon name={'empty'} color={'transparent'} w={'54px'} />
<Box mt={3} color={'myGray.600'}>
</Box>
</Flex>
) : (
<>
<Box fontSize={'3xl'} color={'myGray.600'}>
</Box>
<Grid
mt={1}
gridTemplateColumns={[
'repeat(1,1fr)',
'repeat(1,1fr)',
'repeat(1,1fr)',
'repeat(2,1fr)',
'repeat(3,1fr)'
]}
gridGap={4}
>
{results.map((item) => (
<Box
key={item.id}
pb={2}
borderRadius={'sm'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
cursor={'pointer'}
title={'编辑'}
onClick={() =>
setEditData({
dataId: item.id,
q: item.q,
a: item.a
})
}
>
<Flex p={3} alignItems={'center'} color={'myGray.500'}>
<MyIcon name={'kbTest'} w={'14px'} />
<Progress
mx={2}
flex={1}
value={50}
size="sm"
borderRadius={'20px'}
colorScheme="gray"
/>
<Box>{item.score.toFixed(4)}</Box>
</Flex>
<Box
px={2}
fontSize={'xs'}
color={'myGray.600'}
maxH={'200px'}
overflow={'overlay'}
>
<Box>{item.q}</Box>
<Box>{item.a}</Box>
</Box>
</Box>
))}
</Grid>
</>
)}
</Box>
{editData && (
<InputDataModal
kbId={kbId}
defaultValues={editData}
onClose={() => setEditData(undefined)}
onSuccess={() => setEditData(undefined)}
/>
)}
</Box>
);
};
export default Test;