### What problem does this PR solve? Feat: Upload document #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.18.0
| @@ -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} | |||
| @@ -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 }; | |||
| }; | |||
| @@ -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 { | |||
| @@ -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, | |||
| }; | |||
| }; | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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> | |||
| ); | |||
| } | |||