* fix: remove DefaultTeam (#4037) * fix :Get application bound knowledge base information logical rewrite (#4057) * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * update package * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * fix: 文本提取不支持arrayString,arrayNumber等jsonSchema (#4079) * update doc ;perf: model test (#4098) * perf: extract array * update doc * perf: model test * perf: model test * perf: think tag parse (#4102) * chat quote reader (#3912) * init chat quote full text reader * linked structure * dataset data linked * optimize code * fix ts build * test finish * delete log * fix * fix ts * fix ts * remove nextId * initial scroll * fix * fix * perf: chunk read (#4109) * package * perf: chunk read * feat: api dataset support pdf parse;fix: chunk reader auth (#4117) * feat: api dataset support pdf parse * fix: chunk reader auth * feat: invitation link (#3979) * feat: invitation link schema and apis * feat: add invitation link * feat: member status: active, leave, forbidden * fix: expires show hours and minutes * feat: invalid invitation link hint * fix: typo * chore: fix typo & i18n * fix * pref: fe * feat: add ttl index for 30-day-clean-up * perf: invite member code (#4118) * perf: invite member code * fix: ts * fix: model test channel id;fix: quote reader (#4123) * fix: model test channel id * fix: quote reader * fix chat quote reader (#4125) * perf: model test;perf: sidebar trigger (#4127) * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * perf: model test * perf: sidebar trigger * lock * update nanoid version * fix: select component ux * fix: ts * fix: vitest * remove test * fix: prompt toolcall ui (#4139) * load log error adapt * fix: prompt toolcall ui * perf: commercial function tip * update package * pref: copy link (#4147) * fix(i18n): namespace (#4143) * hiden dataset source (#4152) * hiden dataset source * perf: reader * chore: move all tests into a single folder (#4160) * fix modal close scroll (#4162) * fix modal close scroll * update refresh * feat: rerank modal select and weight (#4164) * fix loadInitData refresh (#4169) * fix * fix * form input number default & api dataset max token * feat: mix search weight (#4170) * feat: mix search weight * feat: svg render * fix: avatar error remove (#4173) * fix: avatar error remove * fix: index * fix: guide * fix: auth * update package;fix: input data model ui (#4181) * update package * fix: ts * update config * update jieba package * add type sign * fix: input data ui * fix: page title refresh (#4186) * fix: ts * update jieba package * fix: page title refresh * fix: remove member length check when opening invite create modal (#4193) * add env to check internal ip (#4187) * fix: ts * update jieba package * add env to check internal ip * package * fix: jieba * reset package * update config * fix: jieba package * init shell * init version * change team reload * update jieba package (#4200) * update jieba package * package * update package * remove invalid code * action * package (#4201) * package * update package * remove invalid code * package * remove i18n tip (#4202) * doc (#4205) * fix: i18n (#4208) * fix: next config (#4207) * reset package * i18n * update config * i18n * remove log --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com> Co-authored-by: shilin <39396378+shilin66@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io>
305 lines
9.8 KiB
TypeScript
305 lines
9.8 KiB
TypeScript
import { Box, Flex, HStack } from '@chakra-ui/react';
|
|
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
|
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
import { useRouter } from 'next/router';
|
|
import { useTranslation } from 'next-i18next';
|
|
import DownloadButton from './DownloadButton';
|
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|
import { downloadFetch } from '@/web/common/system/utils';
|
|
import { useMemo, useState } from 'react';
|
|
import { getDatasetDataPermission } from '@/web/core/dataset/api';
|
|
import ScoreTag from './ScoreTag';
|
|
import { formatScore } from '@/components/core/dataset/QuoteItem';
|
|
import NavButton from './NavButton';
|
|
import { useLinkedScroll } from '@fastgpt/web/hooks/useLinkedScroll';
|
|
import CollectionQuoteItem from './CollectionQuoteItem';
|
|
import { GetCollectionQuoteDataProps } from '@/web/core/chat/context/chatItemContext';
|
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
|
import { getCollectionQuote } from '@/web/core/chat/api';
|
|
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
|
import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource';
|
|
import { QuoteDataItemType } from '@/service/core/chat/constants';
|
|
|
|
const CollectionReader = ({
|
|
rawSearch,
|
|
metadata,
|
|
onClose
|
|
}: {
|
|
rawSearch: SearchDataResponseItemType[];
|
|
metadata: GetCollectionQuoteDataProps;
|
|
onClose: () => void;
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const router = useRouter();
|
|
const { userInfo } = useUserStore();
|
|
|
|
const { collectionId, datasetId, chatItemDataId, sourceId, sourceName } = metadata;
|
|
const [quoteIndex, setQuoteIndex] = useState(0);
|
|
|
|
// Get dataset permission
|
|
const { data: datasetData } = useRequest2(async () => await getDatasetDataPermission(datasetId), {
|
|
manual: !userInfo || !datasetId,
|
|
refreshDeps: [datasetId, userInfo]
|
|
});
|
|
|
|
const filterResults = useMemo(() => {
|
|
setQuoteIndex(0);
|
|
return rawSearch
|
|
.filter((item) => item.collectionId === collectionId)
|
|
.sort((a, b) => (a.chunkIndex || 0) - (b.chunkIndex || 0));
|
|
}, [collectionId, rawSearch]);
|
|
const currentQuoteItem = useMemo(() => {
|
|
const item = filterResults[quoteIndex];
|
|
if (item) {
|
|
return {
|
|
id: item.id,
|
|
index: item.chunkIndex,
|
|
score: item.score
|
|
};
|
|
}
|
|
}, [filterResults, quoteIndex]);
|
|
|
|
// Get quote list
|
|
const params = useMemo(
|
|
() => ({
|
|
collectionId,
|
|
chatItemDataId,
|
|
chatId: metadata.chatId,
|
|
appId: metadata.appId,
|
|
...metadata.outLinkAuthData
|
|
}),
|
|
[chatItemDataId, collectionId, metadata.appId, metadata.chatId, metadata.outLinkAuthData]
|
|
);
|
|
|
|
const {
|
|
dataList: datasetDataList,
|
|
isLoading,
|
|
ScrollData,
|
|
itemRefs,
|
|
loadInitData
|
|
} = useLinkedScroll(getCollectionQuote, {
|
|
params,
|
|
currentData: currentQuoteItem
|
|
});
|
|
|
|
const isDeleted = useMemo(
|
|
() => !isLoading && !datasetDataList.find((item) => item._id === currentQuoteItem?.id),
|
|
[datasetDataList, currentQuoteItem?.id, isLoading]
|
|
);
|
|
|
|
const formatedDataList = useMemo(
|
|
() =>
|
|
datasetDataList.map((item: QuoteDataItemType) => {
|
|
const isCurrentSelected = currentQuoteItem?.id === item._id;
|
|
const quoteIndex = filterResults.findIndex((res) => res.id === item._id);
|
|
|
|
return {
|
|
...item,
|
|
isCurrentSelected,
|
|
quoteIndex
|
|
};
|
|
}),
|
|
[currentQuoteItem?.id, datasetDataList, filterResults]
|
|
);
|
|
|
|
const { runAsync: handleDownload } = useRequest2(async () => {
|
|
await downloadFetch({
|
|
url: '/api/core/dataset/collection/export',
|
|
filename: 'data.csv',
|
|
body: {
|
|
appId: metadata.appId,
|
|
chatId: metadata.chatId,
|
|
chatItemDataId,
|
|
collectionId,
|
|
...metadata.outLinkAuthData
|
|
}
|
|
});
|
|
});
|
|
|
|
const handleRead = getCollectionSourceAndOpen({
|
|
appId: metadata.appId,
|
|
chatId: metadata.chatId,
|
|
chatItemDataId,
|
|
collectionId,
|
|
...metadata.outLinkAuthData
|
|
});
|
|
|
|
return (
|
|
<MyBox display={'flex'} flexDirection={'column'} h={'full'}>
|
|
{/* title */}
|
|
<Box borderBottom={'1px solid'} borderBottomColor={'myGray.150'} px={3} py={2}>
|
|
{/* name */}
|
|
<HStack>
|
|
<Flex alignItems={'center'} flex={'1 0 0'} w={0}>
|
|
<MyIcon
|
|
name={getSourceNameIcon({ sourceId, sourceName }) as any}
|
|
w={['1rem', '1.25rem']}
|
|
color={'primary.600'}
|
|
/>
|
|
<Box
|
|
ml={1}
|
|
maxW={['200px', '220px']}
|
|
className={'textEllipsis'}
|
|
wordBreak={'break-all'}
|
|
fontSize={'sm'}
|
|
color={'myGray.900'}
|
|
fontWeight={'medium'}
|
|
{...(!!userInfo &&
|
|
datasetData?.permission?.hasReadPer && {
|
|
cursor: 'pointer',
|
|
_hover: { color: 'primary.600', textDecoration: 'underline' },
|
|
onClick: () => {
|
|
router.push(
|
|
`/dataset/detail?datasetId=${datasetId}¤tTab=dataCard&collectionId=${collectionId}`
|
|
);
|
|
}
|
|
})}
|
|
>
|
|
{sourceName || t('common:common.UnKnow Source')}
|
|
</Box>
|
|
<Box ml={3}>
|
|
<DownloadButton
|
|
canAccessRawData={true}
|
|
onDownload={handleDownload}
|
|
onRead={handleRead}
|
|
/>
|
|
</Box>
|
|
</Flex>
|
|
<MyIconButton
|
|
icon={'common/closeLight'}
|
|
size={'1.25rem'}
|
|
color={'myGray.900'}
|
|
onClick={onClose}
|
|
/>
|
|
</HStack>
|
|
{datasetData?.permission?.hasReadPer && (
|
|
<Box
|
|
fontSize={'mini'}
|
|
color={'myGray.500'}
|
|
{...(!!userInfo
|
|
? {
|
|
cursor: 'pointer',
|
|
_hover: { color: 'primary.600', textDecoration: 'underline' },
|
|
onClick: () => {
|
|
router.push(`/dataset/detail?datasetId=${datasetId}`);
|
|
}
|
|
}
|
|
: {})}
|
|
>
|
|
{t('chat:data_source', {
|
|
name: datasetData.datasetName
|
|
})}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* header control */}
|
|
{datasetDataList.length > 0 && (
|
|
<Box>
|
|
<Flex
|
|
w={'full'}
|
|
px={4}
|
|
py={2}
|
|
alignItems={'center'}
|
|
borderBottom={'1px solid'}
|
|
borderColor={'myGray.150'}
|
|
>
|
|
{/* 引用序号 */}
|
|
<Flex fontSize={'mini'} mr={3} alignItems={'center'} gap={1}>
|
|
<Box as={'span'} color={'myGray.900'}>
|
|
{t('common:core.chat.Quote')} {quoteIndex + 1}
|
|
</Box>
|
|
<Box as={'span'} color={'myGray.500'}>
|
|
/
|
|
</Box>
|
|
<Box as={'span'} color={'myGray.500'}>
|
|
{filterResults.length}
|
|
</Box>
|
|
</Flex>
|
|
|
|
{/* 检索分数 */}
|
|
{currentQuoteItem?.score ? (
|
|
<ScoreTag {...formatScore(currentQuoteItem?.score)} />
|
|
) : isDeleted ? (
|
|
<Flex
|
|
borderRadius={'sm'}
|
|
py={1}
|
|
px={2}
|
|
color={'red.600'}
|
|
bg={'red.50'}
|
|
alignItems={'center'}
|
|
fontSize={'11px'}
|
|
>
|
|
<MyIcon name="common/info" w={'14px'} mr={1} color={'red.600'} />
|
|
{t('chat:chat.quote.deleted')}
|
|
</Flex>
|
|
) : null}
|
|
|
|
<Box flex={1} />
|
|
|
|
{/* 检索按钮 */}
|
|
<Flex gap={1}>
|
|
<NavButton
|
|
direction="up"
|
|
isDisabled={quoteIndex === 0}
|
|
onClick={() => setQuoteIndex(quoteIndex - 1)}
|
|
/>
|
|
<NavButton
|
|
direction="down"
|
|
isDisabled={quoteIndex === filterResults.length - 1}
|
|
onClick={() => setQuoteIndex(quoteIndex + 1)}
|
|
/>
|
|
</Flex>
|
|
</Flex>
|
|
<Box fontSize={'mini'} color={'myGray.500'} bg={'myGray.25'} px={4} py={1}>
|
|
{t('common:core.chat.quote.Quote Tip')}
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
|
|
{/* quote list */}
|
|
{isLoading || datasetDataList.length > 0 ? (
|
|
<ScrollData flex={'1 0 0'} mt={2} px={5} py={1}>
|
|
<Flex flexDir={'column'}>
|
|
{formatedDataList.map((item, index) => (
|
|
<CollectionQuoteItem
|
|
key={item._id}
|
|
quoteRefs={itemRefs as React.MutableRefObject<Map<string, HTMLDivElement | null>>}
|
|
quoteIndex={item.quoteIndex}
|
|
setQuoteIndex={setQuoteIndex}
|
|
refreshList={() => loadInitData({ scrollWhenFinish: false, refresh: true })}
|
|
updated={item.updated}
|
|
isCurrentSelected={item.isCurrentSelected}
|
|
q={item.q}
|
|
a={item.a}
|
|
dataId={item._id}
|
|
collectionId={collectionId}
|
|
canEdit={!!userInfo && !!datasetData?.permission?.hasWritePer}
|
|
/>
|
|
))}
|
|
</Flex>
|
|
</ScrollData>
|
|
) : (
|
|
<Flex
|
|
flex={'1 0 0'}
|
|
flexDirection={'column'}
|
|
gap={1}
|
|
justifyContent={'center'}
|
|
alignItems={'center'}
|
|
>
|
|
<Box border={'1px dashed'} borderColor={'myGray.400'} p={2} borderRadius={'full'}>
|
|
<MyIcon name="common/fileNotFound" />
|
|
</Box>
|
|
<Box fontSize={'sm'} color={'myGray.500'}>
|
|
{t('chat:chat.quote.No Data')}
|
|
</Box>
|
|
</Flex>
|
|
)}
|
|
</MyBox>
|
|
);
|
|
};
|
|
|
|
export default CollectionReader;
|