mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-01 11:58:38 +00:00
feat: search test
This commit is contained in:
@@ -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;`
|
||||
);
|
||||
|
||||
|
55
client/src/pages/api/openapi/kb/searchTest.ts
Normal file
55
client/src/pages/api/openapi/kb/searchTest.ts
Normal 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
|
||||
});
|
||||
}
|
||||
});
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -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)
|
||||
|
183
client/src/pages/kb/components/Test.tsx
Normal file
183
client/src/pages/kb/components/Test.tsx
Normal 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;
|
Reference in New Issue
Block a user