* 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>
363 lines
12 KiB
TypeScript
363 lines
12 KiB
TypeScript
import React, { useState, useMemo } from 'react';
|
|
import { Box, Card, IconButton, Flex, Button, useTheme } from '@chakra-ui/react';
|
|
import {
|
|
getDatasetDataList,
|
|
delOneDatasetDataById,
|
|
getDatasetCollectionById
|
|
} from '@/web/core/dataset/api';
|
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|
import { useTranslation } from 'next-i18next';
|
|
import { useRouter } from 'next/router';
|
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
import MyInput from '@/components/MyInput';
|
|
import InputDataModal from './InputDataModal';
|
|
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
|
import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection/utils';
|
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
|
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
|
import { useContextSelector } from 'use-context-selector';
|
|
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
|
import TagsPopOver from './CollectionCard/TagsPopOver';
|
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
|
import Markdown from '@/components/Markdown';
|
|
import { useMemoizedFn } from 'ahooks';
|
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
|
import { TabEnum } from './NavBar';
|
|
import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants';
|
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|
|
|
const DataCard = () => {
|
|
const theme = useTheme();
|
|
const router = useRouter();
|
|
const { isPc } = useSystem();
|
|
const { collectionId = '', datasetId } = router.query as {
|
|
collectionId: string;
|
|
datasetId: string;
|
|
};
|
|
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
|
const { feConfigs } = useSystemStore();
|
|
|
|
const { t } = useTranslation();
|
|
const [searchText, setSearchText] = useState('');
|
|
const { toast } = useToast();
|
|
|
|
const scrollParams = useMemo(
|
|
() => ({
|
|
collectionId,
|
|
searchText
|
|
}),
|
|
[collectionId, searchText]
|
|
);
|
|
const EmptyTipDom = useMemo(
|
|
() => <EmptyTip text={t('common:core.dataset.data.Empty Tip')} />,
|
|
[t]
|
|
);
|
|
const {
|
|
data: datasetDataList,
|
|
ScrollData,
|
|
total,
|
|
refreshList,
|
|
setData: setDatasetDataList
|
|
} = useScrollPagination(getDatasetDataList, {
|
|
pageSize: 15,
|
|
params: scrollParams,
|
|
refreshDeps: [searchText, collectionId],
|
|
EmptyTip: EmptyTipDom
|
|
});
|
|
|
|
const [editDataId, setEditDataId] = useState<string>();
|
|
|
|
// get file info
|
|
const { data: collection } = useRequest2(() => getDatasetCollectionById(collectionId), {
|
|
refreshDeps: [collectionId],
|
|
manual: false,
|
|
onError: () => {
|
|
router.replace({
|
|
query: {
|
|
datasetId
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const canWrite = useMemo(() => datasetDetail.permission.hasWritePer, [datasetDetail]);
|
|
|
|
const { openConfirm, ConfirmModal } = useConfirm({
|
|
content: t('common:dataset.Confirm to delete the data'),
|
|
type: 'delete'
|
|
});
|
|
const onDeleteOneData = useMemoizedFn((dataId: string) => {
|
|
openConfirm(async () => {
|
|
try {
|
|
await delOneDatasetDataById(dataId);
|
|
setDatasetDataList((prev) => {
|
|
return prev.filter((data) => data._id !== dataId);
|
|
});
|
|
toast({
|
|
title: t('common:common.Delete Success'),
|
|
status: 'success'
|
|
});
|
|
} catch (error) {
|
|
toast({
|
|
title: getErrText(error),
|
|
status: 'error'
|
|
});
|
|
}
|
|
})();
|
|
});
|
|
|
|
return (
|
|
<MyBox py={[1, 0]} h={'100%'}>
|
|
<Flex flexDirection={'column'} h={'100%'}>
|
|
{/* Header */}
|
|
<Flex alignItems={'center'} px={6}>
|
|
<Box flex={'1 0 0'} mr={[3, 5]} alignItems={'center'}>
|
|
<Box
|
|
className="textEllipsis"
|
|
alignItems={'center'}
|
|
gap={2}
|
|
display={isPc ? 'flex' : ''}
|
|
>
|
|
{collection?._id && (
|
|
<RawSourceBox
|
|
collectionId={collection._id}
|
|
{...getCollectionSourceData(collection)}
|
|
fontSize={['sm', 'md']}
|
|
color={'black'}
|
|
textDecoration={'none'}
|
|
/>
|
|
)}
|
|
</Box>
|
|
{feConfigs?.isPlus && !!collection?.tags?.length && (
|
|
<TagsPopOver currentCollection={collection} />
|
|
)}
|
|
</Box>
|
|
{datasetDetail.type !== 'websiteDataset' && !!collection?.chunkSize && (
|
|
<Button
|
|
ml={2}
|
|
variant={'whitePrimary'}
|
|
size={['sm', 'md']}
|
|
onClick={() => {
|
|
router.push({
|
|
query: {
|
|
datasetId,
|
|
currentTab: TabEnum.import,
|
|
source: ImportDataSourceEnum.reTraining,
|
|
collectionId
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
{t('dataset:retain_collection')}
|
|
</Button>
|
|
)}
|
|
{canWrite && (
|
|
<Button
|
|
ml={2}
|
|
variant={'whitePrimary'}
|
|
size={['sm', 'md']}
|
|
isDisabled={!collection}
|
|
onClick={() => {
|
|
setEditDataId('');
|
|
}}
|
|
>
|
|
{t('common:dataset.Insert Data')}
|
|
</Button>
|
|
)}
|
|
</Flex>
|
|
<Box justifyContent={'center'} px={6} pos={'relative'} w={'100%'}>
|
|
<MyDivider my={'17px'} w={'100%'} />
|
|
</Box>
|
|
<Flex alignItems={'center'} px={6} pb={4}>
|
|
<Flex align={'center'} color={'myGray.500'}>
|
|
<MyIcon name="common/list" mr={2} w={'18px'} />
|
|
<Box as={'span'} fontSize={['sm', '14px']} fontWeight={'500'}>
|
|
{t('dataset:data_amount', {
|
|
dataAmount: total,
|
|
indexAmount: collection?.indexAmount ?? '-'
|
|
})}
|
|
</Box>
|
|
</Flex>
|
|
<Box flex={1} mr={1} />
|
|
<MyInput
|
|
leftIcon={
|
|
<MyIcon
|
|
name="common/searchLight"
|
|
position={'absolute'}
|
|
w={'14px'}
|
|
color={'myGray.600'}
|
|
/>
|
|
}
|
|
bg={'myGray.25'}
|
|
borderColor={'myGray.200'}
|
|
color={'myGray.500'}
|
|
w={['200px', '300px']}
|
|
placeholder={t('common:core.dataset.data.Search data placeholder')}
|
|
value={searchText}
|
|
onChange={(e) => {
|
|
setSearchText(e.target.value);
|
|
}}
|
|
/>
|
|
</Flex>
|
|
{/* data */}
|
|
<ScrollData px={5} pb={5}>
|
|
<Flex flexDir={'column'} gap={2}>
|
|
{datasetDataList.map((item, index) => (
|
|
<Card
|
|
key={item._id}
|
|
cursor={'pointer'}
|
|
p={3}
|
|
userSelect={'none'}
|
|
boxShadow={'none'}
|
|
bg={index % 2 === 1 ? 'myGray.50' : 'blue.50'}
|
|
border={theme.borders.sm}
|
|
position={'relative'}
|
|
overflow={'hidden'}
|
|
_hover={{
|
|
borderColor: 'blue.600',
|
|
boxShadow: 'lg',
|
|
'& .header': { visibility: 'visible' },
|
|
'& .footer': { visibility: 'visible' },
|
|
bg: index % 2 === 1 ? 'myGray.200' : 'blue.100'
|
|
}}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setEditDataId(item._id);
|
|
}}
|
|
>
|
|
{/* Data tag */}
|
|
<Flex
|
|
position={'absolute'}
|
|
zIndex={1}
|
|
alignItems={'center'}
|
|
visibility={'hidden'}
|
|
className="header"
|
|
>
|
|
<MyTag
|
|
px={2}
|
|
type="borderFill"
|
|
borderRadius={'sm'}
|
|
border={'1px'}
|
|
color={'myGray.200'}
|
|
bg={'white'}
|
|
fontWeight={'500'}
|
|
>
|
|
<Box color={'blue.600'}>#{item.chunkIndex ?? '-'} </Box>
|
|
<Box
|
|
ml={1.5}
|
|
className={'textEllipsis'}
|
|
fontSize={'mini'}
|
|
textAlign={'right'}
|
|
color={'myGray.500'}
|
|
>
|
|
ID:{item._id}
|
|
</Box>
|
|
</MyTag>
|
|
</Flex>
|
|
|
|
{/* Data content */}
|
|
<Box wordBreak={'break-all'} fontSize={'sm'}>
|
|
<Markdown source={item.q} isDisabled />
|
|
{!!item.a && (
|
|
<>
|
|
<MyDivider />
|
|
<Markdown source={item.a} isDisabled />
|
|
</>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Mask */}
|
|
<Flex
|
|
className="footer"
|
|
position={'absolute'}
|
|
bottom={2}
|
|
right={2}
|
|
overflow={'hidden'}
|
|
alignItems={'flex-end'}
|
|
visibility={'hidden'}
|
|
fontSize={'mini'}
|
|
>
|
|
<Flex
|
|
alignItems={'center'}
|
|
bg={'white'}
|
|
color={'myGray.600'}
|
|
borderRadius={'sm'}
|
|
border={'1px'}
|
|
borderColor={'myGray.200'}
|
|
h={'24px'}
|
|
px={2}
|
|
fontSize={'mini'}
|
|
boxShadow={'1'}
|
|
py={1}
|
|
mr={2}
|
|
>
|
|
<MyIcon
|
|
bg={'white'}
|
|
color={'myGray.600'}
|
|
borderRadius={'sm'}
|
|
border={'1px'}
|
|
borderColor={'myGray.200'}
|
|
name="common/text/t"
|
|
w={'14px'}
|
|
mr={1}
|
|
/>
|
|
{item.q.length + (item.a?.length || 0)}
|
|
</Flex>
|
|
{canWrite && (
|
|
<IconButton
|
|
display={'flex'}
|
|
p={1}
|
|
boxShadow={'1'}
|
|
icon={<MyIcon name={'common/trash'} w={'14px'} color={'myGray.600'} />}
|
|
variant={'whiteDanger'}
|
|
size={'xsSquare'}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onDeleteOneData(item._id);
|
|
}}
|
|
aria-label={''}
|
|
/>
|
|
)}
|
|
</Flex>
|
|
</Card>
|
|
))}
|
|
</Flex>
|
|
</ScrollData>
|
|
</Flex>
|
|
|
|
{editDataId !== undefined && collection && (
|
|
<InputDataModal
|
|
collectionId={collection._id}
|
|
dataId={editDataId}
|
|
onClose={() => setEditDataId(undefined)}
|
|
onSuccess={(data) => {
|
|
if (editDataId === '') {
|
|
refreshList();
|
|
return;
|
|
}
|
|
setDatasetDataList((prev) => {
|
|
return prev.map((item) => {
|
|
if (item._id === editDataId) {
|
|
return {
|
|
...item,
|
|
...data
|
|
};
|
|
}
|
|
return item;
|
|
});
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
<ConfirmModal />
|
|
</MyBox>
|
|
);
|
|
};
|
|
|
|
export default React.memo(DataCard);
|