mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 00:17:31 +00:00
Collection tag (#2266)
* feat: collection metadata filter (#2211) * feat: add dataset collection tags (#2231) * dataset page * workflow page * move * fix * add plus filter * fix * fix * fix * perf: collection tag code * fix: collection tags (#2249) * fix * fix * fix tags of dataset page * fix tags of workflow page * doc * add comments * fix: collection tags (#2264) * fix: metadata filter * feat: search filter --------- Co-authored-by: heheer <1239331448@qq.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -10,6 +10,7 @@ import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
|
||||
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
|
||||
|
||||
/* ===== dataset ===== */
|
||||
|
||||
@@ -18,6 +19,7 @@ export type GetDatasetCollectionsProps = RequestPaging & {
|
||||
datasetId: string;
|
||||
parentId?: string;
|
||||
searchText?: string;
|
||||
filterTags?: string[];
|
||||
simple?: boolean;
|
||||
selectFolder?: boolean;
|
||||
};
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import {
|
||||
DatasetCollectionSchemaType,
|
||||
DatasetDataSchemaType
|
||||
DatasetDataSchemaType,
|
||||
DatasetTagType
|
||||
} from '@fastgpt/global/core/dataset/type.d';
|
||||
import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller';
|
||||
|
||||
@@ -18,6 +19,7 @@ export type DatasetCollectionsListItemType = {
|
||||
updateTime: DatasetCollectionSchemaType['updateTime'];
|
||||
forbid?: DatasetCollectionSchemaType['forbid'];
|
||||
trainingType?: DatasetCollectionSchemaType['trainingType'];
|
||||
tags?: string[];
|
||||
|
||||
fileId?: string;
|
||||
rawLink?: string;
|
||||
|
@@ -20,6 +20,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
|
||||
parentId = null,
|
||||
searchText = '',
|
||||
selectFolder = false,
|
||||
filterTags = [],
|
||||
simple = false
|
||||
} = req.body as GetDatasetCollectionsProps;
|
||||
searchText = searchText?.replace(/'/g, '');
|
||||
@@ -43,7 +44,8 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
|
||||
? {
|
||||
name: new RegExp(searchText, 'i')
|
||||
}
|
||||
: {})
|
||||
: {}),
|
||||
...(filterTags.length ? { tags: { $in: filterTags } } : {})
|
||||
};
|
||||
|
||||
const selectField = {
|
||||
@@ -57,7 +59,8 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
|
||||
updateTime: 1,
|
||||
trainingType: 1,
|
||||
fileId: 1,
|
||||
rawLink: 1
|
||||
rawLink: 1,
|
||||
tags: 1
|
||||
};
|
||||
|
||||
// not count data amount
|
||||
@@ -68,6 +71,7 @@ async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectio
|
||||
updateTime: -1
|
||||
})
|
||||
.lean();
|
||||
|
||||
return {
|
||||
pageNum,
|
||||
pageSize,
|
||||
|
191
projects/app/src/pages/api/core/dataset/collection/scrollList.ts
Normal file
191
projects/app/src/pages/api/core/dataset/collection/scrollList.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
|
||||
|
||||
export type GetScrollCollectionsProps = PaginationProps<{
|
||||
datasetId: string;
|
||||
parentId?: string | null;
|
||||
searchText?: string;
|
||||
selectFolder?: boolean;
|
||||
filterTags?: string[];
|
||||
simple?: boolean;
|
||||
}>;
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<{}, GetScrollCollectionsProps>
|
||||
): Promise<PaginationResponse<DatasetCollectionsListItemType>> {
|
||||
let {
|
||||
datasetId,
|
||||
pageSize = 10,
|
||||
current = 1,
|
||||
parentId = null,
|
||||
searchText = '',
|
||||
selectFolder = false,
|
||||
filterTags = [],
|
||||
simple = false
|
||||
} = req.query;
|
||||
if (!datasetId) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
searchText = searchText?.replace(/'/g, '');
|
||||
pageSize = Math.min(pageSize, 30);
|
||||
|
||||
// auth dataset and get my role
|
||||
const { teamId, permission } = await authDataset({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
datasetId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const match = {
|
||||
teamId: new Types.ObjectId(teamId),
|
||||
datasetId: new Types.ObjectId(datasetId),
|
||||
parentId: parentId ? new Types.ObjectId(parentId) : null,
|
||||
...(selectFolder ? { type: DatasetCollectionTypeEnum.folder } : {}),
|
||||
...(searchText
|
||||
? {
|
||||
name: new RegExp(searchText, 'i')
|
||||
}
|
||||
: {}),
|
||||
...(filterTags.length ? { tags: { $all: filterTags } } : {})
|
||||
};
|
||||
|
||||
const selectField = {
|
||||
_id: 1,
|
||||
parentId: 1,
|
||||
tmbId: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
forbid: 1,
|
||||
createTime: 1,
|
||||
updateTime: 1,
|
||||
trainingType: 1,
|
||||
fileId: 1,
|
||||
rawLink: 1,
|
||||
tags: 1
|
||||
};
|
||||
|
||||
// not count data amount
|
||||
if (simple) {
|
||||
const collections = await MongoDatasetCollection.find(match)
|
||||
.select(selectField)
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
.skip(pageSize * (current - 1))
|
||||
.limit(pageSize)
|
||||
.lean();
|
||||
|
||||
return {
|
||||
list: await Promise.all(
|
||||
collections.map(async (item) => ({
|
||||
...item,
|
||||
dataAmount: 0,
|
||||
trainingAmount: 0,
|
||||
permission
|
||||
}))
|
||||
),
|
||||
total: await MongoDatasetCollection.countDocuments(match)
|
||||
};
|
||||
}
|
||||
|
||||
const [collections, total]: [DatasetCollectionsListItemType[], number] = await Promise.all([
|
||||
MongoDatasetCollection.aggregate([
|
||||
{
|
||||
$match: match
|
||||
},
|
||||
{
|
||||
$sort: { updateTime: -1 }
|
||||
},
|
||||
{
|
||||
$skip: (current - 1) * pageSize
|
||||
},
|
||||
{
|
||||
$limit: pageSize
|
||||
},
|
||||
// count training data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetTrainingCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [{ $eq: ['$teamId', '$$team_id'] }, { $eq: ['$collectionId', '$$id'] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'trainingCount'
|
||||
}
|
||||
},
|
||||
// count collection total data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetDataCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ['$teamId', '$$team_id'] },
|
||||
{ $eq: ['$datasetId', '$$dataset_id'] },
|
||||
{ $eq: ['$collectionId', '$$id'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'dataCount'
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
...selectField,
|
||||
dataAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$dataCount.count', 0] }, 0]
|
||||
},
|
||||
trainingAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$trainingCount.count', 0] }, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
]),
|
||||
MongoDatasetCollection.countDocuments(match)
|
||||
]);
|
||||
|
||||
const data = await Promise.all(
|
||||
collections.map(async (item) => ({
|
||||
...item,
|
||||
permission
|
||||
}))
|
||||
);
|
||||
|
||||
if (data.find((item) => item.trainingAmount > 0)) {
|
||||
startTrainingQueue();
|
||||
}
|
||||
|
||||
// count collections
|
||||
return {
|
||||
list: data,
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
@@ -10,11 +10,12 @@ export type UpdateDatasetCollectionParams = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
name?: string;
|
||||
tags?: string[];
|
||||
forbid?: boolean;
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
|
||||
const { id, parentId, name, forbid } = req.body;
|
||||
const { id, parentId, name, tags, forbid } = req.body;
|
||||
|
||||
if (!id) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
@@ -32,6 +33,7 @@ async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
|
||||
const updateFields: Record<string, any> = {
|
||||
...(parentId !== undefined && { parentId: parentId || null }),
|
||||
...(name && { name, updateTime: getCollectionUpdateTime({ name }) }),
|
||||
...(tags && { tags }),
|
||||
...(forbid !== undefined && { forbid })
|
||||
};
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import dynamic from 'next/dynamic';
|
||||
|
||||
import InputLabel from './Label';
|
||||
import type { RenderInputProps } from './type';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const RenderList: {
|
||||
types: FlowNodeInputTypeEnum[];
|
||||
@@ -74,7 +75,18 @@ type Props = {
|
||||
mb?: number;
|
||||
};
|
||||
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
|
||||
const copyInputs = useMemo(() => JSON.stringify(flowInputList), [flowInputList]);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const copyInputs = useMemo(
|
||||
() =>
|
||||
JSON.stringify(
|
||||
flowInputList.filter((input) => {
|
||||
if (input.isPro && !feConfigs?.isPlus) return false;
|
||||
return true;
|
||||
})
|
||||
),
|
||||
[feConfigs?.isPlus, flowInputList]
|
||||
);
|
||||
const filterInputs = useMemo(() => {
|
||||
return JSON.parse(copyInputs) as FlowNodeInputItemType[];
|
||||
}, [copyInputs]);
|
||||
|
@@ -90,6 +90,7 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
<MyPopover
|
||||
placement="bottom-end"
|
||||
offset={[20, 10]}
|
||||
p={4}
|
||||
trigger="hover"
|
||||
Trigger={
|
||||
<HStack
|
||||
|
@@ -29,6 +29,8 @@ type CollectionPageContextType = {
|
||||
pageSize: number;
|
||||
searchText: string;
|
||||
setSearchText: Dispatch<SetStateAction<string>>;
|
||||
filterTags: string[];
|
||||
setFilterTags: Dispatch<SetStateAction<string[]>>;
|
||||
};
|
||||
|
||||
export const CollectionPageContext = createContext<CollectionPageContextType>({
|
||||
@@ -52,6 +54,10 @@ export const CollectionPageContext = createContext<CollectionPageContextType>({
|
||||
searchText: '',
|
||||
setSearchText: function (value: SetStateAction<string>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
filterTags: [],
|
||||
setFilterTags: function (value: SetStateAction<string[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,6 +102,7 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
|
||||
// collection list
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [filterTags, setFilterTags] = useState<string[]>([]);
|
||||
const {
|
||||
data: collections,
|
||||
Pagination,
|
||||
@@ -110,7 +117,8 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
params: {
|
||||
datasetId,
|
||||
parentId,
|
||||
searchText
|
||||
searchText,
|
||||
filterTags
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
@@ -124,6 +132,8 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
|
||||
searchText,
|
||||
setSearchText,
|
||||
filterTags,
|
||||
setFilterTags,
|
||||
collections,
|
||||
Pagination,
|
||||
total,
|
||||
|
@@ -32,13 +32,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { CollectionPageContext } from './Context';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import HeaderTagPopOver from './HeaderTagPopOver';
|
||||
|
||||
const FileSourceSelector = dynamic(() => import('../Import/components/FileSourceSelector'));
|
||||
|
||||
const Header = ({}: {}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { setLoading, feConfigs } = useSystemStore();
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -154,7 +155,6 @@ const Header = ({}: {}) => {
|
||||
{isPc && (
|
||||
<Flex alignItems={'center'} mr={4}>
|
||||
<MyInput
|
||||
bg={'myGray.50'}
|
||||
w={['100%', '250px']}
|
||||
size={'sm'}
|
||||
h={'36px'}
|
||||
@@ -188,7 +188,9 @@ const Header = ({}: {}) => {
|
||||
|
||||
{/* diff collection button */}
|
||||
{datasetDetail.permission.hasWritePer && (
|
||||
<>
|
||||
<Flex gap={3}>
|
||||
{feConfigs?.isPlus && <HeaderTagPopOver />}
|
||||
|
||||
{datasetDetail?.type === DatasetTypeEnum.dataset && (
|
||||
<MyMenu
|
||||
offset={[0, 5]}
|
||||
@@ -369,7 +371,7 @@ const Header = ({}: {}) => {
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* modal */}
|
||||
|
@@ -0,0 +1,229 @@
|
||||
import { Box, Button, Checkbox, Flex, Input, useDisclosure } from '@chakra-ui/react';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { postCreateDatasetCollectionTag } from '@/web/core/dataset/api';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { CollectionPageContext } from './Context';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import TagManageModal from './TagManageModal';
|
||||
import { DatasetTagType } from '@fastgpt/global/core/dataset/type';
|
||||
|
||||
const HeaderTagPopOver = () => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTag, setSearchTag] = useState('');
|
||||
const [checkedTags, setCheckedTags] = useState<string[]>([]);
|
||||
|
||||
const { datasetDetail, datasetTags, loadDatasetTags, checkedDatasetTag, setCheckedDatasetTag } =
|
||||
useContextSelector(DatasetPageContext, (v) => v);
|
||||
|
||||
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({
|
||||
mutationFn: async (tag: string) => {
|
||||
const id = await postCreateDatasetCollectionTag({
|
||||
datasetId: datasetDetail._id,
|
||||
tag
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
setSearchTag('');
|
||||
},
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed')
|
||||
});
|
||||
|
||||
const { filterTags, setFilterTags, getData } = useContextSelector(
|
||||
CollectionPageContext,
|
||||
(v) => v
|
||||
);
|
||||
const debounceRefetch = useCallback(
|
||||
debounce(() => {
|
||||
getData(1);
|
||||
}, 300),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: searchTag });
|
||||
}, [searchTag]);
|
||||
|
||||
const {
|
||||
isOpen: isTagManageModalOpen,
|
||||
onOpen: onOpenTagManageModal,
|
||||
onClose: onCloseTagManageModal
|
||||
} = useDisclosure();
|
||||
|
||||
const checkTags = (tag: DatasetTagType) => {
|
||||
let currentCheckedTags = [];
|
||||
if (checkedTags.includes(tag._id)) {
|
||||
currentCheckedTags = checkedTags.filter((t) => t !== tag._id);
|
||||
setCheckedTags(currentCheckedTags);
|
||||
setCheckedDatasetTag(checkedDatasetTag.filter((t) => t._id !== tag._id));
|
||||
} else {
|
||||
currentCheckedTags = [...checkedTags, tag._id];
|
||||
setCheckedTags([...checkedTags, tag._id]);
|
||||
setCheckedDatasetTag([...checkedDatasetTag, tag]);
|
||||
}
|
||||
if (isEqual(currentCheckedTags, filterTags)) return;
|
||||
setFilterTags(currentCheckedTags);
|
||||
debounceRefetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyPopover
|
||||
placement="bottom"
|
||||
hasArrow={false}
|
||||
offset={[2, 2]}
|
||||
w={'180px'}
|
||||
closeOnBlur={true}
|
||||
trigger={'click'}
|
||||
Trigger={
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={3}
|
||||
py={2}
|
||||
w={['140px', '180px']}
|
||||
borderRadius={'md'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
cursor={'pointer'}
|
||||
overflow={'hidden'}
|
||||
h={['28px', '36px']}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<Flex flex={'1 0 0'}>
|
||||
{t('dataset:tag.tags')}
|
||||
<Box as={'span'}>
|
||||
{checkedTags.length > 0 && (
|
||||
<Box ml={1} fontSize={'xs'} color={'myGray.600'}>
|
||||
{`(${checkedTags.length})`}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<MyIcon name={'core/chat/chevronDown'} w={'14px'} />
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<MyBox isLoading={isCreateCollectionTagLoading} onClick={(e) => e.stopPropagation()}>
|
||||
<Box px={1.5} pt={1.5}>
|
||||
<Input
|
||||
pl={2}
|
||||
h={8}
|
||||
borderRadius={'4px'}
|
||||
value={searchTag}
|
||||
placeholder={t('dataset:tag.searchOrAddTag')}
|
||||
onChange={(e) => setSearchTag(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box my={1} px={1.5} maxH={'240px'} overflow={'auto'}>
|
||||
{searchTag && !datasetTags.map((item) => item.tag).includes(searchTag) && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'sm'}
|
||||
px={1}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#1118240D', color: 'primary.700' }}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => {
|
||||
onCreateCollectionTag(searchTag);
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'common/addLight'} w={'16px'} />
|
||||
<Box ml={2} py={2}>
|
||||
{t('dataset:tag.add') + ` "${searchTag}"`}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{[
|
||||
...new Map(
|
||||
[...checkedDatasetTag, ...datasetTags].map((item) => [item._id, item])
|
||||
).values()
|
||||
].map((item) => {
|
||||
const checked = checkedTags.includes(item._id);
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'sm'}
|
||||
px={1}
|
||||
py={1}
|
||||
my={1}
|
||||
cursor={'pointer'}
|
||||
bg={checked ? '#1118240D' : 'transparent'}
|
||||
color={checked ? 'primary.700' : 'myGray.600'}
|
||||
_hover={{ bg: '#1118240D', color: 'primary.700' }}
|
||||
borderRadius={'xs'}
|
||||
key={item._id}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
checkTags(item);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={checkedTags.includes(item._id)}
|
||||
onChange={(e) => {
|
||||
checkTags(item);
|
||||
}}
|
||||
size={'md'}
|
||||
/>
|
||||
<Box ml={2}>{item.tag}</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<Flex borderTop={'1px solid #E8EBF0'} color={'myGray.600'}>
|
||||
<Button
|
||||
w={'full'}
|
||||
fontSize={'sm'}
|
||||
_hover={{ bg: '#1118240D', color: 'primary.700' }}
|
||||
borderRadius={'none'}
|
||||
variant={'unstyled'}
|
||||
onClick={() => {
|
||||
setCheckedTags([]);
|
||||
setFilterTags([]);
|
||||
debounceRefetch();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('dataset:tag.cancel')}
|
||||
</Button>
|
||||
<Box w={'1px'} bg={'myGray.200'}></Box>
|
||||
<Button
|
||||
w={'full'}
|
||||
fontSize={'sm'}
|
||||
_hover={{ bg: '#1118240D', color: 'primary.700' }}
|
||||
borderRadius={'none'}
|
||||
variant={'unstyled'}
|
||||
onClick={() => {
|
||||
onOpenTagManageModal();
|
||||
setCheckedTags([]);
|
||||
}}
|
||||
>
|
||||
{t('dataset:tag.manage')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
)}
|
||||
</MyPopover>
|
||||
{isTagManageModalOpen && (
|
||||
<TagManageModal
|
||||
onClose={() => {
|
||||
onCloseTagManageModal();
|
||||
debounceRefetch();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderTagPopOver;
|
@@ -0,0 +1,530 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Input, Button, Flex, Box, Checkbox } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { CollectionPageContext } from './Context';
|
||||
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import {
|
||||
delDatasetCollectionTag,
|
||||
getDatasetCollectionTags,
|
||||
getScrollCollectionList,
|
||||
getTagUsage,
|
||||
postAddTagsToCollections,
|
||||
postCreateDatasetCollectionTag,
|
||||
updateDatasetCollectionTag
|
||||
} from '@/web/core/dataset/api';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyInput from '@/components/MyInput';
|
||||
import { DatasetTagType } from '@fastgpt/global/core/dataset/type';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const TagManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const loadDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadDatasetTags);
|
||||
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags);
|
||||
const { getData } = useContextSelector(CollectionPageContext, (v) => v);
|
||||
|
||||
const tagInputRef = useRef<HTMLInputElement>(null);
|
||||
const editInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [currentAddTag, setCurrentAddTag] = useState<
|
||||
(DatasetTagType & { collections: string[] }) | undefined
|
||||
>(undefined);
|
||||
|
||||
const [newTag, setNewTag] = useState<string | undefined>(undefined);
|
||||
|
||||
const [currentEditTagContent, setCurrentEditTagContent] = useState<string | undefined>(undefined);
|
||||
const [currentEditTag, setCurrentEditTag] = useState<DatasetTagType | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (newTag !== undefined && tagInputRef.current) {
|
||||
tagInputRef.current?.focus();
|
||||
}
|
||||
}, [newTag]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentEditTag !== undefined && editInputRef.current) {
|
||||
editInputRef.current?.focus();
|
||||
}
|
||||
}, [currentEditTag]);
|
||||
|
||||
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({
|
||||
mutationFn: async (tag: string) => {
|
||||
const id = await postCreateDatasetCollectionTag({
|
||||
datasetId: datasetDetail._id,
|
||||
tag
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
fetchData(1);
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
|
||||
loadAllDatasetTags({ id: datasetDetail._id });
|
||||
},
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed')
|
||||
});
|
||||
|
||||
const { mutate: onDeleteCollectionTag, isLoading: isDeleteCollectionTagLoading } = useRequest({
|
||||
mutationFn: async (tag: string) => {
|
||||
const id = await delDatasetCollectionTag({
|
||||
datasetId: datasetDetail._id,
|
||||
id: tag
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
fetchData(1);
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
|
||||
loadAllDatasetTags({ id: datasetDetail._id });
|
||||
},
|
||||
successToast: t('common:common.Delete Success'),
|
||||
errorToast: t('common:common.Delete Failed')
|
||||
});
|
||||
|
||||
const { mutate: onUpdateCollectionTag, isLoading: isUpdateCollectionTagLoading } = useRequest({
|
||||
mutationFn: async (tag: DatasetTagType) => {
|
||||
const id = await updateDatasetCollectionTag({
|
||||
datasetId: datasetDetail._id,
|
||||
tagId: tag._id,
|
||||
tag: tag.tag
|
||||
});
|
||||
return id;
|
||||
},
|
||||
onSuccess() {
|
||||
fetchData(1);
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
|
||||
loadAllDatasetTags({ id: datasetDetail._id });
|
||||
}
|
||||
});
|
||||
|
||||
const { mutate: onSaveCollectionTag, isLoading: isSaveCollectionTagLoading } = useRequest({
|
||||
mutationFn: async ({
|
||||
tag,
|
||||
originCollectionIds,
|
||||
collectionIds
|
||||
}: {
|
||||
tag: string;
|
||||
originCollectionIds: string[];
|
||||
collectionIds: string[];
|
||||
}) => {
|
||||
try {
|
||||
await postAddTagsToCollections({
|
||||
tag,
|
||||
originCollectionIds,
|
||||
collectionIds,
|
||||
datasetId: datasetDetail._id
|
||||
});
|
||||
} catch (error) {}
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
getData(1);
|
||||
},
|
||||
successToast: t('common:common.Save Success'),
|
||||
errorToast: t('common:common.Save Failed')
|
||||
});
|
||||
|
||||
const {
|
||||
list,
|
||||
ScrollList,
|
||||
isLoading: isRequesting,
|
||||
fetchData,
|
||||
total: tagsTotal
|
||||
} = useScrollPagination(getDatasetCollectionTags, {
|
||||
refreshDeps: [''],
|
||||
debounceWait: 300,
|
||||
|
||||
itemHeight: 56,
|
||||
overscan: 10,
|
||||
|
||||
pageSize: 10,
|
||||
defaultParams: {
|
||||
datasetId: datasetDetail._id,
|
||||
searchText: ''
|
||||
}
|
||||
});
|
||||
|
||||
const { data: tagUsages } = useRequest2(() => getTagUsage(datasetDetail._id), {
|
||||
manual: false
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="core/dataset/tag"
|
||||
title={t('dataset:tag.manage')}
|
||||
w={'580px'}
|
||||
h={'600px'}
|
||||
isLoading={
|
||||
isRequesting ||
|
||||
isCreateCollectionTagLoading ||
|
||||
isDeleteCollectionTagLoading ||
|
||||
isUpdateCollectionTagLoading ||
|
||||
isSaveCollectionTagLoading
|
||||
}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
{currentAddTag === undefined ? (
|
||||
<>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
color={'myGray.900'}
|
||||
pb={2}
|
||||
borderBottom={'1px solid #E8EBF0'}
|
||||
mx={8}
|
||||
pt={6}
|
||||
>
|
||||
<MyIcon name="menu" w={5} />
|
||||
<Box ml={2} fontWeight={'semibold'} flex={'1 0 0'}>{`共${tagsTotal}个标签`}</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name="common/addLight" w={4} />}
|
||||
variant={'outline'}
|
||||
fontSize={'xs'}
|
||||
onClick={() => {
|
||||
setNewTag('');
|
||||
}}
|
||||
>
|
||||
{t('dataset:tag.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<ScrollList
|
||||
px={8}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'sm'}
|
||||
EmptyChildren={<EmptyTip text={t('dataset:dataset.no_tags')} />}
|
||||
>
|
||||
{newTag !== undefined && (
|
||||
<Flex p={2} borderBottom={'1px solid #E8EBF0'}>
|
||||
<Input
|
||||
placeholder={t('dataset:tag.Add_new_tag')}
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
ref={tagInputRef}
|
||||
w={'200px'}
|
||||
onBlur={() => {
|
||||
if (newTag && !list.map((item) => item.data.tag).includes(newTag)) {
|
||||
onCreateCollectionTag(newTag);
|
||||
}
|
||||
setNewTag(undefined);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{list.map((listItem) => {
|
||||
const item = listItem.data;
|
||||
const tagUsage = tagUsages?.find((tagUsage) => tagUsage.tagId === item._id);
|
||||
const collections = tagUsage?.collections || [];
|
||||
const usage = collections.length;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
py={2}
|
||||
borderBottom={'1px solid #E8EBF0'}
|
||||
sx={{
|
||||
'&:hover .icon-box': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
key={item._id}
|
||||
>
|
||||
<Flex
|
||||
px={2}
|
||||
py={1}
|
||||
flex={'1'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
alignItems={'center'}
|
||||
borderRadius={'4px'}
|
||||
>
|
||||
<Flex
|
||||
flex={'1 0 0'}
|
||||
alignItems={'center'}
|
||||
onClick={() => {
|
||||
setCurrentAddTag({ ...item, collections });
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
{currentEditTag?._id !== item._id ? (
|
||||
<Box
|
||||
px={3}
|
||||
py={1.5}
|
||||
bg={'#DBF3FF'}
|
||||
color={'#0884DD'}
|
||||
fontSize={'xs'}
|
||||
borderRadius={'6px'}
|
||||
>
|
||||
{item.tag}
|
||||
</Box>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={t('dataset:tag.Edit_tag')}
|
||||
value={
|
||||
currentEditTagContent !== undefined ? currentEditTagContent : item.tag
|
||||
}
|
||||
onChange={(e) => setCurrentEditTagContent(e.target.value)}
|
||||
ref={editInputRef}
|
||||
w={'200px'}
|
||||
onBlur={() => {
|
||||
if (
|
||||
currentEditTagContent &&
|
||||
!list.map((item) => item.data.tag).includes(currentEditTagContent)
|
||||
) {
|
||||
onUpdateCollectionTag({
|
||||
tag: currentEditTagContent,
|
||||
_id: item._id
|
||||
});
|
||||
}
|
||||
setCurrentEditTag(undefined);
|
||||
setCurrentEditTagContent(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box as={'span'} color={'myGray.500'} ml={2}>{`(${usage})`}</Box>
|
||||
</Flex>
|
||||
<Box
|
||||
className="icon-box"
|
||||
display="none"
|
||||
_hover={{ bg: '#1118240D' }}
|
||||
mr={2}
|
||||
p={1}
|
||||
borderRadius={'6px'}
|
||||
onClick={() => {
|
||||
setCurrentAddTag({ ...item, collections });
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<MyIcon name="common/addLight" w={4} />
|
||||
</Box>
|
||||
<Box
|
||||
className="icon-box"
|
||||
display="none"
|
||||
_hover={{ bg: '#1118240D' }}
|
||||
mr={2}
|
||||
p={1}
|
||||
borderRadius={'6px'}
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
setCurrentEditTag(item);
|
||||
editInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<MyIcon name="edit" w={4} />
|
||||
</Box>
|
||||
<PopoverConfirm
|
||||
showCancel
|
||||
content={t('dataset:tag.delete_tag_confirm')}
|
||||
type="delete"
|
||||
Trigger={
|
||||
<Box
|
||||
className="icon-box"
|
||||
display="none"
|
||||
_hover={{ bg: '#1118240D' }}
|
||||
p={1}
|
||||
borderRadius={'6px'}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<MyIcon name="delete" w={4} />
|
||||
</Box>
|
||||
}
|
||||
onConfirm={() => onDeleteCollectionTag(item._id)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
</>
|
||||
) : (
|
||||
<AddTagToCollections
|
||||
currentAddTag={currentAddTag}
|
||||
setCurrentAddTag={setCurrentAddTag}
|
||||
onSaveCollectionTag={onSaveCollectionTag}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagManageModal;
|
||||
|
||||
const AddTagToCollections = ({
|
||||
currentAddTag,
|
||||
setCurrentAddTag,
|
||||
onSaveCollectionTag
|
||||
}: {
|
||||
currentAddTag: DatasetTagType & { collections: string[] };
|
||||
setCurrentAddTag: (tag: (DatasetTagType & { collections: string[] }) | undefined) => void;
|
||||
onSaveCollectionTag: ({
|
||||
tag,
|
||||
originCollectionIds,
|
||||
collectionIds
|
||||
}: {
|
||||
tag: string;
|
||||
originCollectionIds: string[];
|
||||
collectionIds: string[];
|
||||
}) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const [selectedCollections, setSelectedCollections] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCollections(currentAddTag.collections);
|
||||
}, []);
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const {
|
||||
list: collectionsList,
|
||||
ScrollList: ScrollListCollections,
|
||||
isLoading: isCollectionLoading
|
||||
} = useScrollPagination(getScrollCollectionList, {
|
||||
refreshDeps: [searchText],
|
||||
debounceWait: 300,
|
||||
|
||||
itemHeight: 29,
|
||||
overscan: 10,
|
||||
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
datasetId: datasetDetail._id,
|
||||
searchText
|
||||
}
|
||||
});
|
||||
|
||||
const formatCollections = useMemo(
|
||||
() =>
|
||||
collectionsList.map((item) => {
|
||||
const collection = item.data;
|
||||
const icon = getCollectionIcon(collection.type, collection.name);
|
||||
return {
|
||||
id: collection._id,
|
||||
tags: collection.tags,
|
||||
name: collection.name,
|
||||
icon
|
||||
};
|
||||
}),
|
||||
[collectionsList]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyBox flex={'1 0 0'} isLoading={isCollectionLoading}>
|
||||
<Flex alignItems={'center'} pb={2} mx={8} pt={6} borderBottom={'1px solid #E8EBF0'}>
|
||||
<MyIcon
|
||||
name="common/backFill"
|
||||
w={4}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setCurrentAddTag(undefined);
|
||||
setSearchText('');
|
||||
}}
|
||||
/>
|
||||
{
|
||||
<Flex alignItems={'center'}>
|
||||
<Box
|
||||
ml={2}
|
||||
px={3}
|
||||
py={1.5}
|
||||
bg={'#DBF3FF'}
|
||||
color={'#0884DD'}
|
||||
fontSize={'sm'}
|
||||
borderRadius={'6px'}
|
||||
>
|
||||
{currentAddTag.tag}
|
||||
</Box>
|
||||
<Box
|
||||
as={'span'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.500'}
|
||||
ml={2}
|
||||
>{`(${selectedCollections.length})`}</Box>
|
||||
</Flex>
|
||||
}
|
||||
<Box flex={'1 0 0'}></Box>
|
||||
<MyInput
|
||||
placeholder={t('common:common.Search')}
|
||||
w={'200px'}
|
||||
mr={2}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={<MyIcon name="save" w={4} />}
|
||||
onClick={() => {
|
||||
onSaveCollectionTag({
|
||||
tag: currentAddTag._id,
|
||||
originCollectionIds: currentAddTag.collections,
|
||||
collectionIds: selectedCollections
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common:common.Save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<ScrollListCollections
|
||||
px={8}
|
||||
mt={2}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'sm'}
|
||||
EmptyChildren={<EmptyTip text={t('dataset:dataset.no_collections')} />}
|
||||
>
|
||||
{formatCollections.map((collection) => {
|
||||
return (
|
||||
<Flex
|
||||
px={2}
|
||||
py={1}
|
||||
flex={'1'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
alignItems={'center'}
|
||||
borderRadius={'4px'}
|
||||
key={collection.id}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setSelectedCollections((prev) => {
|
||||
if (prev.includes(collection.id)) {
|
||||
return prev.filter((id) => id !== collection.id);
|
||||
} else {
|
||||
return [...prev, collection.id];
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size={'md'}
|
||||
mr={2}
|
||||
onChange={() => {
|
||||
setSelectedCollections((prev) => {
|
||||
if (prev.includes(collection.id)) {
|
||||
return prev.filter((id) => id !== collection.id);
|
||||
} else {
|
||||
return [...prev, collection.id];
|
||||
}
|
||||
});
|
||||
}}
|
||||
isChecked={selectedCollections.includes(collection.id)}
|
||||
/>
|
||||
<MyIcon name={collection.icon as any} w={'16px'} mr={2} />
|
||||
<Box fontSize={'14px'} borderRadius={'6px'}>
|
||||
{collection.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollListCollections>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
@@ -0,0 +1,287 @@
|
||||
import { Box, Checkbox, Flex, Input } from '@chakra-ui/react';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { postCreateDatasetCollectionTag, putDatasetCollectionById } from '@/web/core/dataset/api';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
import { DatasetCollectionItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type';
|
||||
import { isEqual } from 'lodash';
|
||||
import { DatasetCollectionsListItemType } from '@/global/core/dataset/type';
|
||||
|
||||
const TagsPopOver = ({
|
||||
currentCollection
|
||||
}: {
|
||||
currentCollection: DatasetCollectionItemType | DatasetCollectionsListItemType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const datasetTags = useContextSelector(DatasetPageContext, (v) => v.datasetTags);
|
||||
const loadDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadDatasetTags);
|
||||
const allDatasetTags = useContextSelector(DatasetPageContext, (v) => v.allDatasetTags);
|
||||
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags);
|
||||
|
||||
const [collectionTags, setCollectionTags] = useState<string[]>([]);
|
||||
const [searchTag, setSearchTag] = useState('');
|
||||
const [checkedTags, setCheckedTags] = useState<DatasetTagType[]>([]);
|
||||
|
||||
const [showTagManage, setShowTagManage] = useState(false);
|
||||
const [isFocusInput, setIsFocusInput] = useState(false);
|
||||
const [isUpdateLoading, setIsUpdateLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentCollection.tags) return;
|
||||
setCollectionTags(currentCollection.tags);
|
||||
}, [currentCollection]);
|
||||
|
||||
const tagList = useMemo(
|
||||
() =>
|
||||
(collectionTags
|
||||
?.map((tagId) => {
|
||||
const tagObject = allDatasetTags.find((tag) => tag._id === tagId);
|
||||
return tagObject ? { _id: tagObject._id, tag: tagObject.tag } : null;
|
||||
})
|
||||
.filter((tag) => tag !== null) as {
|
||||
_id: string;
|
||||
tag: string;
|
||||
}[]) || [],
|
||||
[collectionTags, allDatasetTags]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFocusInput) return;
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: searchTag });
|
||||
}, [datasetDetail._id, isFocusInput, loadDatasetTags, searchTag]);
|
||||
|
||||
const [visibleTags, setVisibleTags] = useState<DatasetTagType[]>(tagList);
|
||||
const [overflowTags, setOverflowTags] = useState<DatasetTagType[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
const calculateTags = () => {
|
||||
if (!containerRef.current || !tagList) return;
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth;
|
||||
const tagWidth = 11;
|
||||
let totalWidth = 30;
|
||||
let visibleCount = 0;
|
||||
|
||||
for (let i = 0; i < tagList.length; i++) {
|
||||
const tag = tagList[i];
|
||||
const estimatedWidth = tag.tag.length * tagWidth + 16; // 加上左右 padding 的宽度
|
||||
if (totalWidth + estimatedWidth <= containerWidth) {
|
||||
totalWidth += estimatedWidth;
|
||||
visibleCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setVisibleTags(tagList.slice(0, visibleCount));
|
||||
setOverflowTags(tagList.slice(visibleCount));
|
||||
};
|
||||
|
||||
setTimeout(calculateTags, 100);
|
||||
setCheckedTags(tagList);
|
||||
|
||||
window.addEventListener('resize', calculateTags);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', calculateTags);
|
||||
};
|
||||
}, [tagList]);
|
||||
|
||||
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({
|
||||
mutationFn: async (tag: string) => {
|
||||
const id = await postCreateDatasetCollectionTag({
|
||||
datasetId: datasetDetail._id,
|
||||
tag
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
setSearchTag('');
|
||||
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
|
||||
loadAllDatasetTags({ id: datasetDetail._id });
|
||||
},
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyPopover
|
||||
placement={showTagManage ? 'bottom' : 'bottom-end'}
|
||||
hasArrow={false}
|
||||
offset={[2, 2]}
|
||||
w={'180px'}
|
||||
trigger={'hover'}
|
||||
Trigger={
|
||||
<MyBox
|
||||
ref={containerRef}
|
||||
display={'flex'}
|
||||
isLoading={isUpdateLoading}
|
||||
size={'xs'}
|
||||
mt={1}
|
||||
py={0.5}
|
||||
px={0.25}
|
||||
_hover={{
|
||||
bg: 'myGray.50',
|
||||
borderRadius: '3px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!e.currentTarget.parentElement || !e.currentTarget.parentElement.parentElement)
|
||||
return;
|
||||
e.currentTarget.parentElement.parentElement.style.backgroundColor = 'white';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!e.currentTarget.parentElement || !e.currentTarget.parentElement.parentElement)
|
||||
return;
|
||||
e.currentTarget.parentElement.parentElement.style.backgroundColor = '';
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowTagManage(true);
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<Flex>
|
||||
{visibleTags.map((item, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
h={5}
|
||||
mr={1}
|
||||
px={2}
|
||||
fontSize={'11px'}
|
||||
bg={'#F0FBFF'}
|
||||
color={'#0884DD'}
|
||||
borderRadius={'4px'}
|
||||
>
|
||||
{item.tag}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
{overflowTags.length > 0 && (
|
||||
<Box h={5} px={2} bg={'#1118240D'} borderRadius={'33px'} fontSize={'11px'}>
|
||||
{`+${overflowTags.length}`}
|
||||
</Box>
|
||||
)}
|
||||
</MyBox>
|
||||
}
|
||||
onCloseFunc={async () => {
|
||||
setShowTagManage(false);
|
||||
if (isEqual(checkedTags, tagList) || !showTagManage) return;
|
||||
setIsUpdateLoading(true);
|
||||
await putDatasetCollectionById({
|
||||
id: currentCollection._id,
|
||||
tags: checkedTags.map((tag) => tag._id)
|
||||
});
|
||||
setCollectionTags(checkedTags.map((tag) => tag._id));
|
||||
setIsUpdateLoading(false);
|
||||
}}
|
||||
display={showTagManage || overflowTags.length > 0 ? 'block' : 'none'}
|
||||
>
|
||||
{({}) => (
|
||||
<>
|
||||
{showTagManage ? (
|
||||
<MyBox isLoading={isCreateCollectionTagLoading} onClick={(e) => e.stopPropagation()}>
|
||||
<Box px={1.5} pt={1.5}>
|
||||
<Input
|
||||
onFocus={() => setIsFocusInput(true)}
|
||||
onBlur={() => setIsFocusInput(false)}
|
||||
pl={2}
|
||||
h={7}
|
||||
borderRadius={'4px'}
|
||||
value={searchTag}
|
||||
placeholder={t('dataset:tag.searchOrAddTag')}
|
||||
onChange={(e) => setSearchTag(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box my={1} px={1.5} maxH={'200px'} overflow={'auto'}>
|
||||
{searchTag && !datasetTags.map((item) => item.tag).includes(searchTag) && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
px={1}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#1118240D', color: '#2B5FD9' }}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => {
|
||||
onCreateCollectionTag(searchTag);
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'common/addLight'} w={'14px'} />
|
||||
<Box ml={1} py={1}>
|
||||
{t('dataset:tag.add') + ` "${searchTag}"`}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{datasetTags?.map((item) => {
|
||||
const tagsList = checkedTags.map((tag) => tag.tag);
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
px={1}
|
||||
py={0.5}
|
||||
my={0.5}
|
||||
key={item._id}
|
||||
cursor={'pointer'}
|
||||
bg={tagsList.includes(item.tag) ? '#1118240D' : 'transparent'}
|
||||
color={tagsList.includes(item.tag) ? '#2B5FD9' : 'myGray.600'}
|
||||
_hover={{ bg: '#1118240D', color: '#2B5FD9' }}
|
||||
borderRadius={'xs'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (tagsList.includes(item.tag)) {
|
||||
setCheckedTags(checkedTags.filter((t) => t.tag !== item.tag));
|
||||
} else {
|
||||
setCheckedTags([...checkedTags, item]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={tagsList.includes(item.tag)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setCheckedTags([...checkedTags, item]);
|
||||
} else {
|
||||
setCheckedTags(checkedTags.filter((t) => t._id !== item._id));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box ml={1}>{item.tag}</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</MyBox>
|
||||
) : (
|
||||
<Flex gap={1} p={3} flexWrap={'wrap'}>
|
||||
{overflowTags.map((tag, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
h={5}
|
||||
px={2}
|
||||
fontSize={'11px'}
|
||||
bg={'#F0FBFF'}
|
||||
color={'#0884DD'}
|
||||
borderRadius={'4px'}
|
||||
>
|
||||
{tag.tag}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</MyPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsPopOver;
|
@@ -49,6 +49,8 @@ import {
|
||||
getTrainingTypeLabel
|
||||
} from '@fastgpt/global/core/dataset/collection/utils';
|
||||
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
|
||||
import TagsPopOver from './TagsPopOver';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const Header = dynamic(() => import('./Header'));
|
||||
const EmptyCollectionTip = dynamic(() => import('./EmptyCollectionTip'));
|
||||
@@ -60,6 +62,7 @@ const CollectionCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { datasetT } = useI18n();
|
||||
const { datasetDetail, loadDatasetDetail } = useContextSelector(DatasetPageContext, (v) => v);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { openConfirm: openDeleteConfirm, ConfirmModal: ConfirmDeleteModal } = useConfirm({
|
||||
content: t('common:dataset.Confirm to delete the file'),
|
||||
@@ -244,6 +247,9 @@ const CollectionCard = () => {
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
{feConfigs?.isPlus && !!collection.tags?.length && (
|
||||
<TagsPopOver currentCollection={collection} />
|
||||
)}
|
||||
</Td>
|
||||
<Td py={2}>
|
||||
{!checkCollectionIsFolder(collection.type) ? (
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState, useRef, useMemo } from 'react';
|
||||
import React, { useState, useRef, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@@ -14,8 +14,7 @@ import {
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
useDisclosure,
|
||||
HStack,
|
||||
Switch
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
getDatasetDataList,
|
||||
@@ -26,19 +25,16 @@ import {
|
||||
import { DeleteIcon } from '@chakra-ui/icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { debounce } from 'lodash';
|
||||
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 { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import InputDataModal from '../components/InputDataModal';
|
||||
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
import type { DatasetDataListItemType } from '@/global/core/dataset/type.d';
|
||||
import { TabEnum } from '..';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { DatasetCollectionTypeMap, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { formatFileSize } from '@fastgpt/global/common/file/tools';
|
||||
@@ -54,6 +50,8 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
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';
|
||||
|
||||
const DataCard = () => {
|
||||
const BoxRef = useRef<HTMLDivElement>(null);
|
||||
@@ -66,6 +64,7 @@ const DataCard = () => {
|
||||
datasetId: string;
|
||||
};
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { datasetT } = useI18n();
|
||||
@@ -224,28 +223,34 @@ const DataCard = () => {
|
||||
}
|
||||
/>
|
||||
<Flex className="textEllipsis" flex={'1 0 0'} mr={[3, 5]} alignItems={'center'}>
|
||||
<Box lineHeight={1.2}>
|
||||
{collection?._id && (
|
||||
<RawSourceBox
|
||||
collectionId={collection._id}
|
||||
{...getCollectionSourceData(collection)}
|
||||
fontSize={['sm', 'md']}
|
||||
color={'black'}
|
||||
textDecoration={'none'}
|
||||
/>
|
||||
)}
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('common:core.dataset.collection.id')}:{' '}
|
||||
<Box as={'span'} userSelect={'all'}>
|
||||
{collection?._id}
|
||||
<Box>
|
||||
<Box alignItems={'center'} gap={2} display={isPc ? 'flex' : ''}>
|
||||
{collection?._id && (
|
||||
<RawSourceBox
|
||||
collectionId={collection._id}
|
||||
{...getCollectionSourceData(collection)}
|
||||
fontSize={['sm', 'md']}
|
||||
color={'black'}
|
||||
textDecoration={'none'}
|
||||
/>
|
||||
)}
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('common:core.dataset.collection.id')}:{' '}
|
||||
<Box as={'span'} userSelect={'all'}>
|
||||
{collection?._id}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{feConfigs?.isPlus && !!collection?.tags?.length && (
|
||||
<TagsPopOver currentCollection={collection} />
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{canWrite && (
|
||||
<Box>
|
||||
<Button
|
||||
mx={2}
|
||||
ml={2}
|
||||
mr={isPc ? 2 : 0}
|
||||
variant={'whitePrimary'}
|
||||
size={['sm', 'md']}
|
||||
onClick={() => {
|
||||
|
@@ -150,7 +150,7 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
<Box mb={3}>
|
||||
<LightRowTabs<TabEnum>
|
||||
m={'auto'}
|
||||
w={'260px'}
|
||||
w={'full'}
|
||||
size={isPc ? 'md' : 'sm'}
|
||||
list={tabList}
|
||||
value={currentTab}
|
||||
|
@@ -18,6 +18,7 @@ import {
|
||||
import CollectionPageContextProvider from './components/CollectionCard/Context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const CollectionCard = dynamic(() => import('./components/CollectionCard/index'));
|
||||
const DataCard = dynamic(() => import('./components/DataCard'));
|
||||
@@ -40,21 +41,26 @@ const Detail = ({ datasetId, currentTab }: Props) => {
|
||||
const router = useRouter();
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const loadDatasetDetail = useContextSelector(DatasetPageContext, (v) => v.loadDatasetDetail);
|
||||
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags);
|
||||
|
||||
useQuery([datasetId], () => loadDatasetDetail(datasetId), {
|
||||
useRequest2(() => loadDatasetDetail(datasetId), {
|
||||
onSuccess: () => {
|
||||
loadAllDatasetTags({ id: datasetId });
|
||||
},
|
||||
onError(err: any) {
|
||||
router.replace(`/dataset/list`);
|
||||
toast({
|
||||
title: t(getErrText(err, t('common:common.Load Failed')) as any),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
manual: false
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextHead title={datasetDetail?.name} icon={datasetDetail?.avatar} />
|
||||
<PageContainer>
|
||||
<PageContainer insertProps={{ bg: 'white' }}>
|
||||
<MyBox display={'flex'} flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
|
||||
<Slider currentTab={currentTab} />
|
||||
|
||||
|
@@ -6,18 +6,23 @@ import type {
|
||||
import type {
|
||||
DatasetItemType,
|
||||
DatasetListItemType,
|
||||
DatasetSimpleItemType
|
||||
DatasetSimpleItemType,
|
||||
DatasetTagType,
|
||||
TagUsageType
|
||||
} from '@fastgpt/global/core/dataset/type.d';
|
||||
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq.d';
|
||||
import type {
|
||||
AddTagsToCollectionsParams,
|
||||
CreateDatasetCollectionParams,
|
||||
CreateDatasetCollectionTagParams,
|
||||
CsvTableCreateDatasetCollectionParams,
|
||||
DatasetUpdateBody,
|
||||
ExternalFileCreateDatasetCollectionParams,
|
||||
FileIdCreateDatasetCollectionParams,
|
||||
LinkCreateDatasetCollectionParams,
|
||||
PostWebsiteSyncParams,
|
||||
TextCreateDatasetCollectionParams
|
||||
TextCreateDatasetCollectionParams,
|
||||
UpdateDatasetCollectionTagParams
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import type {
|
||||
GetTrainingQueueProps,
|
||||
@@ -43,6 +48,8 @@ import type { UpdateDatasetCollectionParams } from '@/pages/api/core/dataset/col
|
||||
import type { GetDatasetDataListProps } from '@/pages/api/core/dataset/data/list';
|
||||
import type { UpdateDatasetDataProps } from '@fastgpt/global/core/dataset/controller';
|
||||
import type { DatasetFolderCreateBody } from '@/pages/api/core/dataset/folder/create';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { GetScrollCollectionsProps } from '@/pages/api/core/dataset/collection/scrollList';
|
||||
|
||||
/* ======================== dataset ======================= */
|
||||
export const getDatasets = (data: GetDatasetListBody) =>
|
||||
@@ -117,6 +124,32 @@ export const postLinkCollectionSync = (collectionId: string) =>
|
||||
collectionId
|
||||
});
|
||||
|
||||
/* =============================== tag ==================================== */
|
||||
|
||||
export const postCreateDatasetCollectionTag = (data: CreateDatasetCollectionTagParams) =>
|
||||
POST(`/proApi/core/dataset/tag/create`, data);
|
||||
export const postAddTagsToCollections = (data: AddTagsToCollectionsParams) =>
|
||||
POST(`/proApi/core/dataset/tag/addToCollections`, data);
|
||||
export const delDatasetCollectionTag = (data: { id: string; datasetId: string }) =>
|
||||
DELETE(`/proApi/core/dataset/tag/delete`, data);
|
||||
export const updateDatasetCollectionTag = (data: UpdateDatasetCollectionTagParams) =>
|
||||
POST(`/proApi/core/dataset/tag/update`, data);
|
||||
export const getDatasetCollectionTags = (
|
||||
data: PaginationProps<{
|
||||
datasetId: string;
|
||||
searchText?: string;
|
||||
}>
|
||||
) => GET<PaginationResponse<DatasetTagType>>(`/proApi/core/dataset/tag/list`, data);
|
||||
export const getTagUsage = (datasetId: string) =>
|
||||
GET<TagUsageType[]>(`/proApi/core/dataset/tag/tagUsage?datasetId=${datasetId}`);
|
||||
export const getAllTags = (datasetId: string) =>
|
||||
GET<{ list: DatasetTagType[] }>(`/proApi/core/dataset/tag/getAllTags?datasetId=${datasetId}`);
|
||||
export const getScrollCollectionList = (data: GetScrollCollectionsProps) =>
|
||||
GET<PaginationResponse<DatasetCollectionsListItemType>>(
|
||||
`/core/dataset/collection/scrollList`,
|
||||
data
|
||||
);
|
||||
|
||||
/* =============================== data ==================================== */
|
||||
/* get dataset list */
|
||||
export const getDatasetDataList = (data: GetDatasetDataListProps) =>
|
||||
|
@@ -3,7 +3,6 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getDatasetCollectionPathById, getDatasetCollections } from '@/web/core/dataset/api';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { Box, Flex, ModalFooter, Button, useTheme, Grid, Card, ModalBody } from '@chakra-ui/react';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
|
@@ -51,6 +51,7 @@ export const defaultCollectionDetail: DatasetCollectionItemType = {
|
||||
defaultPermission: DatasetDefaultPermissionVal,
|
||||
inheritPermission: true
|
||||
},
|
||||
tags: [],
|
||||
parentId: '',
|
||||
name: '',
|
||||
type: DatasetCollectionTypeEnum.file,
|
||||
|
@@ -1,22 +1,30 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { ReactNode, SetStateAction, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import {
|
||||
getAllTags,
|
||||
getDatasetById,
|
||||
getDatasetCollectionTags,
|
||||
getDatasetTrainingQueue,
|
||||
getTrainingQueueLen,
|
||||
putDatasetById
|
||||
} from '../api';
|
||||
import { defaultDatasetDetail } from '../constants';
|
||||
import { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api';
|
||||
import { DatasetItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { DatasetItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type';
|
||||
|
||||
type DatasetPageContextType = {
|
||||
datasetId: string;
|
||||
datasetDetail: DatasetItemType;
|
||||
loadDatasetDetail: (id: string) => Promise<DatasetItemType>;
|
||||
updateDataset: (data: DatasetUpdateBody) => Promise<void>;
|
||||
datasetTags: DatasetTagType[];
|
||||
loadDatasetTags: (data: { id: string; searchKey: string }) => Promise<void>;
|
||||
allDatasetTags: DatasetTagType[];
|
||||
loadAllDatasetTags: (data: { id: string }) => Promise<void>;
|
||||
checkedDatasetTag: DatasetTagType[];
|
||||
setCheckedDatasetTag: React.Dispatch<SetStateAction<DatasetTagType[]>>;
|
||||
|
||||
vectorTrainingMap: {
|
||||
colorSchema: string;
|
||||
@@ -52,6 +60,18 @@ export const DatasetPageContext = createContext<DatasetPageContextType>({
|
||||
},
|
||||
updateDataset: function (data: DatasetUpdateBody): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
datasetTags: [],
|
||||
loadDatasetTags: function (data: { id: string; searchKey: string }): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
allDatasetTags: [],
|
||||
loadAllDatasetTags: function (data: { id: string }): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
checkedDatasetTag: [],
|
||||
setCheckedDatasetTag: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,6 +105,28 @@ export const DatasetPageContextProvider = ({
|
||||
}
|
||||
};
|
||||
|
||||
// dataset tags
|
||||
const [datasetTags, setDatasetTags] = useState<DatasetTagType[]>([]);
|
||||
|
||||
const loadDatasetTags = async ({ id, searchKey }: { id: string; searchKey: string }) => {
|
||||
const { list } = await getDatasetCollectionTags({
|
||||
datasetId: id,
|
||||
searchText: searchKey,
|
||||
current: 1,
|
||||
pageSize: 15
|
||||
});
|
||||
setDatasetTags(list);
|
||||
};
|
||||
|
||||
const [checkedDatasetTag, setCheckedDatasetTag] = useState<DatasetTagType[]>([]);
|
||||
|
||||
const [allDatasetTags, setAllDatasetTags] = useState<DatasetTagType[]>([]);
|
||||
|
||||
const loadAllDatasetTags = async ({ id }: { id: string }) => {
|
||||
const { list } = await getAllTags(id);
|
||||
setAllDatasetTags(list);
|
||||
};
|
||||
|
||||
// global queue
|
||||
const { data: { vectorTrainingCount = 0, agentTrainingCount = 0 } = {} } = useQuery(
|
||||
['getTrainingQueueLen'],
|
||||
@@ -152,7 +194,13 @@ export const DatasetPageContextProvider = ({
|
||||
agentTrainingMap,
|
||||
rebuildingCount,
|
||||
trainingCount,
|
||||
refetchDatasetTraining
|
||||
refetchDatasetTraining,
|
||||
datasetTags,
|
||||
loadDatasetTags,
|
||||
checkedDatasetTag,
|
||||
setCheckedDatasetTag,
|
||||
allDatasetTags,
|
||||
loadAllDatasetTags
|
||||
};
|
||||
|
||||
return <DatasetPageContext.Provider value={contextValue}>{children}</DatasetPageContext.Provider>;
|
||||
|
Reference in New Issue
Block a user