diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md b/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md index 465164e85581..3449f3ba2311 100644 --- a/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md @@ -61,6 +61,8 @@ type FileListItem = { {{% alert icon=" " context="success" %}} - parentId - 父级 id,可选,或者 null。 - searchKey - 检索词,可选 +- pageSize - 每页显示的数据项的数量 +- offset - 偏移量 {{% /alert %}} ```bash @@ -69,7 +71,9 @@ curl --location --request POST '{{baseURL}}/v1/file/list' \ --header 'Content-Type: application/json' \ --data-raw '{ "parentId": null, - "searchKey": "" + "searchKey": "", + "pageSize": 15, + "offset": 0 }' ``` @@ -84,16 +88,19 @@ curl --location --request POST '{{baseURL}}/v1/file/list' \ "code": 200, "success": true, "message": "", - "data": [ - { - "id": "xxxx", - "parentId": "xxxx", - "type": "file", // file | folder - "name":"test.json", - "updateTime":"2024-11-26T03:05:24.759Z", - "createTime":"2024-11-26T03:05:24.759Z" - } - ] + "data":{ + "list": [ + { + "id": "xxxx", + "parentId": "xxxx", + "type": "file", // file | folder + "name":"test.json", + "updateTime":"2024-11-26T03:05:24.759Z", + "createTime":"2024-11-26T03:05:24.759Z" + } + ], + "total": 1 + } } ``` diff --git a/packages/global/core/dataset/apiDataset.d.ts b/packages/global/core/dataset/apiDataset.d.ts index 94c036886559..638a76bd7d49 100644 --- a/packages/global/core/dataset/apiDataset.d.ts +++ b/packages/global/core/dataset/apiDataset.d.ts @@ -13,7 +13,11 @@ export type APIFileServer = { authorization: string; }; -export type APIFileListResponse = APIFileItem[]; +export type APIFileListResponse = { + list: APIFileItem[]; + total: number; + metaData?: Record; +}; export type APIFileContentResponse = { content?: string; diff --git a/packages/service/core/dataset/apiDataset/api.ts b/packages/service/core/dataset/apiDataset/api.ts index 162d685f59b0..f71e7c76a42e 100644 --- a/packages/service/core/dataset/apiDataset/api.ts +++ b/packages/service/core/dataset/apiDataset/api.ts @@ -79,33 +79,51 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer } const listFiles = async ({ searchKey, - parentId + parentId, + offset, + pageSize }: { searchKey?: string; parentId?: ParentIdType; + offset: number; + pageSize: number; }) => { - const files = await request( + const response = await request( `/v1/file/list`, { searchKey, - parentId + parentId, + pageSize, + offset }, 'POST' ); - if (!Array.isArray(files)) { - return Promise.reject('Invalid file list format'); + let list: any[] = []; + let total = 0; + + // 兼容旧的数据格式 + if (Array.isArray(response)) { + list = response; + total = response.length; + } else { + list = response.list; + total = response.total; } - if (files.some((file) => !file.id || !file.name || typeof file.type === 'undefined')) { + + if (list.some((file) => !file.id || !file.name || typeof file.type === 'undefined')) { return Promise.reject('Invalid file data format'); } - const formattedFiles = files.map((file) => ({ + const formattedFiles = list.map((file) => ({ ...file, hasChild: file.type === 'folder' })); - return formattedFiles; + return { + list: formattedFiles, + total + }; }; const getFileContent = async ({ teamId, apiFileId }: { teamId: string; apiFileId: string }) => { diff --git a/packages/web/common/fetch/type.d.ts b/packages/web/common/fetch/type.d.ts index 7f2ea1fbf272..038760dd80e1 100644 --- a/packages/web/common/fetch/type.d.ts +++ b/packages/web/common/fetch/type.d.ts @@ -2,6 +2,7 @@ import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; type PaginationProps = T & { pageSize: number | string; + metaData?: Record; } & RequireOnlyOne<{ offset: number | string; pageNum: number | string; @@ -10,4 +11,5 @@ type PaginationProps = T & { type PaginationResponse = { total: number; list: T[]; + metaData?: Record; }; diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index 4ecc77c44f14..7d8a71486d0d 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -89,7 +89,7 @@ export function useVirtualScrollPagination< // init or reload setData(res.list); } else { - setData((prev) => [...prev, ...res.list]); + setData((prevData) => [...prevData, ...res.list]); } } catch (error: any) { toast({ @@ -205,27 +205,30 @@ export function useScrollPagination< const [data, setData] = useState([]); const [total, setTotal] = useState(0); + const [metaData, setMetaData] = useState({}); const [isLoading, { setTrue, setFalse }] = useBoolean(false); - const isEmpty = total === 0 && !isLoading; - const noMore = data.length >= total; + const isEmpty = total === 0 && !isLoading; + const noMore = data.length >= total || !!metaData?.noMore; const loadData = useLockFn( async (init = false, ScrollContainerRef?: RefObject) => { if (noMore && !init) return; - const offset = init ? 0 : data.length; - setTrue(); try { + const offset = init ? 0 : data.length; + const res = await api({ offset, pageSize, + metaData, ...params } as TParams); setTotal(res.total); + setMetaData(res.metaData); if (scrollLoadType === 'top') { const prevHeight = ScrollContainerRef?.current?.scrollHeight || 0; @@ -245,10 +248,10 @@ export function useScrollPagination< ); } - setData((prevData) => (offset === 0 ? res.list : [...res.list, ...prevData])); + setData((prevData) => (init ? res.list : [...res.list, ...prevData])); adjustScrollPosition(); } else { - setData((prevData) => (offset === 0 ? res.list : [...prevData, ...res.list])); + setData((prevData) => (init ? res.list : [...prevData, ...res.list])); } } catch (error: any) { if (showErrorToast) { diff --git a/projects/app/src/pages/api/core/dataset/apiDataset/list.ts b/projects/app/src/pages/api/core/dataset/apiDataset/list.ts index 28d1c63821bd..d83c424c0de7 100644 --- a/projects/app/src/pages/api/core/dataset/apiDataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/apiDataset/list.ts @@ -12,12 +12,13 @@ export type GetApiDatasetFileListProps = { searchKey?: string; parentId?: ParentIdType; datasetId: string; + offset: number; + pageSize: number; + metaData?: Record; }; -export type GetApiDatasetFileListResponse = APIFileItem[]; - async function handler(req: NextApiRequest) { - let { searchKey = '', parentId = null, datasetId } = req.body; + let { searchKey = '', parentId = null, datasetId, pageSize, offset = 0, metaData } = req.body; const { dataset } = await authDataset({ req, @@ -32,13 +33,25 @@ async function handler(req: NextApiRequest) { const yuqueServer = dataset.yuqueServer; if (apiServer) { - return useApiDatasetRequest({ apiServer }).listFiles({ searchKey, parentId }); + const { list, total } = await useApiDatasetRequest({ apiServer }).listFiles({ + searchKey, + parentId, + offset, + pageSize + }); + return { + list, + total + }; } if (feishuServer || yuqueServer) { return getFeishuAndYuqueDatasetFileList({ feishuServer, yuqueServer, - parentId + parentId, + offset, + pageSize, + metaData }); } diff --git a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx index e0cdff95ca54..468ce1ab89c4 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx @@ -13,9 +13,11 @@ import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type import FolderPath from '@/components/common/folder/Path'; import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; import MyBox from '@fastgpt/web/components/common/MyBox'; -import { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset'; +import { APIFileItem, APIFileListResponse } from '@fastgpt/global/core/dataset/apiDataset'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import { useMount } from 'ahooks'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; +import { GetApiDatasetFileListProps } from '@/pages/api/core/dataset/apiDataset/list'; const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), { loading: () => @@ -53,20 +55,20 @@ const CustomAPIFileInput = () => { const [searchKey, setSearchKey] = useState(''); - const { data: fileList = [], loading } = useRequest2( - async () => { - return getApiDatasetFileList({ - datasetId: datasetDetail._id, - parentId: parent?.parentId, - searchKey: searchKey - }); + const { + data: apiFileList, + ScrollData, + isLoading + } = useScrollPagination(getApiDatasetFileList, { + pageSize: 15, + params: { + datasetId: datasetDetail._id, + parentId: parent?.parentId, + searchKey: searchKey }, - { - refreshDeps: [datasetDetail._id, datasetDetail.apiServer, parent, searchKey], - throttleWait: 500, - manual: false - } - ); + + refreshDeps: [datasetDetail._id, datasetDetail.apiServer, parent, searchKey] + }); const { data: existIdList = [] } = useRequest2( () => getApiDatasetFileListExistId({ datasetId: datasetDetail._id }), @@ -83,17 +85,21 @@ const CustomAPIFileInput = () => { const { runAsync: onclickNext, loading: onNextLoading } = useRequest2( async () => { // Computed all selected files - const getFilesRecursively = async (files: APIFileItem[]): Promise => { + const getFilesRecursively = async ( + files: APIFileItem[], + offset: number + ): Promise => { const allFiles: APIFileItem[] = []; for (const file of files) { if (file.type === 'folder') { - const folderFiles = await getApiDatasetFileList({ + const { list: folderFiles, total } = await getApiDatasetFileList({ datasetId: datasetDetail._id, - parentId: file?.id + parentId: file?.id, + offset, + pageSize: 100 }); - - const subFiles = await getFilesRecursively(folderFiles); + const subFiles = await getFilesRecursively(folderFiles, total); allFiles.push(...subFiles); } else { allFiles.push(file); @@ -103,7 +109,7 @@ const CustomAPIFileInput = () => { return allFiles; }; - const allFiles = await getFilesRecursively(selectFiles); + const allFiles = await getFilesRecursively(selectFiles, 0); setSources( allFiles @@ -145,139 +151,143 @@ const CustomAPIFileInput = () => { [selectFiles] ); - const handleSelectAll = useCallback(() => { - const isAllSelected = fileList.length === selectFiles.length; + const isAllSelected = useMemo(() => { + const validSelectFiles = selectFiles.filter((file) => + apiFileList.some((apiFile) => apiFile.id === file.id) + ); + const notExistFiles = apiFileList.filter((file) => !existIdList.some((id) => id === file.id)); + return notExistFiles.length === validSelectFiles.length; + }, [apiFileList, existIdList, selectFiles]); + + const handleSelectAll = useCallback(() => { if (isAllSelected) { setSelectFiles([]); } else { - setSelectFiles(fileList); + setSelectFiles(apiFileList.filter((item) => !existIdList.includes(item.id))); } - }, [fileList, selectFiles]); + }, [apiFileList, existIdList, isAllSelected]); return ( - - - - { - const index = paths.findIndex((item) => item.parentId === parentId); + + + { + const index = paths.findIndex((item) => item.parentId === parentId); + + setParent(paths[index]); + setPaths(paths.slice(0, index + 1)); + }} + /> + {datasetDetail.apiServer && ( + + setSearchKey(e.target.value)} + placeholder={t('common:core.workflow.template.Search')} + /> + + )} + - setParent(paths[index]); - setPaths(paths.slice(0, index + 1)); - }} + + { + if (!(e.target as HTMLElement).closest('.checkbox')) { + handleSelectAll(); + } + }} + > + - {datasetDetail.apiServer && ( - - setSearchKey(e.target.value)} - placeholder={t('common:core.workflow.template.Search')} - /> - - )} + {t('common:Select_all')} - - - { - if (!(e.target as HTMLElement).closest('.checkbox')) { - handleSelectAll(); - } - }} - > - - {t('common:Select_all')} - - {fileList.map((item) => { - const isFolder = item.type === 'folder'; - const isExists = existIdList.includes(item.id); - const isChecked = isExists || selectFiles.some((file) => file.id === item.id); + + {apiFileList.map((item) => { + const isFolder = item.type === 'folder'; + const isExists = existIdList.includes(item.id); + const isChecked = isExists || selectFiles.some((file) => file.id === item.id); - return ( - { + return ( + { + if (isExists) return; + if (!(e.target as HTMLElement).closest('.checkbox')) { + handleItemClick(item); + } + }} + > + { + e.stopPropagation(); if (isExists) return; - if (!(e.target as HTMLElement).closest('.checkbox')) { - handleItemClick(item); + if (isChecked) { + setSelectFiles((state) => state.filter((file) => file.id !== item.id)); + } else { + setSelectFiles((state) => [...state, item]); } }} - > - { - e.stopPropagation(); - if (isExists) return; - if (isChecked) { - setSelectFiles((state) => state.filter((file) => file.id !== item.id)); - } else { - setSelectFiles((state) => [...state, item]); - } - }} - /> - - - {item.name} - - {item.hasChild && } - - ); - })} - - + /> + + + {item.name} + + {item.hasChild && } + + ); + })} + + - + - - + {selectFiles.length > 0 + ? `${t('common:core.dataset.import.Total files', { total: selectFiles.length })} | ` + : ''} + {t('common:common.Next Step')} + + ); }; diff --git a/projects/app/src/service/core/dataset/apiDataset/controller.ts b/projects/app/src/service/core/dataset/apiDataset/controller.ts index d7ed11143ea6..5a13e5c08691 100644 --- a/projects/app/src/service/core/dataset/apiDataset/controller.ts +++ b/projects/app/src/service/core/dataset/apiDataset/controller.ts @@ -1,13 +1,19 @@ -import { GetApiDatasetFileListResponse } from '@/pages/api/core/dataset/apiDataset/list'; -import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { + APIFileListResponse, + FeishuServer, + YuqueServer +} from '@fastgpt/global/core/dataset/apiDataset'; import { POST } from '@fastgpt/service/common/api/plusRequest'; export const getFeishuAndYuqueDatasetFileList = async (data: { feishuServer?: FeishuServer; yuqueServer?: YuqueServer; parentId?: string; + pageSize: number; + offset: number; + metaData?: Record; }) => { - const res = await POST('/core/dataset/systemApiDataset', { + const res = await POST('/core/dataset/systemApiDataset', { type: 'list', ...data }); diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index ac40e1a02ecd..26ad193bae51 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -57,14 +57,12 @@ import type { UpdateDatasetDataProps } from '@fastgpt/global/core/dataset/contro import type { DatasetFolderCreateBody } from '@/pages/api/core/dataset/folder/create'; import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import type { GetScrollCollectionsProps } from '@/pages/api/core/dataset/collection/scrollList'; -import type { - GetApiDatasetFileListProps, - GetApiDatasetFileListResponse -} from '@/pages/api/core/dataset/apiDataset/list'; +import type { GetApiDatasetFileListProps } from '@/pages/api/core/dataset/apiDataset/list'; import type { listExistIdQuery, listExistIdResponse } from '@/pages/api/core/dataset/apiDataset/listExistId'; +import { APIFileListResponse } from '@fastgpt/global/core/dataset/apiDataset'; /* ======================== dataset ======================= */ export const getDatasets = (data: GetDatasetListBody) => @@ -224,6 +222,6 @@ export const getCollectionSource = (data: readCollectionSourceBody) => /* ================== apiDataset ======================== */ export const getApiDatasetFileList = (data: GetApiDatasetFileListProps) => - POST('/core/dataset/apiDataset/list', data); + POST('/core/dataset/apiDataset/list', data); export const getApiDatasetFileListExistId = (data: listExistIdQuery) => GET('/core/dataset/apiDataset/listExistId', data);