V4.8.20 feature (#3686)
* Aiproxy (#3649) * model config * feat: model config ui * perf: rename variable * feat: custom request url * perf: model buffer * perf: init model * feat: json model config * auto login * fix: ts * update packages * package * fix: dockerfile * feat: usage filter & export & dashbord (#3538) * feat: usage filter & export & dashbord * adjust ui * fix tmb scroll * fix code & selecte all * merge * perf: usages list;perf: move components (#3654) * perf: usages list * team sub plan load * perf: usage dashboard code * perf: dashboard ui * perf: move components * add default model config (#3653) * 4.8.20 test (#3656) * provider * perf: model config * model perf (#3657) * fix: model * dataset quote * perf: model config * model tag * doubao model config * perf: config model * feat: model test * fix: POST 500 error on dingtalk bot (#3655) * feat: default model (#3662) * move model config * feat: default model * fix: false triggerd org selection (#3661) * export usage csv i18n (#3660) * export usage csv i18n * fix build * feat: markdown extension (#3663) * feat: markdown extension * media cros * rerank test * default price * perf: default model * fix: cannot custom provider * fix: default model select * update bg * perf: default model selector * fix: usage export * i18n * fix: rerank * update init extension * perf: ip limit check * doubao model order * web default modle * perf: tts selector * perf: tts error * qrcode package * reload buffer (#3665) * reload buffer * reload buffer * tts selector * fix: err tip (#3666) * fix: err tip * perf: training queue * doc * fix interactive edge (#3659) * fix interactive edge * fix * comment * add gemini model * fix: chat model select * perf: supplement assistant empty response (#3669) * perf: supplement assistant empty response * check array * perf: max_token count;feat: support resoner output;fix: member scroll (#3681) * perf: supplement assistant empty response * check array * perf: max_token count * feat: support resoner output * member scroll * update provider order * i18n * fix: stream response (#3682) * perf: supplement assistant empty response * check array * fix: stream response * fix: model config cannot set to null * fix: reasoning response (#3684) * perf: supplement assistant empty response * check array * fix: reasoning response * fix: reasoning response * doc (#3685) * perf: supplement assistant empty response * check array * doc * lock * animation * update doc * update compose * doc * doc --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com>
This commit is contained in:
458
projects/app/src/pageComponents/dataset/detail/Test.tsx
Normal file
458
projects/app/src/pageComponents/dataset/detail/Test.tsx
Normal file
@@ -0,0 +1,458 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Box, Textarea, Button, Flex, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { useSearchTestStore, SearchTestStoreItemType } from '@/web/core/dataset/store/searchTest';
|
||||
import { postSearchText } from '@/web/core/dataset/api';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SearchTestResponse } from '@/global/core/dataset/api';
|
||||
import {
|
||||
DatasetSearchModeEnum,
|
||||
DatasetSearchModeMap
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
|
||||
type FormType = {
|
||||
inputText: string;
|
||||
searchParams: {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
usingReRank?: boolean;
|
||||
datasetSearchUsingExtensionQuery?: boolean;
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { defaultModels } = useSystemStore();
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const { pushDatasetTestItem } = useSearchTestStore();
|
||||
const [inputType, setInputType] = useState<'text' | 'file'>('text');
|
||||
const [datasetTestItem, setDatasetTestItem] = useState<SearchTestStoreItemType>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
const { File, onOpen } = useSelectFile({
|
||||
fileType: '.csv',
|
||||
multiple: false
|
||||
});
|
||||
const [selectFile, setSelectFile] = useState<File>();
|
||||
|
||||
const { getValues, setValue, register, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
inputText: '',
|
||||
searchParams: {
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false,
|
||||
limit: 5000,
|
||||
similarity: 0,
|
||||
datasetSearchUsingExtensionQuery: false,
|
||||
datasetSearchExtensionModel: defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg: ''
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const searchModeData = DatasetSearchModeMap[getValues(`searchParams.searchMode`)];
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectMode,
|
||||
onOpen: onOpenSelectMode,
|
||||
onClose: onCloseSelectMode
|
||||
} = useDisclosure();
|
||||
|
||||
const { runAsync: onTextTest, loading: textTestIsLoading } = useRequest2(
|
||||
({ inputText, searchParams }: FormType) =>
|
||||
postSearchText({ datasetId, text: inputText.trim(), ...searchParams }),
|
||||
{
|
||||
onSuccess(res: SearchTestResponse) {
|
||||
if (!res || res.list.length === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:dataset.test.noResult')
|
||||
});
|
||||
}
|
||||
|
||||
const testItem: SearchTestStoreItemType = {
|
||||
id: nanoid(),
|
||||
datasetId,
|
||||
text: getValues('inputText').trim(),
|
||||
time: new Date(),
|
||||
results: res.list,
|
||||
duration: res.duration,
|
||||
searchMode: res.searchMode,
|
||||
usingReRank: res.usingReRank,
|
||||
limit: res.limit,
|
||||
similarity: res.similarity,
|
||||
queryExtensionModel: res.queryExtensionModel
|
||||
};
|
||||
pushDatasetTestItem(testItem);
|
||||
setDatasetTestItem(testItem);
|
||||
},
|
||||
onError(err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onSelectFile = async (files: File[]) => {
|
||||
const file = files[0];
|
||||
if (!file) return;
|
||||
setSelectFile(file);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDatasetTestItem(undefined);
|
||||
}, [datasetId]);
|
||||
|
||||
return (
|
||||
<Box h={'100%'} display={['block', 'flex']}>
|
||||
{/* left */}
|
||||
<Box
|
||||
h={['auto', '100%']}
|
||||
display={['block', 'flex']}
|
||||
flexDirection={'column'}
|
||||
flex={1}
|
||||
maxW={'500px'}
|
||||
py={4}
|
||||
>
|
||||
<Box
|
||||
border={'2px solid'}
|
||||
p={3}
|
||||
mx={4}
|
||||
borderRadius={'md'}
|
||||
{...(isFocus
|
||||
? {
|
||||
borderColor: 'primary.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
}
|
||||
: {
|
||||
borderColor: 'primary.300'
|
||||
})}
|
||||
>
|
||||
{/* header */}
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
||||
<MySelect<'text' | 'file'>
|
||||
size={'sm'}
|
||||
w={'150px'}
|
||||
list={[
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon mr={2} name={'text'} w={'14px'} color={'primary.600'} />
|
||||
<Box fontSize={'sm'} fontWeight={'bold'} flex={1}>
|
||||
{t('common:core.dataset.test.Test Text')}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: 'text'
|
||||
}
|
||||
// {
|
||||
// label: (
|
||||
// <Flex alignItems={'center'}>
|
||||
// <MyIcon mr={2} name={'file/csv'} w={'14px'} color={'primary.600'} />
|
||||
// <Box fontSize={'sm'} fontWeight={'bold'} flex={1}>
|
||||
// {t('common:core.dataset.test.Batch test')}
|
||||
// </Box>
|
||||
// </Flex>
|
||||
// ),
|
||||
// value: 'file'
|
||||
// }
|
||||
]}
|
||||
value={inputType}
|
||||
onchange={(e) => setInputType(e)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<MyIcon name={searchModeData.icon as any} w={'14px'} />}
|
||||
size={'sm'}
|
||||
onClick={onOpenSelectMode}
|
||||
>
|
||||
{t(searchModeData.title as any)}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Box h={'180px'}>
|
||||
{inputType === 'text' && (
|
||||
<Textarea
|
||||
h={'100%'}
|
||||
resize={'none'}
|
||||
variant={'unstyled'}
|
||||
maxLength={datasetDetail.vectorModel?.maxToken}
|
||||
placeholder={t('common:core.dataset.test.Test Text Placeholder')}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
{...register('inputText', {
|
||||
required: true,
|
||||
onBlur: () => {
|
||||
setIsFocus(false);
|
||||
}
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{inputType === 'file' && (
|
||||
<Box pt={5}>
|
||||
<Flex
|
||||
p={3}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
borderStyle={'dashed'}
|
||||
bg={'white'}
|
||||
cursor={'pointer'}
|
||||
justifyContent={'center'}
|
||||
_hover={{
|
||||
bg: 'primary.100',
|
||||
borderColor: 'primary.500',
|
||||
borderStyle: 'solid'
|
||||
}}
|
||||
onClick={onOpen}
|
||||
>
|
||||
<MyIcon mr={2} name={'file/csv'} w={'24px'} />
|
||||
<Box>
|
||||
{selectFile
|
||||
? selectFile.name
|
||||
: t('common:core.dataset.test.Batch test Placeholder')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={3} fontSize={'sm'}>
|
||||
{t('common:info.csv_message')}
|
||||
<Box
|
||||
as={'span'}
|
||||
color={'primary.600'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
fileDownload({
|
||||
text: `"问题"\n"问题1"\n"问题2"\n"问题3"`,
|
||||
type: 'text/csv',
|
||||
filename: 'Test Template'
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common:info.csv_download')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
isLoading={textTestIsLoading}
|
||||
isDisabled={inputType === 'file' && !selectFile}
|
||||
onClick={() => {
|
||||
if (inputType === 'text') {
|
||||
handleSubmit((data) => onTextTest(data))();
|
||||
} else {
|
||||
// handleSubmit((data) => onFileTest(data))();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('common:core.dataset.test.Test')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box mt={5} px={4} overflow={'overlay'} display={['none', 'block']}>
|
||||
<TestHistories
|
||||
datasetId={datasetId}
|
||||
datasetTestItem={datasetTestItem}
|
||||
setDatasetTestItem={setDatasetTestItem}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* result show */}
|
||||
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={'1 0 0'} bg={'white'}>
|
||||
<TestResults datasetTestItem={datasetTestItem} />
|
||||
</Box>
|
||||
|
||||
{isOpenSelectMode && (
|
||||
<DatasetParamsModal
|
||||
{...getValues('searchParams')}
|
||||
maxTokens={20000}
|
||||
onClose={onCloseSelectMode}
|
||||
onSuccess={(e) => {
|
||||
setValue('searchParams', {
|
||||
...getValues('searchParams'),
|
||||
...e
|
||||
});
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Test);
|
||||
|
||||
const TestHistories = React.memo(function TestHistories({
|
||||
datasetId,
|
||||
datasetTestItem,
|
||||
setDatasetTestItem
|
||||
}: {
|
||||
datasetId: string;
|
||||
datasetTestItem?: SearchTestStoreItemType;
|
||||
setDatasetTestItem: React.Dispatch<React.SetStateAction<SearchTestStoreItemType | undefined>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { datasetTestList, delDatasetTestItemById } = useSearchTestStore();
|
||||
|
||||
const testHistories = useMemo(
|
||||
() => datasetTestList.filter((item) => item.datasetId === datasetId),
|
||||
[datasetId, datasetTestList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'} color={'myGray.900'}>
|
||||
<MyIcon mr={2} name={'history'} w={'18px'} h={'18px'} color={'myGray.900'} />
|
||||
<Box fontSize={'md'}>{t('common:core.dataset.test.test history')}</Box>
|
||||
</Flex>
|
||||
<Box mt={2}>
|
||||
{testHistories.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
py={2}
|
||||
px={3}
|
||||
alignItems={'center'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'md'}
|
||||
_notLast={{
|
||||
mb: 2
|
||||
}}
|
||||
_hover={{
|
||||
borderColor: 'primary.300',
|
||||
boxShadow: '1',
|
||||
'& .delete': {
|
||||
display: 'block'
|
||||
},
|
||||
'& .time': {
|
||||
display: 'none'
|
||||
}
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
fontSize={'sm'}
|
||||
{...(item.id === datasetTestItem?.id && {
|
||||
bg: 'primary.50'
|
||||
})}
|
||||
onClick={() => setDatasetTestItem(item)}
|
||||
>
|
||||
<Box flex={'0 0 auto'} mr={2}>
|
||||
{DatasetSearchModeMap[item.searchMode] ? (
|
||||
<Flex alignItems={'center'} fontWeight={'500'} color={'myGray.500'}>
|
||||
<MyIcon
|
||||
name={DatasetSearchModeMap[item.searchMode].icon as any}
|
||||
w={'12px'}
|
||||
mr={'1px'}
|
||||
/>
|
||||
{t(DatasetSearchModeMap[item.searchMode].title as any)}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Box>
|
||||
<Box flex={1} mr={2} wordBreak={'break-all'} fontWeight={'400'}>
|
||||
{item.text}
|
||||
</Box>
|
||||
<Box className="time" flex={'0 0 auto'} fontSize={'xs'} color={'myGray.500'}>
|
||||
{t(formatTimeToChatTime(item.time) as any).replace('#', ':')}
|
||||
</Box>
|
||||
<MyTooltip label={t('common:core.dataset.test.delete test history')}>
|
||||
<Box className="delete" display={'none'} w={'0.8rem'} h={'0.8rem'} ml={1}>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'0.8rem'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
delDatasetTestItemById(item.id);
|
||||
datasetTestItem?.id === item.id && setDatasetTestItem(undefined);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const TestResults = React.memo(function TestResults({
|
||||
datasetTestItem
|
||||
}: {
|
||||
datasetTestItem?: SearchTestStoreItemType;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!datasetTestItem?.results || datasetTestItem.results.length === 0 ? (
|
||||
<EmptyTip text={t('common:core.dataset.test.test result placeholder')} mt={[10, '20vh']} />
|
||||
) : (
|
||||
<>
|
||||
<Flex fontSize={'md'} color={'myGray.900'} alignItems={'center'}>
|
||||
<MyIcon name={'common/paramsLight'} w={'18px'} mr={2} />
|
||||
{t('common:core.dataset.test.Test params')}
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<SearchParamsTip
|
||||
searchMode={datasetTestItem.searchMode}
|
||||
similarity={datasetTestItem.similarity}
|
||||
limit={datasetTestItem.limit}
|
||||
usingReRank={datasetTestItem.usingReRank}
|
||||
datasetSearchUsingExtensionQuery={!!datasetTestItem.queryExtensionModel}
|
||||
queryExtensionModel={datasetTestItem.queryExtensionModel}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Flex mt={5} mb={3} alignItems={'center'}>
|
||||
<Flex fontSize={'md'} color={'myGray.900'} alignItems={'center'}>
|
||||
<MyIcon name={'common/resultLight'} w={'18px'} mr={2} />
|
||||
{t('common:core.dataset.test.Test Result')}
|
||||
</Flex>
|
||||
<QuestionTip ml={1} label={t('common:core.dataset.test.test result tip')} />
|
||||
<Box ml={2}>({datasetTestItem.duration})</Box>
|
||||
</Flex>
|
||||
<Box mt={1} gap={4}>
|
||||
{datasetTestItem?.results.map((item, index) => (
|
||||
<Box key={item.id} p={3} borderRadius={'lg'} bg={'myGray.100'} _notLast={{ mb: 2 }}>
|
||||
<QuoteItem quoteItem={item} canViewSource />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user