mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-30 18:48:55 +00:00
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:
@@ -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
|
||||
|
@@ -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}¤tTab=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}¤tTab=dataCard&collectionId=${quoteItem.collectionId}`}
|
||||
>
|
||||
{t('core.dataset.Go Dataset')}
|
||||
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
|
@@ -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>
|
||||
|
@@ -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'));
|
||||
|
@@ -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);
|
Reference in New Issue
Block a user