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 ( | ||||
|     <AlertDialog> | ||||
|       <AlertDialogTrigger asChild>{children}</AlertDialogTrigger> | ||||
|       <AlertDialogContent> | ||||
|       <AlertDialogContent | ||||
|         onSelect={(e) => e.preventDefault()} | ||||
|         onClick={(e) => e.stopPropagation()} | ||||
|       > | ||||
|         <AlertDialogHeader> | ||||
|           <AlertDialogTitle> | ||||
|             {title ?? t('common.deleteModalTitle')} | ||||
|  | ||||
| @ -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<SetStateAction<File[]>>; | ||||
| }; | ||||
| 
 | ||||
|   const [files, setFiles] = useState<File[]>([]); | ||||
|   console.log('🚀 ~ TabsDemo ~ files:', files); | ||||
| export function UploaderTabs({ setFiles }: UploaderTabsProps) { | ||||
|   const { t } = useTranslation(); | ||||
| 
 | ||||
|   return ( | ||||
|     <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 [files, setFiles] = useState<File[]>([]); | ||||
| 
 | ||||
|   const handleOk = useCallback(() => { | ||||
|     onOk?.(files); | ||||
|   }, [files, onOk]); | ||||
| 
 | ||||
|   return ( | ||||
|     <Dialog open onOpenChange={hideModal}> | ||||
| @ -45,9 +51,14 @@ export function FileUploadDialog({ hideModal }: IModalProps<any>) { | ||||
|         <DialogHeader> | ||||
|           <DialogTitle>{t('fileManager.uploadFile')}</DialogTitle> | ||||
|         </DialogHeader> | ||||
|         <UploaderTabs></UploaderTabs> | ||||
|         <UploaderTabs setFiles={setFiles}></UploaderTabs> | ||||
|         <DialogFooter> | ||||
|           <Button type="submit" variant={'tertiary'} size={'sm'}> | ||||
|           <Button | ||||
|             type="submit" | ||||
|             variant={'tertiary'} | ||||
|             size={'sm'} | ||||
|             onClick={handleOk} | ||||
|           > | ||||
|             {t('common.save')} | ||||
|           </Button> | ||||
|         </DialogFooter> | ||||
|  | ||||
| @ -14,6 +14,7 @@ interface IProps { | ||||
|   searchString?: string; | ||||
|   onSearchChange?: ChangeEventHandler<HTMLInputElement>; | ||||
|   count?: number; | ||||
|   showFilter?: boolean; | ||||
| } | ||||
| 
 | ||||
| const FilterButton = React.forwardRef< | ||||
| @ -35,18 +36,20 @@ export default function ListFilterBar({ | ||||
|   searchString, | ||||
|   onSearchChange, | ||||
|   count, | ||||
|   showFilter = true, | ||||
| }: PropsWithChildren<IProps>) { | ||||
|   return ( | ||||
|     <div className="flex justify-between mb-6"> | ||||
|       <span className="text-3xl font-bold ">{title}</span> | ||||
|       <div className="flex gap-4 items-center"> | ||||
|         {FilterPopover ? ( | ||||
|           <FilterPopover> | ||||
|             <FilterButton count={count}></FilterButton> | ||||
|           </FilterPopover> | ||||
|         ) : ( | ||||
|           <FilterButton></FilterButton> | ||||
|         )} | ||||
|         {showFilter && | ||||
|           (FilterPopover ? ( | ||||
|             <FilterPopover> | ||||
|               <FilterButton count={count}></FilterButton> | ||||
|             </FilterPopover> | ||||
|           ) : ( | ||||
|             <FilterButton></FilterButton> | ||||
|           ))} | ||||
| 
 | ||||
|         <SearchInput | ||||
|           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, | ||||
|   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<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 = () => { | ||||
|   const { | ||||
|     visible: webCrawlUploadVisible, | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
							
								
								
									
										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 { 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<HTMLDivElement> = | ||||
|     useCallback( | ||||
|       (e) => { | ||||
|         e.stopPropagation(); | ||||
|         showDatasetRenameModal(dataset); | ||||
|       }, | ||||
|       [dataset, showDatasetRenameModal], | ||||
|     ); | ||||
| 
 | ||||
|   const handleDelete = useCallback(() => { | ||||
|   const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => { | ||||
|     deleteKnowledge(dataset.id); | ||||
|   }, [dataset.id, deleteKnowledge]); | ||||
| 
 | ||||
| @ -43,7 +48,12 @@ export function DatasetDropdown({ | ||||
|         <ConfirmDeleteDialog onOk={handleDelete}> | ||||
|           <DropdownMenuItem | ||||
|             className="text-text-delete-red" | ||||
|             onSelect={(e) => e.preventDefault()} | ||||
|             onSelect={(e) => { | ||||
|               e.preventDefault(); | ||||
|             }} | ||||
|             onClick={(e) => { | ||||
|               e.stopPropagation(); | ||||
|             }} | ||||
|           > | ||||
|             {t('common.delete')} <Trash2 /> | ||||
|           </DropdownMenuItem> | ||||
|  | ||||
| @ -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() { | ||||
|       </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"> | ||||
|         {kbs.map((dataset) => { | ||||
|           const owner = displayOwnerName(dataset.tenant_id, dataset.nickname); | ||||
|           return ( | ||||
|             <Card | ||||
|             <DatasetCard | ||||
|               dataset={dataset} | ||||
|               key={dataset.id} | ||||
|               className="bg-colors-background-inverse-weak flex-1" | ||||
|             > | ||||
|               <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> | ||||
|               showDatasetRenameModal={showDatasetRenameModal} | ||||
|             ></DatasetCard> | ||||
|           ); | ||||
|         })} | ||||
|       </div> | ||||
|  | ||||
| @ -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<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 = () => { | ||||
|   const { | ||||
|     visible: connectToKnowledgeVisible, | ||||
|  | ||||
| @ -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 ( | ||||
|     <section className="p-8"> | ||||
|       <ListFilterBar title="Files"> | ||||
|       <ListFilterBar title="Files" showDialog={showFileUploadModal}> | ||||
|         <Upload /> | ||||
|         Upload file | ||||
|       </ListFilterBar> | ||||
|       <FilesTable></FilesTable> | ||||
|       {fileUploadVisible && ( | ||||
|         <FileUploadDialog | ||||
|           hideModal={hideFileUploadModal} | ||||
|           onOk={onFileUploadOk} | ||||
|           loading={fileUploadLoading} | ||||
|         ></FileUploadDialog> | ||||
|       )} | ||||
|     </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 { 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 ( | ||||
|     <section> | ||||
| @ -21,50 +28,12 @@ export function Datasets() { | ||||
|           </div> | ||||
|         ) : ( | ||||
|           <div className="flex gap-4 flex-1"> | ||||
|             {list.slice(0, 3).map((dataset) => ( | ||||
|               <Card | ||||
|             {kbs.slice(0, 4).map((dataset) => ( | ||||
|               <DatasetCard | ||||
|                 key={dataset.id} | ||||
|                 className="bg-colors-background-inverse-weak flex-1 border-colors-outline-neutral-standard max-w-96" | ||||
|               > | ||||
|                 <CardContent className="p-4"> | ||||
|                   <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> | ||||
|                 dataset={dataset} | ||||
|                 showDatasetRenameModal={showDatasetRenameModal} | ||||
|               ></DatasetCard> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
| @ -76,6 +45,14 @@ export function Datasets() { | ||||
|           See all | ||||
|         </Button> | ||||
|       </div> | ||||
|       {datasetRenameVisible && ( | ||||
|         <RenameDialog | ||||
|           hideModal={hideDatasetRenameModal} | ||||
|           onOk={onDatasetRenameOk} | ||||
|           initialName={initialDatasetName} | ||||
|           loading={datasetRenameLoading} | ||||
|         ></RenameDialog> | ||||
|       )} | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 balibabu
						balibabu