feat: Text check before synchronization (#689)

* fix: icon

* fix: web selector

* fix: web selector

* perf: link sync

* dev doc

* chomd doc

* perf: git intro

* 466 intro

* intro img

* add json editor (#5)

* team limit

* websync limit

* json editor

* text editor

* perf: search test

* change cq value type

* doc

* intro img

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-01-04 23:19:24 +08:00
committed by GitHub
parent c2abbb579f
commit 828829011a
64 changed files with 1789 additions and 1489 deletions

View File

@@ -28,14 +28,14 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
},
{
label: t('navbar.Tools'),
icon: 'phoneTabbar/tabbarMore',
icon: 'phoneTabbar/more',
link: '/tools',
activeLink: ['/tools'],
unread: 0
},
{
label: t('navbar.Account'),
icon: 'phoneTabbar/tabbarMe',
icon: 'phoneTabbar/me',
link: '/account',
activeLink: ['/account'],
unread

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import { Box, Flex, Link, Progress, useTheme } from '@chakra-ui/react';
import { Box, Flex, Link, Progress } from '@chakra-ui/react';
import {
type InputDataType,
RawSourceText
@@ -9,7 +9,6 @@ import NextLink from 'next/link';
import MyIcon from '@fastgpt/web/components/common/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';
@@ -19,6 +18,36 @@ import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/da
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
type ScoreItemType = SearchDataResponseItemType['score'][0];
const scoreTheme: Record<
string,
{
color: string;
bg: string;
borderColor: string;
colorSchema: string;
}
> = {
'0': {
color: '#6F5DD7',
bg: '#F0EEFF',
borderColor: '#D3CAFF',
colorSchema: 'purple'
},
'1': {
color: '#9E53C1',
bg: '#FAF1FF',
borderColor: '#ECF',
colorSchema: 'pink'
},
'2': {
color: '#0884DD',
bg: '#F0FBFF',
borderColor: '#BCE7FF',
colorSchema: 'blue'
}
};
const QuoteItem = ({
quoteItem,
canViewSource,
@@ -29,8 +58,6 @@ const QuoteItem = ({
linkToDataset?: boolean;
}) => {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const theme = useTheme();
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
const { mutate: onclickEdit, isLoading } = useRequest({
@@ -43,54 +70,46 @@ const QuoteItem = ({
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');
if (!Array.isArray(quoteItem.score)) {
return {
primaryScore: undefined,
secondaryScore: []
};
}
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');
}
// rrf -> rerank -> embedding -> fullText 优先级
let rrfScore: ScoreItemType | undefined = undefined;
let reRankScore: ScoreItemType | undefined = undefined;
let embeddingScore: ScoreItemType | undefined = undefined;
let fullTextScore: ScoreItemType | undefined = undefined;
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');
quoteItem.score.forEach((item) => {
if (item.type === SearchScoreTypeEnum.rrf) {
rrfScore = item;
} else if (item.type === SearchScoreTypeEnum.reRank) {
reRankScore = item;
} else if (item.type === SearchScoreTypeEnum.embedding) {
embeddingScore = item;
} else if (item.type === SearchScoreTypeEnum.fullText) {
fullTextScore = item;
}
return 'null';
})();
});
const primaryScore = (rrfScore ||
reRankScore ||
embeddingScore ||
fullTextScore) as unknown as ScoreItemType;
const secondaryScore = [rrfScore, reRankScore, embeddingScore, fullTextScore].filter(
// @ts-ignore
(item) => item && primaryScore && item.type !== primaryScore.type
) as unknown as ScoreItemType[];
return {
value: searchScore,
tip: t('core.dataset.Search score tip', {
scoreText: text ? `${text}\n` : text,
detailScore
})
primaryScore,
secondaryScore
};
}, [quoteItem.score, t]);
}, [quoteItem.score]);
return (
<>
@@ -101,86 +120,97 @@ ${t('core.dataset.search.Score')}: ${item.value.toFixed(4)}`
fontSize={'sm'}
whiteSpace={'pre-wrap'}
_hover={{ '& .hover-data': { display: 'flex' } }}
h={'100%'}
display={'flex'}
flexDirection={'column'}
>
<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>
<Flex alignItems={'center'} mb={3}>
{score?.primaryScore && (
<MyTooltip label={t(SearchScoreTypeMap[score.primaryScore.type]?.desc)}>
<Flex
px={'12px'}
py={'5px'}
mr={4}
borderRadius={'md'}
color={'primary.700'}
bg={'primary.50'}
borderWidth={'1px'}
borderColor={'primary.200'}
alignItems={'center'}
fontSize={'sm'}
>
<Box>#{score.primaryScore.index + 1}</Box>
<Box borderRightColor={'primary.700'} borderRightWidth={'1px'} h={'14px'} mx={2} />
<Box>
{t(SearchScoreTypeMap[score.primaryScore.type]?.label)}
{SearchScoreTypeMap[score.primaryScore.type]?.showScore
? ` ${score.primaryScore.value.toFixed(4)}`
: ''}
</Box>
</Flex>
</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>
)}
{score.secondaryScore.map((item, i) => (
<MyTooltip key={item.type} label={t(SearchScoreTypeMap[item.type]?.desc)}>
<Box fontSize={'xs'} mr={3}>
<Flex alignItems={'flex-start'} lineHeight={1.2} mb={1}>
<Box
px={'5px'}
borderWidth={'1px'}
borderRadius={'sm'}
mr={1}
{...(scoreTheme[i] && scoreTheme[i])}
>
<Box transform={'scale(0.9)'}>#{item.index + 1}</Box>
</Box>
<Box transform={'scale(0.9)'}>
{t(SearchScoreTypeMap[item.type]?.label)}: {item.value.toFixed(4)}
</Box>
</Flex>
<Box h={'4px'}>
{SearchScoreTypeMap[item.type]?.showScore && (
<Progress
value={item.value * 100}
h={'4px'}
w={'100%'}
size="sm"
borderRadius={'20px'}
colorScheme={scoreTheme[i]?.colorSchema}
bg="#E8EBF0"
/>
)}
</Box>
</Box>
</MyTooltip>
))}
</Flex>
<Box color={'black'}>{quoteItem.q}</Box>
<Box color={'myGray.600'}>{quoteItem.a}</Box>
<Box flex={'1 0 0'}>
<Box color={'black'}>{quoteItem.q}</Box>
<Box color={'myGray.600'}>{quoteItem.a}</Box>
</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>
)}
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={quoteItem.sourceName}
sourceId={quoteItem.sourceId}
canView={canViewSource}
/>
<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)'}
@@ -199,6 +229,19 @@ ${t('core.dataset.search.Score')}: ${item.value.toFixed(4)}`
</Box>
</MyTooltip>
)}
{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>
)}
</MyBox>

View File

@@ -94,7 +94,7 @@ const NodeCQNode = React.memo(function NodeCQNode({ data }: { data: FlowModuleIt
/>
<SourceHandle
handleKey={item.key}
valueType={ModuleIOValueTypeEnum.string}
valueType={ModuleIOValueTypeEnum.boolean}
/>
</Box>
</Box>

View File

@@ -64,6 +64,10 @@ const RenderList: {
{
types: [FlowNodeInputTypeEnum.addInputParam],
Component: dynamic(() => import('./templates/AddInputParam'))
},
{
types: [FlowNodeInputTypeEnum.JSONEditor],
Component: dynamic(() => import('./templates/JsonEditor'))
}
];
const UserChatInput = dynamic(() => import('./templates/UserChatInput'));

View File

@@ -0,0 +1,39 @@
import React, { useCallback } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
const JsonEditor = ({ item, moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const update = useCallback(
(value: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value
}
});
},
[item, moduleId]
);
return (
<JSONEditor
title={t(item.label)}
bg={'myWhite.400'}
placeholder={t(item.placeholder || '')}
resize
defaultValue={item.value}
onChange={(e) => {
update(e);
}}
/>
);
};
export default React.memo(JsonEditor);