mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 17:29:44 +00:00
perf: kb test
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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数据
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
@@ -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 && (
|
||||||
|
@@ -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',
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 data = {
|
||||||
|
a: e.a,
|
||||||
|
q: e.q,
|
||||||
|
source: '手动录入'
|
||||||
|
};
|
||||||
const { insertLen } = await postKbDataFromList({
|
const { insertLen } = await postKbDataFromList({
|
||||||
kbId,
|
kbId,
|
||||||
mode: TrainingModeEnum.index,
|
mode: TrainingModeEnum.index,
|
||||||
data: [
|
data: [data]
|
||||||
{
|
|
||||||
a: e.a,
|
|
||||||
q: e.q,
|
|
||||||
source: '手动录入'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
||||||
|
@@ -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>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Box fontSize={'3xl'} color={'myGray.600'}>
|
<Flex alignItems={'flex-end'}>
|
||||||
测试结果
|
<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>
|
||||||
|
@@ -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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
1
client/src/types/plugin.d.ts
vendored
1
client/src/types/plugin.d.ts
vendored
@@ -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;
|
||||||
|
Reference in New Issue
Block a user