From b44bbd11b8841bd0b45b7b39da2edcc7d0baf634 Mon Sep 17 00:00:00 2001 From: balibabu Date: Wed, 23 Apr 2025 10:39:09 +0800 Subject: [PATCH] Feat: Upload document #3221 (#7209) ### What problem does this PR solve? Feat: Upload document #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/confirm-delete-dialog.tsx | 5 +- .../components/file-upload-dialog/index.tsx | 27 +++++-- web/src/components/list-filter-bar.tsx | 17 ++-- web/src/hooks/use-document-request.ts | 49 ++++++++++++ web/src/hooks/use-file-request.ts | 48 ++++++++++++ web/src/pages/dataset/dataset/hooks.ts | 43 ----------- web/src/pages/dataset/dataset/index.tsx | 2 +- .../dataset/dataset/use-upload-document.ts | 44 +++++++++++ web/src/pages/datasets/dataset-card.tsx | 64 +++++++++++++++ web/src/pages/datasets/dataset-dropdown.tsx | 22 ++++-- web/src/pages/datasets/index.tsx | 62 ++------------- web/src/pages/files/hooks.ts | 33 -------- web/src/pages/files/index.tsx | 19 ++++- web/src/pages/files/use-upload-file.ts | 35 +++++++++ web/src/pages/home/datasets.tsx | 77 +++++++------------ 15 files changed, 341 insertions(+), 206 deletions(-) create mode 100644 web/src/hooks/use-document-request.ts create mode 100644 web/src/hooks/use-file-request.ts create mode 100644 web/src/pages/dataset/dataset/use-upload-document.ts create mode 100644 web/src/pages/datasets/dataset-card.tsx create mode 100644 web/src/pages/files/use-upload-file.ts diff --git a/web/src/components/confirm-delete-dialog.tsx b/web/src/components/confirm-delete-dialog.tsx index 3843b02a4..c7c5c3efc 100644 --- a/web/src/components/confirm-delete-dialog.tsx +++ b/web/src/components/confirm-delete-dialog.tsx @@ -28,7 +28,10 @@ export function ConfirmDeleteDialog({ return ( {children} - + e.preventDefault()} + onClick={(e) => e.stopPropagation()} + > {title ?? t('common.deleteModalTitle')} diff --git a/web/src/components/file-upload-dialog/index.tsx b/web/src/components/file-upload-dialog/index.tsx index 5d9d12fa6..02e51ef5e 100644 --- a/web/src/components/file-upload-dialog/index.tsx +++ b/web/src/components/file-upload-dialog/index.tsx @@ -8,15 +8,16 @@ import { } from '@/components/ui/dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { IModalProps } from '@/interfaces/common'; -import { useState } from 'react'; +import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FileUploader } from '../file-uploader'; -export function UploaderTabs() { - const { t } = useTranslation(); +type UploaderTabsProps = { + setFiles: Dispatch>; +}; - const [files, setFiles] = useState([]); - console.log('🚀 ~ TabsDemo ~ files:', files); +export function UploaderTabs({ setFiles }: UploaderTabsProps) { + const { t } = useTranslation(); return ( @@ -36,8 +37,13 @@ export function UploaderTabs() { ); } -export function FileUploadDialog({ hideModal }: IModalProps) { +export function FileUploadDialog({ hideModal, onOk }: IModalProps) { const { t } = useTranslation(); + const [files, setFiles] = useState([]); + + const handleOk = useCallback(() => { + onOk?.(files); + }, [files, onOk]); return ( @@ -45,9 +51,14 @@ export function FileUploadDialog({ hideModal }: IModalProps) { {t('fileManager.uploadFile')} - + - diff --git a/web/src/components/list-filter-bar.tsx b/web/src/components/list-filter-bar.tsx index f671c987b..aa04b9b05 100644 --- a/web/src/components/list-filter-bar.tsx +++ b/web/src/components/list-filter-bar.tsx @@ -14,6 +14,7 @@ interface IProps { searchString?: string; onSearchChange?: ChangeEventHandler; count?: number; + showFilter?: boolean; } const FilterButton = React.forwardRef< @@ -35,18 +36,20 @@ export default function ListFilterBar({ searchString, onSearchChange, count, + showFilter = true, }: PropsWithChildren) { return (
{title}
- {FilterPopover ? ( - - - - ) : ( - - )} + {showFilter && + (FilterPopover ? ( + + + + ) : ( + + ))} { + const queryClient = useQueryClient(); + const { id } = useParams(); + + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [DocumentApiAction.UploadDocument], + mutationFn: async (fileList: File[]) => { + const formData = new FormData(); + formData.append('kb_id', id!); + fileList.forEach((file: any) => { + formData.append('file', file); + }); + + try { + const ret = await kbService.document_upload(formData); + const code = get(ret, 'data.code'); + + if (code === 0 || code === 500) { + queryClient.invalidateQueries({ + queryKey: [DocumentApiAction.FetchDocumentList], + }); + } + return ret?.data; + } catch (error) { + console.warn(error); + return { + code: 500, + message: error + '', + }; + } + }, + }); + + return { uploadDocument: mutateAsync, loading, data }; +}; diff --git a/web/src/hooks/use-file-request.ts b/web/src/hooks/use-file-request.ts new file mode 100644 index 000000000..deaccac30 --- /dev/null +++ b/web/src/hooks/use-file-request.ts @@ -0,0 +1,48 @@ +import fileManagerService from '@/services/file-manager-service'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { message } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useSetPaginationParams } from './route-hook'; + +export const enum FileApiAction { + UploadFile = 'uploadFile', + FetchFileList = 'fetchFileList', +} + +export const useUploadFile = () => { + const { setPaginationParams } = useSetPaginationParams(); + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [FileApiAction.UploadFile], + mutationFn: async (params: { fileList: File[]; parentId: string }) => { + const fileList = params.fileList; + const pathList = params.fileList.map( + (file) => (file as any).webkitRelativePath, + ); + const formData = new FormData(); + formData.append('parent_id', params.parentId); + fileList.forEach((file: any, index: number) => { + formData.append('file', file); + formData.append('path', pathList[index]); + }); + try { + const ret = await fileManagerService.uploadFile(formData); + if (ret?.data.code === 0) { + message.success(t('message.uploaded')); + setPaginationParams(1); + queryClient.invalidateQueries({ + queryKey: [FileApiAction.FetchFileList], + }); + } + return ret?.data?.code; + } catch (error) {} + }, + }); + + return { data, loading, uploadFile: mutateAsync }; +}; diff --git a/web/src/pages/dataset/dataset/hooks.ts b/web/src/pages/dataset/dataset/hooks.ts index f94721edd..0f235aec9 100644 --- a/web/src/pages/dataset/dataset/hooks.ts +++ b/web/src/pages/dataset/dataset/hooks.ts @@ -5,12 +5,9 @@ import { useRunNextDocument, useSaveNextDocumentName, useSetNextDocumentParser, - useUploadNextDocument, } from '@/hooks/document-hooks'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; -import { getUnSupportedFilesCount } from '@/utils/document-util'; -import { UploadFile } from 'antd'; import { useCallback, useState } from 'react'; import { useNavigate } from 'umi'; @@ -134,46 +131,6 @@ export const useGetRowSelection = () => { return rowSelection; }; -export const useHandleUploadDocument = () => { - const { - visible: documentUploadVisible, - hideModal: hideDocumentUploadModal, - showModal: showDocumentUploadModal, - } = useSetModalState(); - const { uploadDocument, loading } = useUploadNextDocument(); - - const onDocumentUploadOk = useCallback( - async (fileList: UploadFile[]): Promise => { - if (fileList.length > 0) { - const ret: any = await uploadDocument(fileList); - if (typeof ret?.message !== 'string') { - return; - } - const count = getUnSupportedFilesCount(ret?.message); - /// 500 error code indicates that some file types are not supported - let code = ret?.code; - if ( - ret?.code === 0 || - (ret?.code === 500 && count !== fileList.length) // Some files were not uploaded successfully, but some were uploaded successfully. - ) { - code = 0; - hideDocumentUploadModal(); - } - return code; - } - }, - [uploadDocument, hideDocumentUploadModal], - ); - - return { - documentUploadLoading: loading, - onDocumentUploadOk, - documentUploadVisible, - hideDocumentUploadModal, - showDocumentUploadModal, - }; -}; - export const useHandleWebCrawl = () => { const { visible: webCrawlUploadVisible, diff --git a/web/src/pages/dataset/dataset/index.tsx b/web/src/pages/dataset/dataset/index.tsx index 620fc04f1..7cb612c42 100644 --- a/web/src/pages/dataset/dataset/index.tsx +++ b/web/src/pages/dataset/dataset/index.tsx @@ -2,7 +2,7 @@ import { FileUploadDialog } from '@/components/file-upload-dialog'; import ListFilterBar from '@/components/list-filter-bar'; import { Upload } from 'lucide-react'; import { DatasetTable } from './dataset-table'; -import { useHandleUploadDocument } from './hooks'; +import { useHandleUploadDocument } from './use-upload-document'; export default function Dataset() { const { diff --git a/web/src/pages/dataset/dataset/use-upload-document.ts b/web/src/pages/dataset/dataset/use-upload-document.ts new file mode 100644 index 000000000..2e1915ce2 --- /dev/null +++ b/web/src/pages/dataset/dataset/use-upload-document.ts @@ -0,0 +1,44 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { useUploadNextDocument } from '@/hooks/use-document-request'; +import { getUnSupportedFilesCount } from '@/utils/document-util'; +import { useCallback } from 'react'; + +export const useHandleUploadDocument = () => { + const { + visible: documentUploadVisible, + hideModal: hideDocumentUploadModal, + showModal: showDocumentUploadModal, + } = useSetModalState(); + const { uploadDocument, loading } = useUploadNextDocument(); + + const onDocumentUploadOk = useCallback( + async (fileList: File[]): Promise => { + if (fileList.length > 0) { + const ret: any = await uploadDocument(fileList); + if (typeof ret?.message !== 'string') { + return; + } + const count = getUnSupportedFilesCount(ret?.message); + /// 500 error code indicates that some file types are not supported + let code = ret?.code; + if ( + ret?.code === 0 || + (ret?.code === 500 && count !== fileList.length) // Some files were not uploaded successfully, but some were uploaded successfully. + ) { + code = 0; + hideDocumentUploadModal(); + } + return code; + } + }, + [uploadDocument, hideDocumentUploadModal], + ); + + return { + documentUploadLoading: loading, + onDocumentUploadOk, + documentUploadVisible, + hideDocumentUploadModal, + showDocumentUploadModal, + }; +}; diff --git a/web/src/pages/datasets/dataset-card.tsx b/web/src/pages/datasets/dataset-card.tsx new file mode 100644 index 000000000..c17561e23 --- /dev/null +++ b/web/src/pages/datasets/dataset-card.tsx @@ -0,0 +1,64 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; +import { IKnowledge } from '@/interfaces/database/knowledge'; +import { formatDate } from '@/utils/date'; +import { Ellipsis } from 'lucide-react'; +import { DatasetDropdown } from './dataset-dropdown'; +import { useDisplayOwnerName } from './use-display-owner'; +import { useRenameDataset } from './use-rename-dataset'; + +export type DatasetCardProps = { + dataset: IKnowledge; +} & Pick, 'showDatasetRenameModal'>; + +export function DatasetCard({ + dataset, + showDatasetRenameModal, +}: DatasetCardProps) { + const { navigateToDataset } = useNavigatePage(); + const displayOwnerName = useDisplayOwnerName(); + + const owner = displayOwnerName(dataset.tenant_id, dataset.nickname); + + return ( + + +
+
+ + + CN + + {owner && {owner}} +
+ + + +
+
+
+

+ {dataset.name} +

+

{dataset.doc_num} files

+

+ Created {formatDate(dataset.update_time)} +

+
+
+
+
+ ); +} diff --git a/web/src/pages/datasets/dataset-dropdown.tsx b/web/src/pages/datasets/dataset-dropdown.tsx index 7413a7cde..16e175dda 100644 --- a/web/src/pages/datasets/dataset-dropdown.tsx +++ b/web/src/pages/datasets/dataset-dropdown.tsx @@ -9,7 +9,7 @@ import { import { useDeleteKnowledge } from '@/hooks/use-knowledge-request'; import { IKnowledge } from '@/interfaces/database/knowledge'; import { PenLine, Trash2 } from 'lucide-react'; -import { PropsWithChildren, useCallback } from 'react'; +import { MouseEventHandler, PropsWithChildren, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useRenameDataset } from './use-rename-dataset'; @@ -24,11 +24,16 @@ export function DatasetDropdown({ const { t } = useTranslation(); const { deleteKnowledge } = useDeleteKnowledge(); - const handleShowDatasetRenameModal = useCallback(() => { - showDatasetRenameModal(dataset); - }, [dataset, showDatasetRenameModal]); + const handleShowDatasetRenameModal: MouseEventHandler = + useCallback( + (e) => { + e.stopPropagation(); + showDatasetRenameModal(dataset); + }, + [dataset, showDatasetRenameModal], + ); - const handleDelete = useCallback(() => { + const handleDelete: MouseEventHandler = useCallback(() => { deleteKnowledge(dataset.id); }, [dataset.id, deleteKnowledge]); @@ -43,7 +48,12 @@ export function DatasetDropdown({ e.preventDefault()} + onSelect={(e) => { + e.preventDefault(); + }} + onClick={(e) => { + e.stopPropagation(); + }} > {t('common.delete')} diff --git a/web/src/pages/datasets/index.tsx b/web/src/pages/datasets/index.tsx index 553f7a388..7ed2d2500 100644 --- a/web/src/pages/datasets/index.tsx +++ b/web/src/pages/datasets/index.tsx @@ -1,21 +1,14 @@ import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; -import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; -import { formatDate } from '@/utils/date'; import { pick } from 'lodash'; -import { ChevronRight, Ellipsis, Plus } from 'lucide-react'; +import { Plus } from 'lucide-react'; import { PropsWithChildren, useCallback } from 'react'; +import { DatasetCard } from './dataset-card'; import { DatasetCreatingDialog } from './dataset-creating-dialog'; -import { DatasetDropdown } from './dataset-dropdown'; import { DatasetsFilterPopover } from './datasets-filter-popover'; import { DatasetsPagination } from './datasets-pagination'; import { useSaveKnowledge } from './hooks'; -import { useDisplayOwnerName } from './use-display-owner'; import { useRenameDataset } from './use-rename-dataset'; export default function Datasets() { @@ -26,7 +19,6 @@ export default function Datasets() { onCreateOk, loading: creatingLoading, } = useSaveKnowledge(); - const { navigateToDataset } = useNavigatePage(); const { kbs, @@ -48,8 +40,6 @@ export default function Datasets() { showDatasetRenameModal, } = useRenameDataset(); - const displayOwnerName = useDisplayOwnerName(); - const handlePageChange = useCallback( (page: number, pageSize?: number) => { setPagination({ page, pageSize }); @@ -76,52 +66,12 @@ export default function Datasets() {
{kbs.map((dataset) => { - const owner = displayOwnerName(dataset.tenant_id, dataset.nickname); return ( - - -
-
- - - CN - - {owner && {owner}} -
- - - -
-
-
-

- {dataset.name} -

-

- {dataset.doc_num} files -

-

- Created {formatDate(dataset.update_time)} -

-
- -
-
-
+ showDatasetRenameModal={showDatasetRenameModal} + > ); })}
diff --git a/web/src/pages/files/hooks.ts b/web/src/pages/files/hooks.ts index 3b13e719e..42c03c496 100644 --- a/web/src/pages/files/hooks.ts +++ b/web/src/pages/files/hooks.ts @@ -6,11 +6,9 @@ import { useFetchParentFolderList, useMoveFile, useRenameFile, - useUploadFile, } from '@/hooks/file-manager-hooks'; import { IFile } from '@/interfaces/database/file-manager'; import { TableRowSelection } from 'antd/es/table/interface'; -import { UploadFile } from 'antd/lib'; import { useCallback, useMemo, useState } from 'react'; import { useNavigate, useSearchParams } from 'umi'; @@ -157,37 +155,6 @@ export const useHandleDeleteFile = ( return { handleRemoveFile }; }; -export const useHandleUploadFile = () => { - const { - visible: fileUploadVisible, - hideModal: hideFileUploadModal, - showModal: showFileUploadModal, - } = useSetModalState(); - const { uploadFile, loading } = useUploadFile(); - const id = useGetFolderId(); - - const onFileUploadOk = useCallback( - async (fileList: UploadFile[]): Promise => { - if (fileList.length > 0) { - const ret: number = await uploadFile({ fileList, parentId: id }); - if (ret === 0) { - hideFileUploadModal(); - } - return ret; - } - }, - [uploadFile, hideFileUploadModal, id], - ); - - return { - fileUploadLoading: loading, - onFileUploadOk, - fileUploadVisible, - hideFileUploadModal, - showFileUploadModal, - }; -}; - export const useHandleConnectToKnowledge = () => { const { visible: connectToKnowledgeVisible, diff --git a/web/src/pages/files/index.tsx b/web/src/pages/files/index.tsx index 15869d4c9..706522bc8 100644 --- a/web/src/pages/files/index.tsx +++ b/web/src/pages/files/index.tsx @@ -1,15 +1,32 @@ +import { FileUploadDialog } from '@/components/file-upload-dialog'; import ListFilterBar from '@/components/list-filter-bar'; import { Upload } from 'lucide-react'; import { FilesTable } from './files-table'; +import { useHandleUploadFile } from './use-upload-file'; export default function Files() { + const { + fileUploadVisible, + hideFileUploadModal, + showFileUploadModal, + fileUploadLoading, + onFileUploadOk, + } = useHandleUploadFile(); + return (
- + Upload file + {fileUploadVisible && ( + + )}
); } diff --git a/web/src/pages/files/use-upload-file.ts b/web/src/pages/files/use-upload-file.ts new file mode 100644 index 000000000..6dabec461 --- /dev/null +++ b/web/src/pages/files/use-upload-file.ts @@ -0,0 +1,35 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { useUploadFile } from '@/hooks/use-file-request'; +import { useCallback } from 'react'; +import { useGetFolderId } from './hooks'; + +export const useHandleUploadFile = () => { + const { + visible: fileUploadVisible, + hideModal: hideFileUploadModal, + showModal: showFileUploadModal, + } = useSetModalState(); + const { uploadFile, loading } = useUploadFile(); + const id = useGetFolderId(); + + const onFileUploadOk = useCallback( + async (fileList: File[]): Promise => { + if (fileList.length > 0) { + const ret: number = await uploadFile({ fileList, parentId: id }); + if (ret === 0) { + hideFileUploadModal(); + } + return ret; + } + }, + [uploadFile, hideFileUploadModal, id], + ); + + return { + fileUploadLoading: loading, + onFileUploadOk, + fileUploadVisible, + hideFileUploadModal, + showFileUploadModal, + }; +}; diff --git a/web/src/pages/home/datasets.tsx b/web/src/pages/home/datasets.tsx index d1c4cf7c5..e9e548328 100644 --- a/web/src/pages/home/datasets.tsx +++ b/web/src/pages/home/datasets.tsx @@ -1,15 +1,22 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; import { CardSkeleton } from '@/components/ui/skeleton'; -import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; -import { formatDate } from '@/utils/date'; -import { ChevronRight, Trash2 } from 'lucide-react'; +import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; +import { DatasetCard } from '../datasets/dataset-card'; +import { useRenameDataset } from '../datasets/use-rename-dataset'; export function Datasets() { - const { navigateToDatasetList, navigateToDataset } = useNavigatePage(); - const { list, loading } = useFetchKnowledgeList(); + const { navigateToDatasetList } = useNavigatePage(); + const { kbs, loading } = useFetchNextKnowledgeListByPage(); + const { + datasetRenameLoading, + initialDatasetName, + onDatasetRenameOk, + datasetRenameVisible, + hideDatasetRenameModal, + showDatasetRenameModal, + } = useRenameDataset(); return (
@@ -21,50 +28,12 @@ export function Datasets() {
) : (
- {list.slice(0, 3).map((dataset) => ( - ( + - -
- {dataset.avatar ? ( -
- ) : ( - - - CN - - )} - -
-
-
-

- {dataset.name} -

-
- {dataset.doc_num} files -
-

- Created {formatDate(dataset.update_time)} -

-
- -
- - + dataset={dataset} + showDatasetRenameModal={showDatasetRenameModal} + > ))}
)} @@ -76,6 +45,14 @@ export function Datasets() { See all
+ {datasetRenameVisible && ( + + )} ); }