mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
feat: search test
This commit is contained in:
@@ -13,8 +13,8 @@
|
||||
"@alicloud/openapi-client": "^0.4.5",
|
||||
"@alicloud/tea-util": "^1.4.5",
|
||||
"@chakra-ui/icons": "^2.0.17",
|
||||
"@chakra-ui/react": "^2.5.1",
|
||||
"@chakra-ui/system": "^2.5.5",
|
||||
"@chakra-ui/react": "^2.7.0",
|
||||
"@chakra-ui/system": "^2.5.8",
|
||||
"@dqbd/tiktoken": "^1.0.6",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
|
903
client/pnpm-lock.yaml
generated
903
client/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,10 @@ import {
|
||||
Props as PushDataProps,
|
||||
Response as PushDateResponse
|
||||
} from '@/pages/api/openapi/kb/pushData';
|
||||
import {
|
||||
Props as SearchTestProps,
|
||||
Response as SearchTestResponse
|
||||
} from '@/pages/api/openapi/kb/searchTest';
|
||||
|
||||
export type KbUpdateParams = {
|
||||
id: string;
|
||||
@@ -83,3 +87,6 @@ export const postSplitData = (data: {
|
||||
prompt: string;
|
||||
mode: `${TrainingModeEnum}`;
|
||||
}) => POST(`/openapi/text/pushData`, data);
|
||||
|
||||
export const searchText = (data: SearchTestProps) =>
|
||||
POST<SearchTestResponse>(`/openapi/kb/searchTest`, data);
|
||||
|
1
client/src/components/Icon/icons/history.svg
Normal file
1
client/src/components/Icon/icons/history.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686557412109" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2150" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M511.998 64C264.574 64 64 264.574 64 511.998S264.574 960 511.998 960 960 759.422 960 511.998 759.422 64 511.998 64z m353.851 597.438c-82.215 194.648-306.657 285.794-501.306 203.579S78.749 558.36 160.964 363.711 467.621 77.917 662.27 160.132c168.009 70.963 262.57 250.652 225.926 429.313a383.995 383.995 0 0 1-22.347 71.993z" p-id="2151"></path><path d="M543.311 498.639V256.121c0-17.657-14.314-31.97-31.97-31.97s-31.97 14.314-31.97 31.97v269.005l201.481 201.481c12.485 12.485 32.728 12.485 45.213 0s12.485-32.728 0-45.213L543.311 498.639z" p-id="2152"></path></svg>
|
After Width: | Height: | Size: 875 B |
1
client/src/components/Icon/icons/kbTest.svg
Normal file
1
client/src/components/Icon/icons/kbTest.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686561811905" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2855" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M992 528c0 273.9-222.1 496-496 496S0 801.9 0 528 222.1 32 496 32c86.2 0 167.3 22 238 60.7 2.3 1.3 2.8 4.4 0.9 6.3l-37 37.3-4.2 4.3c-1.2 1.2-3.1 1.5-4.6 0.8-8.2-4.1-16.5-7.9-24.9-11.5C610.9 107.4 554.3 96 496 96s-114.9 11.4-168.1 33.9c-51.4 21.8-97.7 52.9-137.3 92.6-39.7 39.7-70.9 85.9-92.6 137.3C75.4 413.1 64 469.6 64 528c0 58.3 11.4 114.9 33.9 168.1 21.8 51.4 52.9 97.6 92.6 137.3 39.7 39.7 85.9 70.9 137.3 92.6 53.3 22.6 109.9 34 168.2 34s114.9-11.4 168.1-33.9c51.4-21.8 97.7-52.9 137.3-92.6 39.7-39.7 70.9-85.9 92.6-137.3 22.6-53.3 34-109.9 34-168.2 0-58.4-11.4-114.9-33.9-168.1-3.6-8.5-7.4-16.8-11.5-25-0.8-1.5-0.5-3.4 0.8-4.6l4.3-4.2 37.3-37c1.9-1.9 5-1.4 6.3 0.9C970 360.6 992 441.7 992 528z" p-id="2856"></path><path d="M781.4 397c-3.7-8-11.7-13.1-20.6-13.1H740c-6 0-11.8 2.4-16 6.6-7 7-8.6 17.6-4.1 26.4 2.6 5.1 5 10.3 7.3 15.7 13.2 31.2 19.9 64.3 19.9 98.5s-6.7 67.3-19.9 98.5c-12.7 30.1-31 57.2-54.2 80.4-23.3 23.3-50.3 41.5-80.4 54.2-31.3 13.1-64.4 19.8-98.6 19.8s-67.3-6.7-98.5-19.9c-30.1-12.7-57.2-31-80.4-54.2-23.3-23.3-41.5-50.3-54.2-80.4-13.2-31.2-19.9-64.3-19.9-98.5s6.7-67.3 19.9-98.5c12.7-30.1 31-57.2 54.2-80.4 23.3-23.3 50.3-41.5 80.4-54.2 31.2-13.2 64.3-19.9 98.5-19.9s67.3 6.7 98.5 19.9c4.9 2.1 9.8 4.3 14.6 6.7 8.8 4.4 19.4 2.6 26.3-4.4 4.3-4.3 6.7-10.1 6.7-16.2v-20.2c0-9-5.2-17.1-13.4-20.8-40.4-18.6-85.3-29-132.6-29-175.5 0-318 143.4-317 318.9C178 707.1 319.6 848 494 848c174.8 0 316.6-141.3 317-316.2 0.1-48.2-10.5-93.9-29.6-134.8z" p-id="2857"></path><path d="M634.5 488.5c-0.8-2.9-4.5-3.9-6.7-1.7l-34.7 34.7-1.8 1.8c-9 9-15.7 20.1-20.1 32.1-11.5 31.6-42.4 54-78.3 52.7-41.6-1.6-75.3-35.3-76.9-76.9-1.4-35.9 21-66.8 52.7-78.3 12-4.4 23-11.1 32.1-20.1l1.8-1.8 34.7-34.7c2.2-2.2 1.2-5.8-1.7-6.7-12.9-3.7-26.5-5.6-40.6-5.5-79.4 0.5-143 64.5-143 143.9 0 79.5 64.5 144 144 144 79.4 0 143.4-63.6 144-142.9 0.1-14.1-1.8-27.8-5.5-40.6z" p-id="2858"></path><path d="M1014.3 146H882c-2.2 0-4-1.8-4-4V9.8c0-2.4-2-4-4-4-1 0-2 0.4-2.8 1.2L766.8 112.4l-46.1 46.5-44 44.4c-3 3-4.6 7-4.6 11.3v85.5c0 4.3-1.7 8.3-4.7 11.3l-94.7 94.7-47.4 47.4-51.8 51.9c-12.5 12.5-12.5 32.8 0 45.3 6.3 6.3 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l51.8-51.9 123.2-123.2 19-19c3-3 7.1-4.7 11.3-4.7h85.5c4.2 0 8.3-1.7 11.3-4.6l44.3-43.9 46.5-46.1L1017 152.9c2.6-2.6 0.8-6.9-2.7-6.9zM864 214.3l-44 43.5-25.6 25.4c-3 3-7 4.6-11.3 4.6H744c-4.4 0-8-3.6-8-8v-39c0-4.2 1.7-8.3 4.6-11.3l25.5-25.7 43.5-43.9 1.6-1.6c1.6-1.7 4.5-0.7 4.8 1.6 4.8 25.8 23.5 41.6 48.6 47.7 2.1 0.5 2.9 3.2 1.3 4.7l-1.9 2z" p-id="2859"></path></svg>
|
After Width: | Height: | Size: 2.8 KiB |
1
client/src/components/Icon/icons/text.svg
Normal file
1
client/src/components/Icon/icons/text.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686557165145" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2404" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M815.104 69.632q27.648 25.6 44.032 42.496t25.088 28.672 10.752 19.968 2.048 14.336l0 16.384-151.552 0q-10.24 0-17.92-7.68t-12.8-17.92-7.68-20.992-2.56-16.896l0-126.976 3.072 0q8.192 0 16.896 2.56t19.968 9.728 28.16 20.48 42.496 35.84zM640 129.024q0 20.48 6.144 42.496t19.456 40.96 33.792 31.232 48.128 12.288l149.504 0 0 577.536q0 29.696-11.776 53.248t-31.232 39.936-43.008 25.6-46.08 9.216l-503.808 0q-19.456 0-42.496-11.264t-43.008-29.696-33.28-41.984-13.312-49.152l0-696.32q0-21.504 9.728-44.544t26.624-42.496 38.4-32.256 45.056-12.8l391.168 0 0 128zM704.512 768q26.624 0 45.056-18.944t18.432-45.568-18.432-45.056-45.056-18.432l-384 0q-26.624 0-45.056 18.432t-18.432 45.056 18.432 45.568 45.056 18.944l384 0zM768 448.512q0-26.624-18.432-45.568t-45.056-18.944l-384 0q-26.624 0-45.056 18.944t-18.432 45.568 18.432 45.056 45.056 18.432l384 0q26.624 0 45.056-18.432t18.432-45.056z" p-id="2405" ></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@@ -30,7 +30,10 @@ const map = {
|
||||
menu: require('./icons/menu.svg').default,
|
||||
edit: require('./icons/edit.svg').default,
|
||||
inform: require('./icons/inform.svg').default,
|
||||
export: require('./icons/export.svg').default
|
||||
export: require('./icons/export.svg').default,
|
||||
text: require('./icons/text.svg').default,
|
||||
history: require('./icons/history.svg').default,
|
||||
kbTest: require('./icons/kbTest.svg').default
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof map;
|
||||
|
@@ -71,6 +71,9 @@ const Button = defineStyleConfig({
|
||||
color: 'white',
|
||||
_hover: {
|
||||
filter: 'brightness(115%)'
|
||||
},
|
||||
_disabled: {
|
||||
bg: '#3370ff !important'
|
||||
}
|
||||
},
|
||||
base: {
|
||||
|
@@ -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;
|
30
client/src/store/kb.ts
Normal file
30
client/src/store/kb.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { type KbTestItemType } from '@/types/plugin';
|
||||
|
||||
type State = {
|
||||
kbTestList: KbTestItemType[];
|
||||
pushKbTestItem: (data: KbTestItemType) => void;
|
||||
};
|
||||
|
||||
export const useKbStore = create<State>()(
|
||||
devtools(
|
||||
persist(
|
||||
immer((set, get) => ({
|
||||
kbTestList: [],
|
||||
pushKbTestItem(data) {
|
||||
set((state) => {
|
||||
state.kbTestList = [data, ...state.kbTestList].slice(0, 400);
|
||||
});
|
||||
}
|
||||
})),
|
||||
{
|
||||
name: 'kbStore',
|
||||
partialize: (state) => ({
|
||||
kbTestList: state.kbTestList
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
7
client/src/types/plugin.d.ts
vendored
7
client/src/types/plugin.d.ts
vendored
@@ -13,6 +13,13 @@ export interface KbDataItemType {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export type KbTestItemType = {
|
||||
kbId: string;
|
||||
text: string;
|
||||
time: Date;
|
||||
results: (KbDataItemType & { score: number })[];
|
||||
};
|
||||
|
||||
export type TextPluginRequestParams = {
|
||||
input: string;
|
||||
};
|
||||
|
Reference in New Issue
Block a user