v4.6.6-search test adapt diff search mode (#685)

This commit is contained in:
Archer
2024-01-03 15:40:03 +08:00
committed by GitHub
parent c766a0ed8a
commit 13b10720ac
57 changed files with 1101 additions and 612 deletions

View File

@@ -1,4 +1,4 @@
### Fast GPT V4.6.5
### Fast GPT V4.6.6
1. 新增 - [问题补全模块](https://doc.fastgpt.in/docs/workflow/modules/coreferenceresolution/)
2. 新增 - [文本编辑模块](https://doc.fastgpt.in/docs/workflow/modules/text_editor/)

View File

@@ -296,6 +296,7 @@
"Select Image": "Select Image",
"Send Message": "Send Message",
"Speaking": "I'm listening...",
"Start Chat": "Start Chat",
"Stop Speak": "Stop Speak",
"Type a message": "Input problem",
"error": {
@@ -379,6 +380,7 @@
"Name": "Name",
"Quote Length": "Quote Length",
"Read Dataset": "Read Dataset",
"Search score tip": "{{scoreText}}Here are the rankings and scores:\n----\n{{detailScore}}",
"Set Empty Result Tip": ",Response empty text",
"Set Website Config": "Configuring Website",
"Similarity": "Similarity",
@@ -435,6 +437,7 @@
"Too Long": "Content is too long",
"Total Amount": "{{total}} Chunks",
"data is deleted": "Data is deleted",
"get data error": "Get data error",
"id": "Data ID"
},
"error": {
@@ -482,6 +485,7 @@
"link": "Link",
"search": {
"Dataset Search Params": "Dataset Search Params",
"Embedding score": "Embedding score",
"Empty result response": "Empty Response",
"Empty result response Tips": "If you fill in the content, if no suitable content is found, you will directly reply to the content.",
"Max Tokens": "Max Tokens",
@@ -489,8 +493,14 @@
"Min Similarity": "Min Similarity",
"Min Similarity Tips": "The similarity of different index models is different, please use the search test to select the appropriate value",
"Params Setting": "Params Setting",
"Rank": "Rank",
"Rank Tip": "Ranking in all data",
"ReRank": "ReRank",
"ReRank desc": "The rearrangement model is used for secondary ranking to enhance the overall ranking.",
"Read score": "Read score",
"Rerank score": "ReRank score",
"Score": "Score",
"Search type": "Type",
"Top K": "Top K",
"mode": {
"embedding": "Vector search",
@@ -500,6 +510,12 @@
"mixedRecall": "Mixedrecall",
"mixedRecall desc": "Returns the combined results of vector and full-text searches, sorted using the RRF algorithm."
},
"score": {
"embedding": "Embedding",
"fullText": "Full text",
"reRank": "ReRank",
"rrf": "RRF Merge"
},
"search mode": "Search Mode"
},
"status": {
@@ -507,11 +523,15 @@
"syncing": "Syncing"
},
"test": {
"Batch test": "Batch test",
"Batch test Placeholder": "Select one csv file",
"Search Test": "Search Test",
"Test": "Start",
"Test File": "",
"Test Result": "Results",
"Test Text": "Text",
"Test Text Placeholder": "Enter the text you want to test",
"Test params": "Search Params",
"delete test history": "Delete the test result",
"test history": "Test History",
"test result placeholder": "The test results will be presented here",

View File

@@ -296,6 +296,7 @@
"Select Image": "选择图片",
"Send Message": "发送",
"Speaking": "我在听,请说...",
"Start Chat": "开始对话",
"Stop Speak": "停止录音",
"Type a message": "输入问题",
"error": {
@@ -379,6 +380,7 @@
"Name": "知识库名称",
"Quote Length": "引用内容长度",
"Read Dataset": "查看知识库详情",
"Search score tip": "{{scoreText}}下面是详细排名和得分情况:\n----\n{{detailScore}}",
"Set Empty Result Tip": ",未搜索到内容时回复指定内容",
"Set Website Config": "开始配置网站信息",
"Similarity": "相关度",
@@ -435,6 +437,7 @@
"Too Long": "总长度超长了",
"Total Amount": "{{total}} 组",
"data is deleted": "该数据已被删除",
"get data error": "获取数据异常",
"id": "数据ID"
},
"error": {
@@ -482,6 +485,7 @@
"link": "链接",
"search": {
"Dataset Search Params": "搜索参数",
"Embedding score": "语意检索得分",
"Empty result response": "空搜索回复",
"Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。",
"Max Tokens": "引用上限",
@@ -489,8 +493,14 @@
"Min Similarity": "最低相关度",
"Min Similarity Tips": "不同索引模型的相关度有区别,请通过搜索测试来选择合适的数值,使用 ReRank 时,相关度可能会很低。",
"Params Setting": "搜索参数设置",
"Rank": "排名",
"Rank Tip": "在所有数据中的排名",
"ReRank": "结果重排",
"ReRank desc": "使用重排模型来进行二次排序,可增强综合排名。",
"Read score": "查看得分",
"Rerank score": "结果重排得分",
"Score": "得分",
"Search type": "类型",
"Top K": "单次搜索上限",
"mode": {
"embedding": "语义检索",
@@ -500,6 +510,12 @@
"mixedRecall": "混合检索",
"mixedRecall desc": "使用向量检索与全文检索的综合结果返回使用RRF算法进行排序。"
},
"score": {
"embedding": "语义检索",
"fullText": "全文检索",
"reRank": "结果重排",
"rrf": "RRF 合并"
},
"search mode": "搜索模式"
},
"status": {
@@ -507,11 +523,15 @@
"syncing": "同步中"
},
"test": {
"Batch test": "批量测试",
"Batch test Placeholder": "选择一个 Csv 文件",
"Search Test": "搜索测试",
"Test": "测试",
"Test File": "批量测试",
"Test Result": "测试结果",
"Test Text": "测试文本",
"Test Text": "单个文本测试",
"Test Text Placeholder": "输入需要测试的文本",
"Test params": "测试参数",
"delete test history": "删除该测试结果",
"test history": "测试历史",
"test result placeholder": "测试结果将在这里展示",
@@ -847,7 +867,7 @@
"Response Detail": "返回详情",
"Response Detail tips": "是否需要返回详情(引用内容,调用时间等,不会返回预设提示词和完整上下文)",
"token auth": "身份验证",
"token auth Tips": "身份校验服务器地址,如填写该值,每次对话前都会指定服务器发送一个请求,进行身份校验",
"token auth Tips": "身份校验服务器地址,如填写该值,每次对话前都会指定服务器发送一个请求,进行身份校验",
"token auth use cases": "查看身份验证使用说明"
},
"permission": {

View File

@@ -32,7 +32,7 @@ const ContextModal = ({
<Box
key={i}
p={2}
borderRadius={'lg'}
borderRadius={'md'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}

View File

@@ -9,12 +9,16 @@ const FeedbackModal = ({
appId,
chatId,
chatItemId,
shareId,
outLinkUid,
onSuccess,
onClose
}: {
appId: string;
chatId: string;
chatItemId: string;
shareId?: string;
outLinkUid?: string;
onSuccess: (e: string) => void;
onClose: () => void;
}) => {
@@ -28,6 +32,8 @@ const FeedbackModal = ({
appId,
chatId,
chatItemId,
shareId,
outLinkUid,
userBadFeedback: val
});
},

View File

@@ -1,31 +1,38 @@
import React, { useCallback, useState } from 'react';
import { ModalBody, Box, useTheme, Flex, Progress, Link } from '@chakra-ui/react';
import { getDatasetDataItemById } from '@/web/core/dataset/api';
import { useLoading } from '@/web/common/hooks/useLoading';
import { useToast } from '@/web/common/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyIcon from '@/components/Icon';
import InputDataModal, {
RawSourceText,
type InputDataType
} from '@/pages/dataset/detail/components/InputDataModal';
import React, { useMemo, useState } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useTranslation } from 'next-i18next';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import MyTooltip from '../MyTooltip';
import NextLink from 'next/link';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import QuoteItem from '../core/dataset/QuoteItem';
import { RawSourceText } from '@/pages/dataset/detail/components/InputDataModal';
const QuoteModal = ({
rawSearch = [],
onClose,
isShare
isShare,
metadata
}: {
rawSearch: SearchDataResponseItemType[];
onClose: () => void;
isShare: boolean;
metadata?: {
collectionId: string;
sourceId?: string;
sourceName: string;
};
}) => {
const { t } = useTranslation();
const filterResults = useMemo(
() =>
metadata
? rawSearch.filter(
(item) =>
item.collectionId === metadata.collectionId && item.sourceId === metadata.sourceId
)
: rawSearch,
[metadata, rawSearch]
);
return (
<>
@@ -35,18 +42,22 @@ const QuoteModal = ({
h={['90vh', '80vh']}
isCentered
minW={['90vw', '600px']}
iconSrc="/imgs/modal/quote.svg"
iconSrc={!!metadata ? undefined : '/imgs/modal/quote.svg'}
title={
<Box>
{t('core.chat.Quote Amount', { amount: rawSearch.length })}
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{metadata ? (
<RawSourceText {...metadata} canView={false} />
) : (
<>{t('core.chat.Quote Amount', { amount: rawSearch.length })}</>
)}
<Box fontSize={'xs'} color={'myGray.500'} fontWeight={'normal'}>
{t('core.chat.quote.Quote Tip')}
</Box>
</Box>
}
>
<ModalBody whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
<QuoteList rawSearch={rawSearch} isShare={isShare} />
<ModalBody>
<QuoteList rawSearch={filterResults} isShare={isShare} />
</ModalBody>
</MyModal>
</>
@@ -62,38 +73,7 @@ export const QuoteList = React.memo(function QuoteList({
rawSearch: SearchDataResponseItemType[];
isShare: boolean;
}) {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const theme = useTheme();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
/**
* click edit, get new DataItem
*/
const onclickEdit = useCallback(
async (item: InputDataType) => {
if (!item.id) return;
try {
setIsLoading(true);
const data = await getDatasetDataItemById(item.id);
if (!data) {
throw new Error('该数据已被删除');
}
setEditInputData(data);
} catch (err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
setIsLoading(false);
},
[setIsLoading, toast]
);
return (
<>
@@ -102,113 +82,15 @@ export const QuoteList = React.memo(function QuoteList({
key={i}
flex={'1 0 0'}
p={2}
borderRadius={'lg'}
borderRadius={'sm'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
overflow={'hidden'}
_hover={{ '& .hover-data': { display: 'flex' } }}
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
>
<Flex alignItems={'flex-end'} mb={3} fontSize={'sm'}>
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={item.sourceName}
sourceId={item.sourceId}
canView={!isShare}
/>
<Box flex={1} />
{!isShare && (
<Link
as={NextLink}
className="hover-data"
display={'none'}
alignItems={'center'}
color={'primary.500'}
href={`/dataset/detail?datasetId=${item.datasetId}&currentTab=dataCard&collectionId=${item.collectionId}`}
>
{t('core.dataset.Go Dataset')}
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
</Link>
)}
</Flex>
<Box color={'black'}>{item.q}</Box>
<Box color={'myGray.600'}>{item.a}</Box>
{!isShare && (
<Flex alignItems={'center'} fontSize={'sm'} mt={3} gap={4} color={'myGray.500'}>
{isPc && (
<MyTooltip label={t('core.dataset.data.id')}>
<Flex border={theme.borders.base} py={'1px'} px={3} borderRadius={'3px'}>
# {item.id}
</Flex>
</MyTooltip>
)}
<MyTooltip label={t('core.dataset.Quote Length')}>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{item.q.length + (item.a?.length || 0)}
</Flex>
</MyTooltip>
{/* {!isShare && item.score && (
<MyTooltip label={t('core.dataset.Similarity')}>
<Flex alignItems={'center'}>
<MyIcon name={'kbTest'} w={'12px'} />
<Progress
mx={2}
w={['60px', '90px']}
value={item.score * 100}
size="sm"
borderRadius={'20px'}
colorScheme="myGray"
border={theme.borders.base}
/>
<Box>{item.score.toFixed(4)}</Box>
</Flex>
</MyTooltip>
)} */}
<Box flex={1} />
{item.id && (
<MyTooltip label={t('core.dataset.data.Edit')}>
<Box
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={['16px', '18px']}
h={['16px', '18px']}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'primary.600'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
</MyTooltip>
)}
</Flex>
)}
<QuoteItem quoteItem={item} canViewSource={!isShare} linkToDataset={!isShare} />
</Box>
))}
{editInputData && editInputData.id && (
<InputDataModal
onClose={() => setEditInputData(undefined)}
onSuccess={() => {
console.log('更新引用成功');
}}
onDelete={() => {
console.log('删除引用成功');
}}
defaultValue={editInputData}
collectionId={editInputData.collectionId}
/>
)}
<Loading fixed={false} />
</>
);
});

View File

@@ -11,8 +11,6 @@ import MyTooltip from '../MyTooltip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import ChatBoxDivider from '@/components/core/chat/Divider';
import MyIcon from '../Icon';
import { getFileAndOpen } from '@/web/core/dataset/utils';
import { strIsLink } from '@fastgpt/global/common/string/tools';
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
@@ -29,7 +27,14 @@ const ResponseTags = ({
const theme = useTheme();
const { isPc } = useSystemStore();
const { t } = useTranslation();
const [quoteModalData, setQuoteModalData] = useState<SearchDataResponseItemType[]>();
const [quoteModalData, setQuoteModalData] = useState<{
rawSearch: SearchDataResponseItemType[];
metadata?: {
collectionId: string;
sourceId?: string;
sourceName: string;
};
}>();
const [contextModalData, setContextModalData] = useState<ChatItemType[]>();
const {
isOpen: isOpenWholeModal,
@@ -52,8 +57,8 @@ const ResponseTags = ({
.filter(Boolean) as SearchDataResponseItemType[];
const sourceList = quoteList.reduce(
(acc: Record<string, SearchDataResponseItemType[]>, cur) => {
if (!acc[cur.sourceName]) {
acc[cur.sourceName] = [cur];
if (!acc[cur.collectionId]) {
acc[cur.collectionId] = [cur];
}
return acc;
},
@@ -70,7 +75,8 @@ const ResponseTags = ({
sourceName: item.sourceName,
sourceId: item.sourceId,
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }),
canReadQuote: !isShare || strIsLink(item.sourceId)
canReadQuote: !isShare || strIsLink(item.sourceId),
collectionId: item.collectionId
})),
historyPreview: chatData?.historyPreview,
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
@@ -89,88 +95,52 @@ const ResponseTags = ({
<ChatBoxDivider icon="core/chat/quoteFill" text={t('chat.Quote')} />
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
{sourceList.map((item) => (
<Flex
key={item.sourceName}
alignItems={'center'}
flexWrap={'wrap'}
fontSize={'sm'}
border={theme.borders.sm}
py={1}
px={2}
borderRadius={'md'}
_hover={{
'.controller': {
display: 'flex'
}
}}
overflow={'hidden'}
position={'relative'}
>
<Image src={item.icon} alt={''} mr={1} w={'12px'} />
<Box className="textEllipsis" flex={'1 0 0'}>
{item.sourceName}
</Box>
<Box
className="controller"
display={'none'}
pr={2}
position={'absolute'}
right={0}
left={0}
justifyContent={'flex-end'}
<MyTooltip key={item.sourceName} label={t('core.chat.quote.Read Quote')}>
<Flex
alignItems={'center'}
h={'100%'}
lineHeight={0}
bg={`linear-gradient(to left, white,white ${
item.sourceId ? '60px' : '30px'
}, rgba(255,255,255,0) 80%)`}
fontSize={'sm'}
border={theme.borders.sm}
py={1}
px={2}
borderRadius={'sm'}
_hover={{
'.controller': {
display: 'flex'
}
}}
overflow={'hidden'}
position={'relative'}
cursor={'pointer'}
onClick={(e) => {
e.stopPropagation();
setQuoteModalData({
rawSearch: quoteList,
metadata: {
collectionId: item.collectionId,
sourceId: item.sourceId,
sourceName: item.sourceName
}
});
}}
>
<MyTooltip label={t('core.chat.quote.Read Quote')}>
<MyIcon
name="common/viewLight"
w={'14px'}
cursor={'pointer'}
_hover={{
color: 'green.600'
}}
onClick={(e) => {
e.stopPropagation();
setQuoteModalData(quoteList);
}}
/>
</MyTooltip>
{item.sourceId && item.canReadQuote && (
<MyTooltip label={t('core.chat.quote.Read Source')}>
<MyIcon
ml={4}
name="common/routePushLight"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={async (e) => {
e.stopPropagation();
if (!item.sourceId) return;
await getFileAndOpen(item.sourceId);
}}
/>
</MyTooltip>
)}
</Box>
</Flex>
<Image src={item.icon} alt={''} mr={1} flexShrink={0} w={'12px'} />
<Box className="textEllipsis3" wordBreak={'break-all'} flex={'1 0 0'}>
{item.sourceName}
</Box>
</Flex>
</MyTooltip>
))}
</Flex>
</>
)}
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
<Flex alignItems={'center'} mt={3} flexWrap={'wrap'}>
{quoteList.length > 0 && (
<MyTooltip label="查看引用">
<Tag
colorSchema="blue"
cursor={'pointer'}
{...TagStyles}
onClick={() => setQuoteModalData(quoteList)}
onClick={() => setQuoteModalData({ rawSearch: quoteList })}
>
{quoteList.length}
</Tag>
@@ -213,7 +183,7 @@ const ResponseTags = ({
{!!quoteModalData && (
<QuoteModal
rawSearch={quoteModalData}
{...quoteModalData}
isShare={isShare}
onClose={() => setQuoteModalData(undefined)}
/>

View File

@@ -632,14 +632,13 @@ const ChatBox = (
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
setVariableInputFinish(true);
})}
>
{'开始对话'}
{t('core.chat.Start Chat')}
</Button>
)}
</Card>
@@ -952,6 +951,8 @@ const ChatBox = (
appId={appId}
chatId={chatId}
chatItemId={feedbackId}
shareId={shareId}
outLinkUid={outLinkUid}
onClose={() => setFeedbackId(undefined)}
onSuccess={(content: string) => {
setChatHistory((state) =>
@@ -1142,7 +1143,7 @@ function ChatAvatar({ src, type }: { src?: string; type: 'Human' | 'AI' }) {
w={['28px', '34px']}
h={['28px', '34px']}
p={'2px'}
borderRadius={'lg'}
borderRadius={'sm'}
border={theme.borders.base}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
bg={type === 'Human' ? 'white' : 'primary.50'}
@@ -1208,7 +1209,7 @@ function ChatController({
cursor: 'pointer',
p: 1,
bg: 'white',
borderRadius: 'lg',
borderRadius: 'md',
boxShadow: '0 0 5px rgba(0,0,0,0.1)',
border: theme.borders.base,
mr: 3

View File

@@ -9,7 +9,6 @@ import {
Skeleton,
useDisclosure
} from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
const MdImage = ({ src }: { src?: string }) => {
const [isLoading, setIsLoading] = useState(true);
@@ -34,6 +33,7 @@ const MdImage = ({ src }: { src?: string }) => {
cursor={succeed ? 'pointer' : 'default'}
loading="eager"
objectFit={'contain'}
referrerPolicy="no-referrer"
onLoad={() => {
setIsLoading(false);
setSucceed(true);
@@ -53,6 +53,7 @@ const MdImage = ({ src }: { src?: string }) => {
alt={''}
w={'100%'}
maxH={'80vh'}
referrerPolicy="no-referrer"
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
objectFit={'contain'}

View File

@@ -39,7 +39,7 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
px={2}
lineHeight={1}
py={1}
borderRadius={'md'}
borderRadius={'sm'}
fontSize={'xs'}
alignItems={'center'}
{...theme}

View File

@@ -77,7 +77,7 @@ const Editor = React.memo(function Editor({
return (
<Box h={'100%'} w={'100%'} position={'relative'}>
<Textarea ref={textareaRef} textAlign={'justify'} maxW={'100%'} {...props} />
<Textarea ref={textareaRef} maxW={'100%'} {...props} />
{onOpenModal && (
<Box
zIndex={1}

View File

@@ -0,0 +1,223 @@
import React, { useMemo, useState } from 'react';
import { Box, Flex, Link, Progress, useTheme } from '@chakra-ui/react';
import {
type InputDataType,
RawSourceText
} from '@/pages/dataset/detail/components/InputDataModal';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type.d';
import NextLink from 'next/link';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import dynamic from 'next/dynamic';
import MyBox from '@/components/common/MyBox';
import { getDatasetDataItemById } from '@/web/core/dataset/api';
import { useRequest } from '@/web/common/hooks/useRequest';
import { DatasetDataItemType } from '@fastgpt/global/core/dataset/type';
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constant';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
const QuoteItem = ({
quoteItem,
canViewSource,
linkToDataset
}: {
quoteItem: SearchDataResponseItemType;
canViewSource?: boolean;
linkToDataset?: boolean;
}) => {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const theme = useTheme();
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
const { mutate: onclickEdit, isLoading } = useRequest({
mutationFn: async (id: string) => {
return getDatasetDataItemById(id);
},
onSuccess(data: DatasetDataItemType) {
setEditInputData(data);
},
errorToast: t('core.dataset.data.get data error')
});
const rank = useMemo(() => {
if (quoteItem.score.length === 1) {
return quoteItem.score[0].index;
}
const rrf = quoteItem.score?.find((item) => item.type === SearchScoreTypeEnum.rrf);
if (rrf) return rrf.index;
return 0;
}, [quoteItem.score]);
const score = useMemo(() => {
let searchScore: number | undefined = undefined;
let text = '';
const reRankScore = quoteItem.score?.find((item) => item.type === SearchScoreTypeEnum.reRank);
if (reRankScore) {
searchScore = reRankScore.value;
text = t('core.dataset.search.Rerank score');
}
const embScore = quoteItem.score?.find((item) => item.type === SearchScoreTypeEnum.embedding);
if (embScore && quoteItem.score.length === 1) {
searchScore = embScore.value;
text = t('core.dataset.search.Embedding score');
}
const detailScore = (() => {
if (Array.isArray(quoteItem.score)) {
return quoteItem.score
.map(
(item) =>
`${t('core.dataset.search.Search type')}: ${t(SearchScoreTypeMap[item.type]?.label)}
${t('core.dataset.search.Rank')}: ${item.index + 1}
${t('core.dataset.search.Score')}: ${item.value.toFixed(4)}`
)
.join('\n----\n');
}
return 'null';
})();
return {
value: searchScore,
tip: t('core.dataset.Search score tip', {
scoreText: text ? `${text}\n` : text,
detailScore
})
};
}, [quoteItem.score, t]);
return (
<>
<MyBox
isLoading={isLoading}
position={'relative'}
overflow={'hidden'}
fontSize={'sm'}
whiteSpace={'pre-wrap'}
_hover={{ '& .hover-data': { display: 'flex' } }}
>
<Flex alignItems={'flex-end'} mb={3}>
{rank !== undefined && (
<MyTooltip label={t('core.dataset.search.Rank Tip')}>
<Box px={2} py={'3px'} mr={3} bg={'myGray.200'} borderRadius={'md'}>
# {rank + 1}
</Box>
</MyTooltip>
)}
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={quoteItem.sourceName}
sourceId={quoteItem.sourceId}
canView={canViewSource}
/>
<Box flex={1} />
{linkToDataset && (
<Link
as={NextLink}
className="hover-data"
display={'none'}
alignItems={'center'}
color={'primary.500'}
href={`/dataset/detail?datasetId=${quoteItem.datasetId}&currentTab=dataCard&collectionId=${quoteItem.collectionId}`}
>
{t('core.dataset.Go Dataset')}
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
</Link>
)}
</Flex>
<Box color={'black'}>{quoteItem.q}</Box>
<Box color={'myGray.600'}>{quoteItem.a}</Box>
{canViewSource && (
<Flex alignItems={'center'} mt={3} gap={4} color={'myGray.500'} fontSize={'xs'}>
{isPc && (
<Flex border={theme.borders.base} px={3} borderRadius={'xs'} lineHeight={'16px'}>
ID: {quoteItem.id}
</Flex>
)}
<MyTooltip label={t('core.dataset.Quote Length')}>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{quoteItem.q.length + (quoteItem.a?.length || 0)}
</Flex>
</MyTooltip>
{canViewSource && score && (
<MyTooltip label={score.tip}>
<Flex alignItems={'center'}>
<MyIcon name={'kbTest'} w={'12px'} />
{score.value ? (
<>
<Progress
mx={2}
w={['60px', '90px']}
value={score?.value * 100}
size="sm"
borderRadius={'20px'}
colorScheme="myGray"
border={theme.borders.base}
/>
<Box>{score?.value.toFixed(4)}</Box>
</>
) : (
<Box ml={1} cursor={'pointer'}>
{t('core.dataset.search.Read score')}
</Box>
)}
</Flex>
</MyTooltip>
)}
<Box flex={1} />
{quoteItem.id && (
<MyTooltip label={t('core.dataset.data.Edit')}>
<Box
className="hover-data"
display={['flex', 'none']}
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={['16px', '18px']}
h={['16px', '18px']}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'primary.600'
}}
onClick={() => onclickEdit(quoteItem.id)}
/>
</Box>
</MyTooltip>
)}
</Flex>
)}
</MyBox>
{editInputData && editInputData.id && (
<InputDataModal
onClose={() => setEditInputData(undefined)}
onSuccess={() => {
console.log('更新引用成功');
}}
onDelete={() => {
console.log('删除引用成功');
}}
defaultValue={editInputData}
collectionId={editInputData.collectionId}
/>
)}
</>
);
};
export default React.memo(QuoteItem);

View File

@@ -128,7 +128,7 @@ const VariableEdit = ({
</Flex>
</Flex>
{formatVariables.length > 0 && (
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
<TableContainer>
<Table bg={'white'}>
<Thead>
@@ -266,7 +266,7 @@ const VariableEdit = ({
w={'16px'}
cursor={'pointer'}
p={2}
borderRadius={'lg'}
borderRadius={'md'}
_hover={{ bg: 'red.100' }}
onClick={() => removeEnums(i)}
/>

View File

@@ -7,6 +7,7 @@ import dynamic from 'next/dynamic';
import InputLabel from './Label';
import type { RenderInputProps } from './type.d';
import { getFlowStore, type useFlowProviderStoreType } from '../../../FlowProvider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const RenderList: {
types: `${FlowNodeInputTypeEnum}`[];
@@ -65,6 +66,7 @@ const RenderList: {
Component: dynamic(() => import('./templates/AddInputParam'))
}
];
const UserChatInput = dynamic(() => import('./templates/UserChatInput'));
type Props = {
flowInputList: FlowNodeInputItemType[];
@@ -124,18 +126,23 @@ const RenderInput = ({ flowInputList, moduleId, CustomComponent = {} }: Props) =
})();
return (
input.type !== FlowNodeInputTypeEnum.hidden && (
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
{!!input.label && (
<InputLabel moduleId={moduleId} inputKey={input.key} mode={mode} {...input} />
)}
{!!RenderComponent && (
<Box mt={2} className={'nodrag'}>
{RenderComponent}
</Box>
)}
</Box>
)
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
{input.key === ModuleInputKeyEnum.userChatInput && (
<UserChatInput inputs={filterInputs} item={input} moduleId={moduleId} />
)}
{input.type !== FlowNodeInputTypeEnum.hidden && (
<>
{!!input.label && (
<InputLabel moduleId={moduleId} inputKey={input.key} mode={mode} {...input} />
)}
{!!RenderComponent && (
<Box mt={2} className={'nodrag'}>
{RenderComponent}
</Box>
)}
</>
)}
</Box>
);
})}
</>

View File

@@ -0,0 +1,41 @@
import React from 'react';
import type { RenderInputProps } from '../type';
import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import TargetHandle from '../../TargetHandle';
import SourceHandle from '../../SourceHandle';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
const UserChatInput = ({ item }: RenderInputProps) => {
const { t } = useTranslation();
return (
<Flex
className="nodrag"
cursor={'default'}
alignItems={'center'}
justifyContent={'space-between'}
position={'relative'}
>
<Box position={'relative'}>
<TargetHandle handleKey={ModuleInputKeyEnum.userChatInput} valueType={item.valueType} />
{t('core.module.input.label.user question')}
<Box
position={'absolute'}
top={'-2px'}
right={'-8px'}
color={'red.500'}
fontWeight={'bold'}
>
*
</Box>
</Box>
<Box position={'relative'}>
{t('core.module.input.label.user question')}
<SourceHandle handleKey={ModuleOutputKeyEnum.userChatInput} valueType={item.valueType} />
</Box>
</Flex>
);
};
export default React.memo(UserChatInput);

View File

@@ -59,8 +59,13 @@ export type SearchTestProps = {
limit?: number;
searchMode?: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
similarity?: number;
};
export type SearchTestResponse = {
list: SearchDataResponseItemType[];
duration: string;
limit: number;
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
similarity: number;
};

View File

@@ -36,11 +36,7 @@ export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录>
</对话记录>
`;
export const Prompt_CQJson = `我会给你几个问题类型,请参考额外的背景知识(可能为空)和对话内容,判断我本次问题类型,并返回对应类型的 ID格式为 JSON 字符串:
"""
'{"问题类型":"类型的 ID"}'
"""
export const Prompt_CQJson = `我会给你几个问题类型,请参考背景知识(可能为空)和对话记录,判断我本次问题”的类型,并返回一个问题“类型ID”:
<问题类型>
{{typeList}}
</问题类型>
@@ -49,9 +45,13 @@ export const Prompt_CQJson = `我会给你几个问题类型,请参考额外
{{systemPrompt}}
</背景知识>
<对话内容>
{{text}}
</对话内容>
<对话记录>
{{history}}
</对话记录>
Human"{{question}}"
类型ID=
`;
export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题引导我继续提问。问题的长度应小于20个字符按 JSON 格式返回: ["问题1", "问题2", "问题3"]`;

View File

@@ -14,7 +14,14 @@ import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryEx
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { datasetId, text, limit = 20, searchMode, usingReRank } = req.body as SearchTestProps;
const {
datasetId,
text,
limit = 1500,
similarity,
searchMode,
usingReRank
} = req.body as SearchTestProps;
if (!datasetId || !text) {
throw new Error('缺少参数');
@@ -40,11 +47,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// model: global.chatModels[0].model
// });
const { searchRes, tokens } = await searchDatasetData({
const { searchRes, tokens, ...result } = await searchDatasetData({
rawQuery: text,
queries: [text],
model: dataset.vectorModel,
limit: Math.min(limit * 800, 30000),
limit: Math.min(limit, 20000),
similarity,
datasetIds: [datasetId],
searchMode,
usingReRank
@@ -68,7 +76,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
jsonRes<SearchTestResponse>(res, {
data: {
list: searchRes,
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
...result
}
});
} catch (err) {

View File

@@ -133,7 +133,7 @@ const InfoModal = ({
w={['26px', '34px']}
h={['26px', '34px']}
cursor={'pointer'}
borderRadius={'lg'}
borderRadius={'md'}
mr={4}
title={'点击切换头像'}
onClick={() => onOpenSelectFile()}

View File

@@ -128,7 +128,7 @@ const Logs = ({ appId }: { appId: string }) => {
py={1}
alignItems={'center'}
justifyContent={'center'}
borderRadius={'lg'}
borderRadius={'md'}
fontWeight={'bold'}
>
<MyIcon
@@ -148,7 +148,7 @@ const Logs = ({ appId }: { appId: string }) => {
py={1}
alignItems={'center'}
justifyContent={'center'}
borderRadius={'lg'}
borderRadius={'md'}
fontWeight={'bold'}
>
<MyIcon

View File

@@ -20,7 +20,7 @@ const OutLink = ({ appId }: { appId: string }) => {
</Box>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 360px))']}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 400px))']}
iconSize={'20px'}
list={[
{

View File

@@ -597,7 +597,7 @@ function Settings({ appId }: { appId: string }) {
<Box
borderWidth={'1px'}
borderColor={'primary.1'}
borderRadius={'lg'}
borderRadius={'md'}
mt={2}
px={5}
py={4}

View File

@@ -108,7 +108,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
borderRight={theme.borders.base}
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'md'} />
<Box ml={2} fontWeight={'bold'}>
{appDetail.name}
</Box>

View File

@@ -99,7 +99,7 @@ const MyApps = () => {
borderWidth={'1.5px'}
borderColor={'borderColor.low'}
bg={'white'}
borderRadius={'lg'}
borderRadius={'md'}
userSelect={'none'}
position={'relative'}
display={'flex'}

View File

@@ -170,7 +170,7 @@ const ChatHistorySlider = ({
variant={'whiteDanger'}
size={'mdSquare'}
aria-label={''}
borderRadius={'xl'}
borderRadius={'50%'}
onClick={openConfirm(onClearHistory)}
>
<MyIcon name={'clear'} w={'16px'} />
@@ -191,7 +191,7 @@ const ChatHistorySlider = ({
px={4}
cursor={'pointer'}
userSelect={'none'}
borderRadius={'lg'}
borderRadius={'md'}
mb={2}
_hover={{
bg: 'myGray.100',
@@ -287,7 +287,7 @@ const ChatHistorySlider = ({
py={2}
px={3}
mb={3}
borderRadius={'lg'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {

View File

@@ -46,7 +46,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
px={3}
mb={3}
cursor={'pointer'}
borderRadius={'lg'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {

View File

@@ -380,7 +380,7 @@ const FileSelect = ({
textAlign={'center'}
bg={'myWhite.400'}
p={5}
borderRadius={'lg'}
borderRadius={'md'}
border={'1px dashed'}
borderColor={'myGray.300'}
w={'100%'}

View File

@@ -1,13 +1,26 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Box, Textarea, Button, Flex, useTheme, Grid, useDisclosure } from '@chakra-ui/react';
import {
Box,
Textarea,
Button,
Flex,
useTheme,
Grid,
useDisclosure,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} 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 { 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, { RawSourceText, 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';
@@ -17,26 +30,55 @@ 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';
import { useForm } from 'react-hook-form';
import MySelect from '@/components/Select';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { fileDownload, readCsvContent } from '@/web/common/file/utils';
import { delay } from '@fastgpt/global/common/system/utils';
import QuoteItem from '@/components/core/dataset/QuoteItem';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
type FormType = {
inputText: string;
searchParams: {
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
limit: number;
similarity: number;
};
};
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 { pushDatasetTestItem } = useSearchTestStore();
const [inputType, setInputType] = useState<'text' | 'file'>('text');
const [datasetTestItem, setDatasetTestItem] = useState<SearchTestStoreItemType>();
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
const [searchMode, setSearchMode] = useState<`${DatasetSearchModeEnum}`>(
DatasetSearchModeEnum.embedding
);
const [usingReRank, setUsingReRank] = useState(false);
const searchModeData = DatasetSearchModeMap[searchMode];
const [refresh, setRefresh] = 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
}
}
});
const searchModeData = DatasetSearchModeMap[getValues('searchParams.searchMode')];
const {
isOpen: isOpenSelectMode,
@@ -44,14 +86,9 @@ const Test = ({ datasetId }: { datasetId: string }) => {
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, usingReRank, limit: 20 }),
const { mutate: onTextTest, isLoading: textTestIsLoading } = useRequest({
mutationFn: ({ inputText, searchParams }: FormType) =>
postSearchText({ datasetId, text: inputText.trim(), ...searchParams }),
onSuccess(res: SearchTestResponse) {
if (!res || res.list.length === 0) {
return toast({
@@ -62,11 +99,14 @@ const Test = ({ datasetId }: { datasetId: string }) => {
const testItem: SearchTestStoreItemType = {
id: nanoid(),
datasetId,
text: inputText.trim(),
text: getValues('inputText').trim(),
time: new Date(),
results: res.list,
duration: res.duration,
searchMode
searchMode: res.searchMode,
usingReRank: res.usingReRank,
limit: res.limit,
similarity: res.similarity
};
pushDatasetTestItem(testItem);
setDatasetTestItem(testItem);
@@ -78,6 +118,40 @@ const Test = ({ datasetId }: { datasetId: string }) => {
});
}
});
const { mutate: onFileTest, isLoading: fileTestIsLoading } = useRequest({
mutationFn: async ({ searchParams }: FormType) => {
if (!selectFile) return Promise.reject('File is not selected');
const { data } = await readCsvContent(selectFile);
const testList = data.slice(0, 100);
const results: SearchTestResponse[] = [];
for await (const item of testList) {
try {
const result = await postSearchText({ datasetId, text: item[0].trim(), ...searchParams });
results.push(result);
} catch (error) {
await delay(500);
}
}
return results;
},
onSuccess(res: SearchTestResponse[]) {
console.log(res);
},
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);
@@ -85,7 +159,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
return (
<Box h={'100%'} display={['block', 'flex']}>
{/* input */}
{/* left */}
<Box
h={['auto', '100%']}
display={['block', 'flex']}
@@ -96,11 +170,39 @@ const Test = ({ datasetId }: { datasetId: string }) => {
borderRight={['none', theme.borders.base]}
>
<Box border={'2px solid'} borderColor={'primary.500'} 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={'primary.600'} />
{t('core.dataset.test.Test Text')}
</Box>
{/* header */}
<Flex alignItems={'center'} justifyContent={'space-between'}>
<MySelect
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('core.dataset.test.Test Text')}
</Box>
</Flex>
),
value: 'text'
}
// {
// label: (
// <Flex alignItems={'center'}>
// <MyIcon mr={2} name={'csvImport'} w={'14px'} color={'primary.600'} />
// <Box fontSize={'sm'} fontWeight={'bold'} flex={1}>
// {t('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'} />}
@@ -110,257 +212,293 @@ const Test = ({ datasetId }: { datasetId: string }) => {
{t(searchModeData.title)}
</Button>
</Flex>
<Textarea
rows={6}
resize={'none'}
variant={'unstyled'}
maxLength={datasetDetail.vectorModel.maxToken}
placeholder={t('core.dataset.test.Test Text Placeholder')}
defaultValue={inputText}
onBlur={(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}>
<Box h={'180px'}>
{inputType === 'text' && (
<Textarea
h={'100%'}
resize={'none'}
variant={'unstyled'}
maxLength={datasetDetail.vectorModel.maxToken}
placeholder={t('core.dataset.test.Test Text Placeholder')}
{...register('inputText', {
required: true
})}
/>
)}
{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={'csvImport'} w={'24px'} />
<Box>
{selectFile ? selectFile.name : t('core.dataset.test.Batch test Placeholder')}
</Box>
</Flex>
<Box mt={3} fontSize={'sm'}>
CSV 100
<Box
as={'span'}
color={'primary.600'}
cursor={'pointer'}
onClick={() => {
fileDownload({
text: `"问题"\n"问题1"\n"问题2"\n"问题3"`,
type: 'text/csv',
filename: 'Test Template'
});
}}
>
</Box>
</Box>
</Box>
)}
</Box>
<Flex justifyContent={'flex-end'}>
<Button
size={'sm'}
isLoading={textTestIsLoading || fileTestIsLoading}
isDisabled={inputType === 'file' && !selectFile}
onClick={() => {
if (inputType === 'text') {
handleSubmit((data) => onTextTest(data))();
} else {
handleSubmit((data) => onFileTest(data))();
}
}}
>
{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 w={'80px'}>{t('core.dataset.search.search mode')}</Box>
<Box flex={1}>{t('core.dataset.test.Test Text')}</Box>
<Box w={'70px'}>{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'}
fontSize={'sm'}
onClick={() => setDatasetTestItem(item)}
>
<Box w={'80px'}>
{DatasetSearchModeMap[item.searchMode] ? (
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[item.searchMode].icon as any}
w={'12px'}
mr={'1px'}
/>
{t(DatasetSearchModeMap[item.searchMode].title)}
</Flex>
) : (
'-'
)}
</Box>
<Box flex={1} mr={2}>
{item.text}
</Box>
<Box w={'70px'}>{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>
<TestHistories
datasetId={datasetId}
datasetTestItem={datasetTestItem}
setDatasetTestItem={setDatasetTestItem}
/>
</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={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
cursor={'pointer'}
title={t('common.Edit')}
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={3}
borderRadius={'md'}
>
# {index + 1}
</Box>
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={item.sourceName}
sourceId={item.sourceId}
canView
/>
{/* <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>
</>
)}
<TestResults datasetTestItem={datasetTestItem} />
</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}
usingReRank={usingReRank}
{...getValues('searchParams')}
maxTokens={20000}
onClose={onCloseSelectMode}
onSuccess={(e) => {
setSearchMode(e.searchMode);
e.usingReRank !== undefined && setUsingReRank(e.usingReRank);
setValue('searchParams', {
...getValues('searchParams'),
...e
});
setRefresh((state) => !state);
}}
/>
)}
<File onSelect={onSelectFile} />
</Box>
);
};
export default Test;
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 theme = useTheme();
const { datasetTestList, delDatasetTestItemById } = useSearchTestStore();
const testHistories = useMemo(
() => datasetTestList.filter((item) => item.datasetId === datasetId),
[datasetId, datasetTestList]
);
return (
<>
<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={'0 0 80px'}>{t('core.dataset.search.search mode')}</Box>
<Box flex={1}>{t('core.dataset.test.Test Text')}</Box>
<Box flex={'0 0 70px'}>{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'}
fontSize={'sm'}
onClick={() => setDatasetTestItem(item)}
>
<Box flex={'0 0 80px'}>
{DatasetSearchModeMap[item.searchMode] ? (
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[item.searchMode].icon as any}
w={'12px'}
mr={'1px'}
/>
{t(DatasetSearchModeMap[item.searchMode].title)}
</Flex>
) : (
'-'
)}
</Box>
<Box flex={1} mr={2} wordBreak={'break-all'}>
{item.text}
</Box>
<Box flex={'0 0 70px'}>{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>
</>
);
});
const TestResults = React.memo(function TestResults({
datasetTestItem
}: {
datasetTestItem?: SearchTestStoreItemType;
}) {
const { t } = useTranslation();
const theme = useTheme();
return (
<>
{!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>
) : (
<>
<Box fontSize={'xl'} color={'myGray.600'}>
{t('core.dataset.test.Test params')}
</Box>
<TableContainer mb={3} bg={'myGray.150'} borderRadius={'md'}>
<Table>
<Thead>
<Tr>
<Th>{t('core.dataset.search.search mode')}</Th>
<Th>{t('core.dataset.search.ReRank')}</Th>
<Th>{t('core.dataset.search.Max Tokens')}</Th>
<Th>{t('core.dataset.search.Min Similarity')}</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[datasetTestItem.searchMode]?.icon as any}
w={'12px'}
mr={'1px'}
/>
{t(DatasetSearchModeMap[datasetTestItem.searchMode]?.title)}
</Flex>
</Td>
<Td>{datasetTestItem.usingReRank ? '✅' : '❌'}</Td>
<Td>{datasetTestItem.limit}</Td>
<Td>{datasetTestItem.similarity}</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<Flex alignItems={'center'}>
<Box fontSize={'xl'} 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,minmax(0, 1fr))',
'repeat(1,minmax(0, 1fr))',
'repeat(1,minmax(0, 1fr))',
'repeat(1,minmax(0, 1fr))',
'repeat(2,minmax(0, 1fr))'
]}
gridGap={4}
>
{datasetTestItem?.results.map((item, index) => (
<Box
key={item.id}
p={2}
borderRadius={'sm'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
>
<QuoteItem quoteItem={item} canViewSource />
</Box>
))}
</Grid>
</>
)}
</>
);
});

View File

@@ -156,7 +156,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
borderRight={theme.borders.base}
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={datasetDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Avatar src={datasetDetail.avatar} w={'34px'} borderRadius={'md'} />
<Box ml={2}>
<Box fontWeight={'bold'}>{datasetDetail.name}</Box>
</Box>

View File

@@ -211,7 +211,7 @@ const Kb = () => {
borderWidth={1.5}
borderColor={dragTargetId === dataset._id ? 'primary.600' : 'borderColor.low'}
bg={'white'}
borderRadius={'lg'}
borderRadius={'md'}
minH={'130px'}
position={'relative'}
data-drag-id={dataset.type === DatasetTypeEnum.folder ? dataset._id : undefined}
@@ -390,7 +390,7 @@ const Kb = () => {
/>
)}
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={dataset.avatar} borderRadius={'lg'} w={'28px'} />
<Avatar src={dataset.avatar} borderRadius={'md'} w={'28px'} />
<Box mx={3} className="textEllipsis3">
{dataset.name}
</Box>

View File

@@ -60,7 +60,7 @@ const MyModules = () => {
borderWidth={'1.5px'}
borderColor={'borderColor.low'}
bg={'white'}
borderRadius={'lg'}
borderRadius={'md'}
userSelect={'none'}
position={'relative'}
_hover={{

View File

@@ -18,7 +18,8 @@ export function reRankRecall({ query, inputs }: PostReRankProps) {
{
headers: {
Authorization: `Bearer ${model.requestAuth}`
}
},
timeout: 120000
}
)
.then((data) => {

View File

@@ -275,25 +275,20 @@ export async function searchDatasetData(props: {
const oneChunkToken = 50;
const estimatedLen = Math.max(20, Math.ceil(maxTokens / oneChunkToken));
// Increase search range, reduce hnsw loss. 20 ~ 100
if (searchMode === DatasetSearchModeEnum.embedding) {
return {
embeddingLimit: Math.min(estimatedLen, 100),
embeddingLimit: Math.min(estimatedLen, 80),
fullTextLimit: 0
};
}
// 50 < 2*limit < value < 100
if (searchMode === DatasetSearchModeEnum.fullTextRecall) {
return {
embeddingLimit: 0,
fullTextLimit: Math.min(estimatedLen, 50)
};
}
// mixed
// 50 < 2*limit < embedding < 80
// 20 < limit < fullTextLimit < 40
return {
embeddingLimit: Math.min(estimatedLen, 80),
embeddingLimit: Math.min(estimatedLen, 60),
fullTextLimit: Math.min(estimatedLen, 40)
};
};
@@ -340,7 +335,6 @@ export async function searchDatasetData(props: {
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
sourceName: collection.name || '',
@@ -389,7 +383,6 @@ export async function searchDatasetData(props: {
collectionId: 1,
q: 1,
a: 1,
indexes: 1,
chunkIndex: 1
}
)
@@ -464,6 +457,7 @@ export async function searchDatasetData(props: {
return mergeResult;
} catch (error) {
usingReRank = false;
return [];
}
};
@@ -553,6 +547,11 @@ export async function searchDatasetData(props: {
const rrfConcat = (
arr: { k: number; list: SearchDataResponseItemType[] }[]
): SearchDataResponseItemType[] => {
arr = arr.filter((item) => item.list.length > 0);
if (arr.length === 0) return [];
if (arr.length === 1) return arr[0].list;
const map = new Map<string, SearchDataResponseItemType & { rrfScore: number }>();
// rrf
@@ -643,7 +642,7 @@ export async function searchDatasetData(props: {
// embedding recall and fullText recall rrf concat
const rrfConcatResults = rrfConcat([
{ k: 60, list: embeddingRecallResults },
{ k: 60, list: fullTextRecallResults },
{ k: 64, list: fullTextRecallResults },
{ k: 60, list: reRankResults }
]);
@@ -685,6 +684,10 @@ export async function searchDatasetData(props: {
return {
searchRes: filterResultsByMaxTokens(scoreFilter, maxTokens),
tokens,
searchMode,
limit: maxTokens,
similarity,
usingReRank,
usingSimilarityFilter
};
}

View File

@@ -176,10 +176,12 @@ async function completions({
{
obj: ChatRoleEnum.Human,
value: replaceVariable(cqModel.functionPrompt || Prompt_CQJson, {
systemPrompt,
typeList: agents.map((item) => `{"${item.value}": ${item.key}}`).join('\n'),
text: `${histories.map((item) => `${item.obj}:${item.value}`).join('\n')}
Human:${userChatInput}`
systemPrompt: systemPrompt || 'null',
typeList: agents
.map((item) => `{"questionType": "${item.value}", "typeId": "${item.key}"}`)
.join('\n'),
history: histories.map((item) => `${item.obj}:${item.value}`).join('\n'),
question: userChatInput
})
}
];
@@ -194,7 +196,8 @@ Human:${userChatInput}`
});
const answer = data.choices?.[0].message?.content || '';
const id = agents.find((item) => answer.includes(item.key))?.key || '';
const id =
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
return {
inputTokens: data.usage?.prompt_tokens || 0,

View File

@@ -52,7 +52,12 @@ export async function dispatchDatasetSearch(
const concatQueries = [userChatInput];
// start search
const { searchRes, tokens, usingSimilarityFilter } = await searchDatasetData({
const {
searchRes,
tokens,
usingSimilarityFilter,
usingReRank: searchUsingReRank
} = await searchDatasetData({
rawQuery: userChatInput,
queries: concatQueries,
model: vectorModel.model,
@@ -81,7 +86,7 @@ export async function dispatchDatasetSearch(
similarity: usingSimilarityFilter ? similarity : undefined,
limit,
searchMode,
searchUsingReRank: usingReRank
searchUsingReRank: searchUsingReRank
}
};
}

View File

@@ -181,7 +181,7 @@ export async function dispatchModules({
});
}
// get fetch params
// get module running params
const params: Record<string, any> = {};
module.inputs.forEach((item: any) => {
params[item.key] = item.value;
@@ -198,6 +198,7 @@ export async function dispatchModules({
inputs: params
};
// run module
const dispatchRes: Record<string, any> = await (async () => {
if (callbackMap[module.flowType]) {
return callbackMap[module.flowType](dispatchData);
@@ -205,10 +206,13 @@ export async function dispatchModules({
return {};
})();
// format response data. Add modulename and moduletype
const formatResponseData = (() => {
if (!dispatchRes[ModuleOutputKeyEnum.responseData]) return undefined;
if (Array.isArray(dispatchRes[ModuleOutputKeyEnum.responseData]))
if (Array.isArray(dispatchRes[ModuleOutputKeyEnum.responseData])) {
return dispatchRes[ModuleOutputKeyEnum.responseData];
}
return {
moduleName: module.name,
moduleType: module.flowType,
@@ -216,8 +220,16 @@ export async function dispatchModules({
};
})();
// Pass userChatInput
const hasUserChatInputTarget = !!module.outputs.find(
(item) => item.key === ModuleOutputKeyEnum.userChatInput
)?.targets?.length;
return moduleOutput(module, {
[ModuleOutputKeyEnum.finish]: true,
[ModuleOutputKeyEnum.userChatInput]: hasUserChatInputTarget
? params[ModuleOutputKeyEnum.userChatInput]
: undefined,
...dispatchRes,
[ModuleOutputKeyEnum.responseData]: formatResponseData
});

View File

@@ -12,6 +12,9 @@ export type SearchTestStoreItemType = {
duration: string;
results: SearchDataResponseItemType[];
searchMode: `${DatasetSearchModeEnum}`;
limit: number;
usingReRank: boolean;
similarity: number;
};
type State = {

View File

@@ -417,8 +417,8 @@ export const theme = extendTheme({
body: 'PingFang,Noto Sans,-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
},
fontSizes: {
xs: '10',
sm: '12px',
xs: '12px',
sm: '13px',
md: '14px',
lg: '16px',
xl: '18px',
@@ -440,7 +440,7 @@ export const theme = extendTheme({
md: '1px solid #DAE0E2',
lg: '1px solid #D0E0E2'
},
borderRadius: {
radii: {
xs: '4px',
sm: '6px',
md: '8px',