mirror of
				https://github.com/infiniflow/ragflow.git
				synced 2025-10-31 09:50:00 +00:00 
			
		
		
		
	### What problem does this PR solve? Feat: Upload document #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
		
							parent
							
								
									1e91318445
								
							
						
					
					
						commit
						b44bbd11b8
					
				| @ -28,7 +28,10 @@ export function ConfirmDeleteDialog({ | |||||||
|   return ( |   return ( | ||||||
|     <AlertDialog> |     <AlertDialog> | ||||||
|       <AlertDialogTrigger asChild>{children}</AlertDialogTrigger> |       <AlertDialogTrigger asChild>{children}</AlertDialogTrigger> | ||||||
|       <AlertDialogContent> |       <AlertDialogContent | ||||||
|  |         onSelect={(e) => e.preventDefault()} | ||||||
|  |         onClick={(e) => e.stopPropagation()} | ||||||
|  |       > | ||||||
|         <AlertDialogHeader> |         <AlertDialogHeader> | ||||||
|           <AlertDialogTitle> |           <AlertDialogTitle> | ||||||
|             {title ?? t('common.deleteModalTitle')} |             {title ?? t('common.deleteModalTitle')} | ||||||
|  | |||||||
| @ -8,15 +8,16 @@ import { | |||||||
| } from '@/components/ui/dialog'; | } from '@/components/ui/dialog'; | ||||||
| import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | ||||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||||
| import { useState } from 'react'; | import { Dispatch, SetStateAction, useCallback, useState } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { FileUploader } from '../file-uploader'; | import { FileUploader } from '../file-uploader'; | ||||||
| 
 | 
 | ||||||
| export function UploaderTabs() { | type UploaderTabsProps = { | ||||||
|   const { t } = useTranslation(); |   setFiles: Dispatch<SetStateAction<File[]>>; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|   const [files, setFiles] = useState<File[]>([]); | export function UploaderTabs({ setFiles }: UploaderTabsProps) { | ||||||
|   console.log('🚀 ~ TabsDemo ~ files:', files); |   const { t } = useTranslation(); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Tabs defaultValue="account"> |     <Tabs defaultValue="account"> | ||||||
| @ -36,8 +37,13 @@ export function UploaderTabs() { | |||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function FileUploadDialog({ hideModal }: IModalProps<any>) { | export function FileUploadDialog({ hideModal, onOk }: IModalProps<File[]>) { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const [files, setFiles] = useState<File[]>([]); | ||||||
|  | 
 | ||||||
|  |   const handleOk = useCallback(() => { | ||||||
|  |     onOk?.(files); | ||||||
|  |   }, [files, onOk]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Dialog open onOpenChange={hideModal}> |     <Dialog open onOpenChange={hideModal}> | ||||||
| @ -45,9 +51,14 @@ export function FileUploadDialog({ hideModal }: IModalProps<any>) { | |||||||
|         <DialogHeader> |         <DialogHeader> | ||||||
|           <DialogTitle>{t('fileManager.uploadFile')}</DialogTitle> |           <DialogTitle>{t('fileManager.uploadFile')}</DialogTitle> | ||||||
|         </DialogHeader> |         </DialogHeader> | ||||||
|         <UploaderTabs></UploaderTabs> |         <UploaderTabs setFiles={setFiles}></UploaderTabs> | ||||||
|         <DialogFooter> |         <DialogFooter> | ||||||
|           <Button type="submit" variant={'tertiary'} size={'sm'}> |           <Button | ||||||
|  |             type="submit" | ||||||
|  |             variant={'tertiary'} | ||||||
|  |             size={'sm'} | ||||||
|  |             onClick={handleOk} | ||||||
|  |           > | ||||||
|             {t('common.save')} |             {t('common.save')} | ||||||
|           </Button> |           </Button> | ||||||
|         </DialogFooter> |         </DialogFooter> | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ interface IProps { | |||||||
|   searchString?: string; |   searchString?: string; | ||||||
|   onSearchChange?: ChangeEventHandler<HTMLInputElement>; |   onSearchChange?: ChangeEventHandler<HTMLInputElement>; | ||||||
|   count?: number; |   count?: number; | ||||||
|  |   showFilter?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const FilterButton = React.forwardRef< | const FilterButton = React.forwardRef< | ||||||
| @ -35,18 +36,20 @@ export default function ListFilterBar({ | |||||||
|   searchString, |   searchString, | ||||||
|   onSearchChange, |   onSearchChange, | ||||||
|   count, |   count, | ||||||
|  |   showFilter = true, | ||||||
| }: PropsWithChildren<IProps>) { | }: PropsWithChildren<IProps>) { | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex justify-between mb-6"> |     <div className="flex justify-between mb-6"> | ||||||
|       <span className="text-3xl font-bold ">{title}</span> |       <span className="text-3xl font-bold ">{title}</span> | ||||||
|       <div className="flex gap-4 items-center"> |       <div className="flex gap-4 items-center"> | ||||||
|         {FilterPopover ? ( |         {showFilter && | ||||||
|           <FilterPopover> |           (FilterPopover ? ( | ||||||
|             <FilterButton count={count}></FilterButton> |             <FilterPopover> | ||||||
|           </FilterPopover> |               <FilterButton count={count}></FilterButton> | ||||||
|         ) : ( |             </FilterPopover> | ||||||
|           <FilterButton></FilterButton> |           ) : ( | ||||||
|         )} |             <FilterButton></FilterButton> | ||||||
|  |           ))} | ||||||
| 
 | 
 | ||||||
|         <SearchInput |         <SearchInput | ||||||
|           value={searchString} |           value={searchString} | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								web/src/hooks/use-document-request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								web/src/hooks/use-document-request.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | import kbService from '@/services/knowledge-service'; | ||||||
|  | import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||||||
|  | import { get } from 'lodash'; | ||||||
|  | import { useParams } from 'umi'; | ||||||
|  | 
 | ||||||
|  | export const enum DocumentApiAction { | ||||||
|  |   UploadDocument = 'uploadDocument', | ||||||
|  |   FetchDocumentList = 'fetchDocumentList', | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const useUploadNextDocument = () => { | ||||||
|  |   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 }; | ||||||
|  | }; | ||||||
							
								
								
									
										48
									
								
								web/src/hooks/use-file-request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								web/src/hooks/use-file-request.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 }; | ||||||
|  | }; | ||||||
| @ -5,12 +5,9 @@ import { | |||||||
|   useRunNextDocument, |   useRunNextDocument, | ||||||
|   useSaveNextDocumentName, |   useSaveNextDocumentName, | ||||||
|   useSetNextDocumentParser, |   useSetNextDocumentParser, | ||||||
|   useUploadNextDocument, |  | ||||||
| } from '@/hooks/document-hooks'; | } from '@/hooks/document-hooks'; | ||||||
| import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; | import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; | ||||||
| import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | ||||||
| import { getUnSupportedFilesCount } from '@/utils/document-util'; |  | ||||||
| import { UploadFile } from 'antd'; |  | ||||||
| import { useCallback, useState } from 'react'; | import { useCallback, useState } from 'react'; | ||||||
| import { useNavigate } from 'umi'; | import { useNavigate } from 'umi'; | ||||||
| 
 | 
 | ||||||
| @ -134,46 +131,6 @@ export const useGetRowSelection = () => { | |||||||
|   return rowSelection; |   return rowSelection; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const useHandleUploadDocument = () => { |  | ||||||
|   const { |  | ||||||
|     visible: documentUploadVisible, |  | ||||||
|     hideModal: hideDocumentUploadModal, |  | ||||||
|     showModal: showDocumentUploadModal, |  | ||||||
|   } = useSetModalState(); |  | ||||||
|   const { uploadDocument, loading } = useUploadNextDocument(); |  | ||||||
| 
 |  | ||||||
|   const onDocumentUploadOk = useCallback( |  | ||||||
|     async (fileList: UploadFile[]): Promise<number | undefined> => { |  | ||||||
|       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 = () => { | export const useHandleWebCrawl = () => { | ||||||
|   const { |   const { | ||||||
|     visible: webCrawlUploadVisible, |     visible: webCrawlUploadVisible, | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ import { FileUploadDialog } from '@/components/file-upload-dialog'; | |||||||
| import ListFilterBar from '@/components/list-filter-bar'; | import ListFilterBar from '@/components/list-filter-bar'; | ||||||
| import { Upload } from 'lucide-react'; | import { Upload } from 'lucide-react'; | ||||||
| import { DatasetTable } from './dataset-table'; | import { DatasetTable } from './dataset-table'; | ||||||
| import { useHandleUploadDocument } from './hooks'; | import { useHandleUploadDocument } from './use-upload-document'; | ||||||
| 
 | 
 | ||||||
| export default function Dataset() { | export default function Dataset() { | ||||||
|   const { |   const { | ||||||
|  | |||||||
							
								
								
									
										44
									
								
								web/src/pages/dataset/dataset/use-upload-document.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								web/src/pages/dataset/dataset/use-upload-document.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<number | undefined> => { | ||||||
|  |       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, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										64
									
								
								web/src/pages/datasets/dataset-card.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								web/src/pages/datasets/dataset-card.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'>; | ||||||
|  | 
 | ||||||
|  | export function DatasetCard({ | ||||||
|  |   dataset, | ||||||
|  |   showDatasetRenameModal, | ||||||
|  | }: DatasetCardProps) { | ||||||
|  |   const { navigateToDataset } = useNavigatePage(); | ||||||
|  |   const displayOwnerName = useDisplayOwnerName(); | ||||||
|  | 
 | ||||||
|  |   const owner = displayOwnerName(dataset.tenant_id, dataset.nickname); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Card | ||||||
|  |       key={dataset.id} | ||||||
|  |       className="bg-colors-background-inverse-weak flex-1" | ||||||
|  |       onClick={navigateToDataset(dataset.id)} | ||||||
|  |     > | ||||||
|  |       <CardContent className="p-4"> | ||||||
|  |         <section className="flex justify-between mb-4"> | ||||||
|  |           <div className="flex  gap-2"> | ||||||
|  |             <Avatar className="w-[70px] h-[70px] rounded-lg"> | ||||||
|  |               <AvatarImage src={dataset.avatar} /> | ||||||
|  |               <AvatarFallback className="rounded-lg">CN</AvatarFallback> | ||||||
|  |             </Avatar> | ||||||
|  |             {owner && <Badge className="h-5">{owner}</Badge>} | ||||||
|  |           </div> | ||||||
|  |           <DatasetDropdown | ||||||
|  |             showDatasetRenameModal={showDatasetRenameModal} | ||||||
|  |             dataset={dataset} | ||||||
|  |           > | ||||||
|  |             <Button variant="ghost" size="icon"> | ||||||
|  |               <Ellipsis /> | ||||||
|  |             </Button> | ||||||
|  |           </DatasetDropdown> | ||||||
|  |         </section> | ||||||
|  |         <div className="flex justify-between items-end"> | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-lg font-semibold mb-2 line-clamp-1"> | ||||||
|  |               {dataset.name} | ||||||
|  |             </h3> | ||||||
|  |             <p className="text-sm opacity-80">{dataset.doc_num} files</p> | ||||||
|  |             <p className="text-sm opacity-80"> | ||||||
|  |               Created {formatDate(dataset.update_time)} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </CardContent> | ||||||
|  |     </Card> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @ -9,7 +9,7 @@ import { | |||||||
| import { useDeleteKnowledge } from '@/hooks/use-knowledge-request'; | import { useDeleteKnowledge } from '@/hooks/use-knowledge-request'; | ||||||
| import { IKnowledge } from '@/interfaces/database/knowledge'; | import { IKnowledge } from '@/interfaces/database/knowledge'; | ||||||
| import { PenLine, Trash2 } from 'lucide-react'; | import { PenLine, Trash2 } from 'lucide-react'; | ||||||
| import { PropsWithChildren, useCallback } from 'react'; | import { MouseEventHandler, PropsWithChildren, useCallback } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { useRenameDataset } from './use-rename-dataset'; | import { useRenameDataset } from './use-rename-dataset'; | ||||||
| 
 | 
 | ||||||
| @ -24,11 +24,16 @@ export function DatasetDropdown({ | |||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const { deleteKnowledge } = useDeleteKnowledge(); |   const { deleteKnowledge } = useDeleteKnowledge(); | ||||||
| 
 | 
 | ||||||
|   const handleShowDatasetRenameModal = useCallback(() => { |   const handleShowDatasetRenameModal: MouseEventHandler<HTMLDivElement> = | ||||||
|     showDatasetRenameModal(dataset); |     useCallback( | ||||||
|   }, [dataset, showDatasetRenameModal]); |       (e) => { | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         showDatasetRenameModal(dataset); | ||||||
|  |       }, | ||||||
|  |       [dataset, showDatasetRenameModal], | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|   const handleDelete = useCallback(() => { |   const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => { | ||||||
|     deleteKnowledge(dataset.id); |     deleteKnowledge(dataset.id); | ||||||
|   }, [dataset.id, deleteKnowledge]); |   }, [dataset.id, deleteKnowledge]); | ||||||
| 
 | 
 | ||||||
| @ -43,7 +48,12 @@ export function DatasetDropdown({ | |||||||
|         <ConfirmDeleteDialog onOk={handleDelete}> |         <ConfirmDeleteDialog onOk={handleDelete}> | ||||||
|           <DropdownMenuItem |           <DropdownMenuItem | ||||||
|             className="text-text-delete-red" |             className="text-text-delete-red" | ||||||
|             onSelect={(e) => e.preventDefault()} |             onSelect={(e) => { | ||||||
|  |               e.preventDefault(); | ||||||
|  |             }} | ||||||
|  |             onClick={(e) => { | ||||||
|  |               e.stopPropagation(); | ||||||
|  |             }} | ||||||
|           > |           > | ||||||
|             {t('common.delete')} <Trash2 /> |             {t('common.delete')} <Trash2 /> | ||||||
|           </DropdownMenuItem> |           </DropdownMenuItem> | ||||||
|  | |||||||
| @ -1,21 +1,14 @@ | |||||||
| import ListFilterBar from '@/components/list-filter-bar'; | import ListFilterBar from '@/components/list-filter-bar'; | ||||||
| import { RenameDialog } from '@/components/rename-dialog'; | 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 { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; | ||||||
| import { formatDate } from '@/utils/date'; |  | ||||||
| import { pick } from 'lodash'; | import { pick } from 'lodash'; | ||||||
| import { ChevronRight, Ellipsis, Plus } from 'lucide-react'; | import { Plus } from 'lucide-react'; | ||||||
| import { PropsWithChildren, useCallback } from 'react'; | import { PropsWithChildren, useCallback } from 'react'; | ||||||
|  | import { DatasetCard } from './dataset-card'; | ||||||
| import { DatasetCreatingDialog } from './dataset-creating-dialog'; | import { DatasetCreatingDialog } from './dataset-creating-dialog'; | ||||||
| import { DatasetDropdown } from './dataset-dropdown'; |  | ||||||
| import { DatasetsFilterPopover } from './datasets-filter-popover'; | import { DatasetsFilterPopover } from './datasets-filter-popover'; | ||||||
| import { DatasetsPagination } from './datasets-pagination'; | import { DatasetsPagination } from './datasets-pagination'; | ||||||
| import { useSaveKnowledge } from './hooks'; | import { useSaveKnowledge } from './hooks'; | ||||||
| import { useDisplayOwnerName } from './use-display-owner'; |  | ||||||
| import { useRenameDataset } from './use-rename-dataset'; | import { useRenameDataset } from './use-rename-dataset'; | ||||||
| 
 | 
 | ||||||
| export default function Datasets() { | export default function Datasets() { | ||||||
| @ -26,7 +19,6 @@ export default function Datasets() { | |||||||
|     onCreateOk, |     onCreateOk, | ||||||
|     loading: creatingLoading, |     loading: creatingLoading, | ||||||
|   } = useSaveKnowledge(); |   } = useSaveKnowledge(); | ||||||
|   const { navigateToDataset } = useNavigatePage(); |  | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     kbs, |     kbs, | ||||||
| @ -48,8 +40,6 @@ export default function Datasets() { | |||||||
|     showDatasetRenameModal, |     showDatasetRenameModal, | ||||||
|   } = useRenameDataset(); |   } = useRenameDataset(); | ||||||
| 
 | 
 | ||||||
|   const displayOwnerName = useDisplayOwnerName(); |  | ||||||
| 
 |  | ||||||
|   const handlePageChange = useCallback( |   const handlePageChange = useCallback( | ||||||
|     (page: number, pageSize?: number) => { |     (page: number, pageSize?: number) => { | ||||||
|       setPagination({ page, pageSize }); |       setPagination({ page, pageSize }); | ||||||
| @ -76,52 +66,12 @@ export default function Datasets() { | |||||||
|       </ListFilterBar> |       </ListFilterBar> | ||||||
|       <div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8"> |       <div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8"> | ||||||
|         {kbs.map((dataset) => { |         {kbs.map((dataset) => { | ||||||
|           const owner = displayOwnerName(dataset.tenant_id, dataset.nickname); |  | ||||||
|           return ( |           return ( | ||||||
|             <Card |             <DatasetCard | ||||||
|  |               dataset={dataset} | ||||||
|               key={dataset.id} |               key={dataset.id} | ||||||
|               className="bg-colors-background-inverse-weak flex-1" |               showDatasetRenameModal={showDatasetRenameModal} | ||||||
|             > |             ></DatasetCard> | ||||||
|               <CardContent className="p-4"> |  | ||||||
|                 <section className="flex justify-between mb-4"> |  | ||||||
|                   <div className="flex  gap-2"> |  | ||||||
|                     <Avatar className="w-[70px] h-[70px] rounded-lg"> |  | ||||||
|                       <AvatarImage src={dataset.avatar} /> |  | ||||||
|                       <AvatarFallback className="rounded-lg">CN</AvatarFallback> |  | ||||||
|                     </Avatar> |  | ||||||
|                     {owner && <Badge className="h-5">{owner}</Badge>} |  | ||||||
|                   </div> |  | ||||||
|                   <DatasetDropdown |  | ||||||
|                     showDatasetRenameModal={showDatasetRenameModal} |  | ||||||
|                     dataset={dataset} |  | ||||||
|                   > |  | ||||||
|                     <Button variant="ghost" size="icon"> |  | ||||||
|                       <Ellipsis /> |  | ||||||
|                     </Button> |  | ||||||
|                   </DatasetDropdown> |  | ||||||
|                 </section> |  | ||||||
|                 <div className="flex justify-between items-end"> |  | ||||||
|                   <div> |  | ||||||
|                     <h3 className="text-lg font-semibold mb-2"> |  | ||||||
|                       {dataset.name} |  | ||||||
|                     </h3> |  | ||||||
|                     <p className="text-sm opacity-80"> |  | ||||||
|                       {dataset.doc_num} files |  | ||||||
|                     </p> |  | ||||||
|                     <p className="text-sm opacity-80"> |  | ||||||
|                       Created {formatDate(dataset.update_time)} |  | ||||||
|                     </p> |  | ||||||
|                   </div> |  | ||||||
|                   <Button |  | ||||||
|                     variant="icon" |  | ||||||
|                     size="icon" |  | ||||||
|                     onClick={navigateToDataset(dataset.id)} |  | ||||||
|                   > |  | ||||||
|                     <ChevronRight className="h-6 w-6" /> |  | ||||||
|                   </Button> |  | ||||||
|                 </div> |  | ||||||
|               </CardContent> |  | ||||||
|             </Card> |  | ||||||
|           ); |           ); | ||||||
|         })} |         })} | ||||||
|       </div> |       </div> | ||||||
|  | |||||||
| @ -6,11 +6,9 @@ import { | |||||||
|   useFetchParentFolderList, |   useFetchParentFolderList, | ||||||
|   useMoveFile, |   useMoveFile, | ||||||
|   useRenameFile, |   useRenameFile, | ||||||
|   useUploadFile, |  | ||||||
| } from '@/hooks/file-manager-hooks'; | } from '@/hooks/file-manager-hooks'; | ||||||
| import { IFile } from '@/interfaces/database/file-manager'; | import { IFile } from '@/interfaces/database/file-manager'; | ||||||
| import { TableRowSelection } from 'antd/es/table/interface'; | import { TableRowSelection } from 'antd/es/table/interface'; | ||||||
| import { UploadFile } from 'antd/lib'; |  | ||||||
| import { useCallback, useMemo, useState } from 'react'; | import { useCallback, useMemo, useState } from 'react'; | ||||||
| import { useNavigate, useSearchParams } from 'umi'; | import { useNavigate, useSearchParams } from 'umi'; | ||||||
| 
 | 
 | ||||||
| @ -157,37 +155,6 @@ export const useHandleDeleteFile = ( | |||||||
|   return { handleRemoveFile }; |   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<number | undefined> => { |  | ||||||
|       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 = () => { | export const useHandleConnectToKnowledge = () => { | ||||||
|   const { |   const { | ||||||
|     visible: connectToKnowledgeVisible, |     visible: connectToKnowledgeVisible, | ||||||
|  | |||||||
| @ -1,15 +1,32 @@ | |||||||
|  | import { FileUploadDialog } from '@/components/file-upload-dialog'; | ||||||
| import ListFilterBar from '@/components/list-filter-bar'; | import ListFilterBar from '@/components/list-filter-bar'; | ||||||
| import { Upload } from 'lucide-react'; | import { Upload } from 'lucide-react'; | ||||||
| import { FilesTable } from './files-table'; | import { FilesTable } from './files-table'; | ||||||
|  | import { useHandleUploadFile } from './use-upload-file'; | ||||||
| 
 | 
 | ||||||
| export default function Files() { | export default function Files() { | ||||||
|  |   const { | ||||||
|  |     fileUploadVisible, | ||||||
|  |     hideFileUploadModal, | ||||||
|  |     showFileUploadModal, | ||||||
|  |     fileUploadLoading, | ||||||
|  |     onFileUploadOk, | ||||||
|  |   } = useHandleUploadFile(); | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <section className="p-8"> |     <section className="p-8"> | ||||||
|       <ListFilterBar title="Files"> |       <ListFilterBar title="Files" showDialog={showFileUploadModal}> | ||||||
|         <Upload /> |         <Upload /> | ||||||
|         Upload file |         Upload file | ||||||
|       </ListFilterBar> |       </ListFilterBar> | ||||||
|       <FilesTable></FilesTable> |       <FilesTable></FilesTable> | ||||||
|  |       {fileUploadVisible && ( | ||||||
|  |         <FileUploadDialog | ||||||
|  |           hideModal={hideFileUploadModal} | ||||||
|  |           onOk={onFileUploadOk} | ||||||
|  |           loading={fileUploadLoading} | ||||||
|  |         ></FileUploadDialog> | ||||||
|  |       )} | ||||||
|     </section> |     </section> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								web/src/pages/files/use-upload-file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/src/pages/files/use-upload-file.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<number | undefined> => { | ||||||
|  |       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, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @ -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 { Button } from '@/components/ui/button'; | ||||||
| import { Card, CardContent } from '@/components/ui/card'; |  | ||||||
| import { CardSkeleton } from '@/components/ui/skeleton'; | import { CardSkeleton } from '@/components/ui/skeleton'; | ||||||
| import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; |  | ||||||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | ||||||
| import { formatDate } from '@/utils/date'; | import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; | ||||||
| import { ChevronRight, Trash2 } from 'lucide-react'; | import { DatasetCard } from '../datasets/dataset-card'; | ||||||
|  | import { useRenameDataset } from '../datasets/use-rename-dataset'; | ||||||
| 
 | 
 | ||||||
| export function Datasets() { | export function Datasets() { | ||||||
|   const { navigateToDatasetList, navigateToDataset } = useNavigatePage(); |   const { navigateToDatasetList } = useNavigatePage(); | ||||||
|   const { list, loading } = useFetchKnowledgeList(); |   const { kbs, loading } = useFetchNextKnowledgeListByPage(); | ||||||
|  |   const { | ||||||
|  |     datasetRenameLoading, | ||||||
|  |     initialDatasetName, | ||||||
|  |     onDatasetRenameOk, | ||||||
|  |     datasetRenameVisible, | ||||||
|  |     hideDatasetRenameModal, | ||||||
|  |     showDatasetRenameModal, | ||||||
|  |   } = useRenameDataset(); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <section> |     <section> | ||||||
| @ -21,50 +28,12 @@ export function Datasets() { | |||||||
|           </div> |           </div> | ||||||
|         ) : ( |         ) : ( | ||||||
|           <div className="flex gap-4 flex-1"> |           <div className="flex gap-4 flex-1"> | ||||||
|             {list.slice(0, 3).map((dataset) => ( |             {kbs.slice(0, 4).map((dataset) => ( | ||||||
|               <Card |               <DatasetCard | ||||||
|                 key={dataset.id} |                 key={dataset.id} | ||||||
|                 className="bg-colors-background-inverse-weak flex-1 border-colors-outline-neutral-standard max-w-96" |                 dataset={dataset} | ||||||
|               > |                 showDatasetRenameModal={showDatasetRenameModal} | ||||||
|                 <CardContent className="p-4"> |               ></DatasetCard> | ||||||
|                   <div className="flex justify-between mb-4"> |  | ||||||
|                     {dataset.avatar ? ( |  | ||||||
|                       <div |  | ||||||
|                         className="w-[70px] h-[70px] rounded-xl bg-cover" |  | ||||||
|                         style={{ backgroundImage: `url(${dataset.avatar})` }} |  | ||||||
|                       /> |  | ||||||
|                     ) : ( |  | ||||||
|                       <Avatar> |  | ||||||
|                         <AvatarImage src="https://github.com/shadcn.png" /> |  | ||||||
|                         <AvatarFallback>CN</AvatarFallback> |  | ||||||
|                       </Avatar> |  | ||||||
|                     )} |  | ||||||
|                     <Button variant="ghost" size="icon"> |  | ||||||
|                       <Trash2 /> |  | ||||||
|                     </Button> |  | ||||||
|                   </div> |  | ||||||
|                   <div className="flex justify-between items-end"> |  | ||||||
|                     <div> |  | ||||||
|                       <h3 className="text-lg font-semibold mb-2"> |  | ||||||
|                         {dataset.name} |  | ||||||
|                       </h3> |  | ||||||
|                       <div className="text-sm opacity-80"> |  | ||||||
|                         {dataset.doc_num} files |  | ||||||
|                       </div> |  | ||||||
|                       <p className="text-sm opacity-80"> |  | ||||||
|                         Created {formatDate(dataset.update_time)} |  | ||||||
|                       </p> |  | ||||||
|                     </div> |  | ||||||
|                     <Button |  | ||||||
|                       variant="icon" |  | ||||||
|                       size="icon" |  | ||||||
|                       onClick={navigateToDataset(dataset.id)} |  | ||||||
|                     > |  | ||||||
|                       <ChevronRight className="h-6 w-6" /> |  | ||||||
|                     </Button> |  | ||||||
|                   </div> |  | ||||||
|                 </CardContent> |  | ||||||
|               </Card> |  | ||||||
|             ))} |             ))} | ||||||
|           </div> |           </div> | ||||||
|         )} |         )} | ||||||
| @ -76,6 +45,14 @@ export function Datasets() { | |||||||
|           See all |           See all | ||||||
|         </Button> |         </Button> | ||||||
|       </div> |       </div> | ||||||
|  |       {datasetRenameVisible && ( | ||||||
|  |         <RenameDialog | ||||||
|  |           hideModal={hideDatasetRenameModal} | ||||||
|  |           onOk={onDatasetRenameOk} | ||||||
|  |           initialName={initialDatasetName} | ||||||
|  |           loading={datasetRenameLoading} | ||||||
|  |         ></RenameDialog> | ||||||
|  |       )} | ||||||
|     </section> |     </section> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 balibabu
						balibabu