349 lines
12 KiB
TypeScript
349 lines
12 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import {
|
|
Box,
|
|
Textarea,
|
|
Button,
|
|
Flex,
|
|
useTheme,
|
|
Grid,
|
|
Progress,
|
|
Switch,
|
|
useDisclosure
|
|
} from '@chakra-ui/react';
|
|
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
|
import { useSearchTestStore, SearchTestStoreItemType } from '@/web/core/dataset/store/searchTest';
|
|
import { getDatasetDataItemById, postSearchText } from '@/web/core/dataset/api';
|
|
import MyIcon from '@/components/Icon';
|
|
import { useRequest } from '@/web/common/hooks/useRequest';
|
|
import { formatTimeToChatTime } from '@/utils/tools';
|
|
import InputDataModal, { type InputDataType } from './InputDataModal';
|
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
|
import { useToast } from '@/web/common/hooks/useToast';
|
|
import { customAlphabet } from 'nanoid';
|
|
import MyTooltip from '@/components/MyTooltip';
|
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
|
import { useTranslation } from 'next-i18next';
|
|
import { SearchTestResponse } from '@/global/core/dataset/api';
|
|
import { DatasetSearchModeEnum, DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
|
|
import dynamic from 'next/dynamic';
|
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
|
|
|
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
|
|
|
|
const Test = ({ datasetId }: { datasetId: string }) => {
|
|
const { t } = useTranslation();
|
|
const theme = useTheme();
|
|
const { toast } = useToast();
|
|
const { setLoading } = useSystemStore();
|
|
const { datasetDetail } = useDatasetStore();
|
|
const { datasetTestList, pushDatasetTestItem, delDatasetTestItemById, updateDatasetItemById } =
|
|
useSearchTestStore();
|
|
const [inputText, setInputText] = useState('');
|
|
const [datasetTestItem, setDatasetTestItem] = useState<SearchTestStoreItemType>();
|
|
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
|
|
const [searchMode, setSearchMode] = useState<`${DatasetSearchModeEnum}`>(
|
|
DatasetSearchModeEnum.embedding
|
|
);
|
|
const searchModeData = DatasetSearchModeMap[searchMode];
|
|
|
|
const {
|
|
isOpen: isOpenSelectMode,
|
|
onOpen: onOpenSelectMode,
|
|
onClose: onCloseSelectMode
|
|
} = useDisclosure();
|
|
|
|
const testHistories = useMemo(
|
|
() => datasetTestList.filter((item) => item.datasetId === datasetId),
|
|
[datasetId, datasetTestList]
|
|
);
|
|
|
|
const { mutate, isLoading } = useRequest({
|
|
mutationFn: () => postSearchText({ datasetId, text: inputText.trim(), searchMode, limit: 30 }),
|
|
onSuccess(res: SearchTestResponse) {
|
|
if (!res || res.list.length === 0) {
|
|
return toast({
|
|
status: 'warning',
|
|
title: t('dataset.test.noResult')
|
|
});
|
|
}
|
|
const testItem: SearchTestStoreItemType = {
|
|
id: nanoid(),
|
|
datasetId,
|
|
text: inputText.trim(),
|
|
time: new Date(),
|
|
results: res.list,
|
|
duration: res.duration
|
|
};
|
|
pushDatasetTestItem(testItem);
|
|
setDatasetTestItem(testItem);
|
|
},
|
|
onError(err) {
|
|
toast({
|
|
title: getErrText(err),
|
|
status: 'error'
|
|
});
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
setDatasetTestItem(undefined);
|
|
}, [datasetId]);
|
|
|
|
return (
|
|
<Box h={'100%'} display={['block', 'flex']}>
|
|
{/* input */}
|
|
<Box
|
|
h={['auto', '100%']}
|
|
display={['block', 'flex']}
|
|
flexDirection={'column'}
|
|
flex={1}
|
|
maxW={'500px'}
|
|
py={4}
|
|
borderRight={['none', theme.borders.base]}
|
|
>
|
|
<Box border={'2px solid'} borderColor={'myBlue.600'} p={3} mx={4} borderRadius={'md'}>
|
|
<Flex alignItems={'center'}>
|
|
<Box fontSize={'sm'} fontWeight={'bold'} flex={1}>
|
|
<MyIcon mr={2} name={'text'} w={'18px'} h={'18px'} color={'myBlue.700'} />
|
|
{t('core.dataset.test.Test Text')}
|
|
</Box>
|
|
<Button
|
|
variant={'base'}
|
|
leftIcon={<MyIcon name={searchModeData.icon as any} w={'14px'} />}
|
|
size={'sm'}
|
|
onClick={onOpenSelectMode}
|
|
>
|
|
{t(searchModeData.title)}
|
|
</Button>
|
|
</Flex>
|
|
<Textarea
|
|
rows={6}
|
|
resize={'none'}
|
|
variant={'unstyled'}
|
|
maxLength={datasetDetail.vectorModel.maxToken}
|
|
placeholder={t('core.dataset.test.Test Text Placeholder')}
|
|
value={inputText}
|
|
onChange={(e) => setInputText(e.target.value)}
|
|
/>
|
|
<Flex alignItems={'center'} justifyContent={'flex-end'}>
|
|
<Box mx={3} color={'myGray.500'}>
|
|
{inputText.length}
|
|
</Box>
|
|
<Button isDisabled={inputText === ''} isLoading={isLoading} onClick={mutate}>
|
|
{t('core.dataset.test.Test')}
|
|
</Button>
|
|
</Flex>
|
|
</Box>
|
|
<Box mt={5} flex={'1 0 0'} px={4} overflow={'overlay'} display={['none', 'block']}>
|
|
<Flex alignItems={'center'} color={'myGray.600'}>
|
|
<MyIcon mr={2} name={'history'} w={'16px'} h={'16px'} />
|
|
<Box fontSize={'2xl'}>{t('core.dataset.test.test history')}</Box>
|
|
</Flex>
|
|
<Box mt={2}>
|
|
<Flex py={2} fontWeight={'bold'} borderBottom={theme.borders.sm}>
|
|
<Box flex={1}>{t('core.dataset.test.Test Text')}</Box>
|
|
<Box w={'80px'}>{t('common.Time')}</Box>
|
|
<Box w={'14px'}></Box>
|
|
</Flex>
|
|
{testHistories.map((item) => (
|
|
<Flex
|
|
key={item.id}
|
|
p={1}
|
|
alignItems={'center'}
|
|
borderBottom={theme.borders.base}
|
|
_hover={{
|
|
bg: '#f4f4f4',
|
|
'& .delete': {
|
|
display: 'block'
|
|
}
|
|
}}
|
|
cursor={'pointer'}
|
|
onClick={() => setDatasetTestItem(item)}
|
|
>
|
|
<Box flex={1} mr={2}>
|
|
{item.text}
|
|
</Box>
|
|
<Box w={'80px'}>{formatTimeToChatTime(item.time)}</Box>
|
|
<MyTooltip label={t('core.dataset.test.delete test history')}>
|
|
<Box w={'14px'} h={'14px'}>
|
|
<MyIcon
|
|
className="delete"
|
|
name={'delete'}
|
|
w={'14px'}
|
|
display={'none'}
|
|
_hover={{ color: 'red.600' }}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
delDatasetTestItemById(item.id);
|
|
datasetTestItem?.id === item.id && setDatasetTestItem(undefined);
|
|
}}
|
|
/>
|
|
</Box>
|
|
</MyTooltip>
|
|
</Flex>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
{/* result show */}
|
|
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={'1 0 0'}>
|
|
{!datasetTestItem?.results || datasetTestItem.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'}>
|
|
{t('core.dataset.test.test result placeholder')}
|
|
</Box>
|
|
</Flex>
|
|
) : (
|
|
<>
|
|
<Flex alignItems={'center'}>
|
|
<Box fontSize={'3xl'} color={'myGray.600'}>
|
|
{t('core.dataset.test.Test Result')}
|
|
</Box>
|
|
<MyTooltip label={t('core.dataset.test.test result tip')} forceShow>
|
|
<QuestionOutlineIcon
|
|
mx={2}
|
|
color={'myGray.600'}
|
|
cursor={'pointer'}
|
|
fontSize={'lg'}
|
|
/>
|
|
</MyTooltip>
|
|
<Box>({datasetTestItem.duration})</Box>
|
|
</Flex>
|
|
<Grid
|
|
mt={1}
|
|
gridTemplateColumns={[
|
|
'repeat(1,1fr)',
|
|
'repeat(1,1fr)',
|
|
'repeat(1,1fr)',
|
|
'repeat(1,1fr)',
|
|
'repeat(2,1fr)'
|
|
]}
|
|
gridGap={4}
|
|
>
|
|
{datasetTestItem?.results.map((item, index) => (
|
|
<Box
|
|
key={item.id}
|
|
pb={2}
|
|
borderRadius={'sm'}
|
|
border={theme.borders.base}
|
|
_notLast={{ mb: 2 }}
|
|
cursor={'pointer'}
|
|
title={'编辑'}
|
|
onClick={async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await getDatasetDataItemById(item.id);
|
|
|
|
if (!data) {
|
|
throw new Error(t('core.dataset.data.data is deleted'));
|
|
}
|
|
|
|
setEditInputData({
|
|
id: data.id,
|
|
collectionId: data.collectionId,
|
|
q: data.q,
|
|
a: data.a,
|
|
indexes: data.indexes
|
|
});
|
|
} catch (err) {
|
|
toast({
|
|
status: 'warning',
|
|
title: getErrText(err)
|
|
});
|
|
}
|
|
setLoading(false);
|
|
}}
|
|
>
|
|
<Flex p={3} alignItems={'center'} color={'myGray.500'}>
|
|
<Box
|
|
border={theme.borders.base}
|
|
px={2}
|
|
fontSize={'sm'}
|
|
mr={1}
|
|
borderRadius={'md'}
|
|
>
|
|
# {index + 1}
|
|
</Box>
|
|
<MyIcon name={'kbTest'} w={'14px'} />
|
|
<Progress
|
|
mx={2}
|
|
flex={'1 0 0'}
|
|
value={item.score * 100}
|
|
size="sm"
|
|
borderRadius={'20px'}
|
|
colorScheme="gray"
|
|
/>
|
|
<Box>{item.score.toFixed(4)}</Box>
|
|
</Flex>
|
|
<Box px={2} fontSize={'xs'} color={'myGray.600'} wordBreak={'break-word'}>
|
|
<Box>{item.q}</Box>
|
|
<Box>{item.a}</Box>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Grid>
|
|
</>
|
|
)}
|
|
</Box>
|
|
|
|
{!!editInputData && (
|
|
<InputDataModal
|
|
collectionId={editInputData.collectionId}
|
|
defaultValue={editInputData}
|
|
onClose={() => setEditInputData(undefined)}
|
|
onSuccess={(data) => {
|
|
if (datasetTestItem && editInputData.id) {
|
|
const newTestItem: SearchTestStoreItemType = {
|
|
...datasetTestItem,
|
|
results: datasetTestItem.results.map((item) =>
|
|
item.id === editInputData.id
|
|
? {
|
|
...item,
|
|
q: data.q || '',
|
|
a: data.a || ''
|
|
}
|
|
: item
|
|
)
|
|
};
|
|
updateDatasetItemById(newTestItem);
|
|
setDatasetTestItem(newTestItem);
|
|
}
|
|
|
|
setEditInputData(undefined);
|
|
}}
|
|
onDelete={() => {
|
|
if (datasetTestItem && editInputData.id) {
|
|
const newTestItem = {
|
|
...datasetTestItem,
|
|
results: datasetTestItem.results.filter((item) => item.id !== editInputData.id)
|
|
};
|
|
updateDatasetItemById(newTestItem);
|
|
setDatasetTestItem(newTestItem);
|
|
}
|
|
setEditInputData(undefined);
|
|
}}
|
|
/>
|
|
)}
|
|
{isOpenSelectMode && (
|
|
<DatasetParamsModal
|
|
searchMode={searchMode}
|
|
onClose={onCloseSelectMode}
|
|
onSuccess={(e) => {
|
|
setSearchMode(e.searchMode);
|
|
}}
|
|
/>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default Test;
|