### What problem does this PR solve? fix: cannot save the system model setting #468 feat: rename file in FileManager feat: add FileManager feat: override useSelector type ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)tags/v0.3.1
| devtool: 'source-map', | devtool: 'source-map', | ||||
| proxy: { | proxy: { | ||||
| '/v1': { | '/v1': { | ||||
| target: 'http://123.60.95.134:9380/', | |||||
| target: 'http://192.168.200.233:9380/', | |||||
| changeOrigin: true, | changeOrigin: true, | ||||
| // pathRewrite: { '^/v1': '/v1' }, | // pathRewrite: { '^/v1': '/v1' }, | ||||
| }, | }, | 
| // This file is generated by Umi automatically | |||||
| // DO NOT CHANGE IT MANUALLY! | |||||
| type CSSModuleClasses = { readonly [key: string]: string }; | |||||
| declare module '*.css' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| declare module '*.scss' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| declare module '*.sass' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| declare module '*.less' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| declare module '*.styl' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| declare module '*.stylus' { | |||||
| const classes: CSSModuleClasses; | |||||
| export default classes; | |||||
| } | |||||
| // images | |||||
| declare module '*.jpg' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.jpeg' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.png' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.gif' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.svg' { | |||||
| import * as React from 'react'; | |||||
| export const ReactComponent: React.FunctionComponent< | |||||
| React.SVGProps<SVGSVGElement> & { title?: string } | |||||
| >; | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.ico' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.webp' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.avif' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| // media | |||||
| declare module '*.mp4' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.webm' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.ogg' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.mp3' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.wav' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.flac' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.aac' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| // fonts | |||||
| declare module '*.woff' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.woff2' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.eot' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.ttf' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.otf' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| // other | |||||
| declare module '*.wasm' { | |||||
| const initWasm: ( | |||||
| options: WebAssembly.Imports, | |||||
| ) => Promise<WebAssembly.Exports>; | |||||
| export default initWasm; | |||||
| } | |||||
| declare module '*.webmanifest' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.pdf' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | |||||
| declare module '*.txt' { | |||||
| const src: string; | |||||
| export default src; | |||||
| } | 
| import { IFileListRequestBody } from '@/interfaces/request/file-manager'; | |||||
| import { useCallback } from 'react'; | |||||
| import { useDispatch, useSelector } from 'umi'; | |||||
| export const useFetchFileList = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const fetchFileList = useCallback( | |||||
| (payload: IFileListRequestBody) => { | |||||
| return dispatch<any>({ | |||||
| type: 'fileManager/listFile', | |||||
| payload, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| return fetchFileList; | |||||
| }; | |||||
| export const useRemoveFile = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const removeFile = useCallback( | |||||
| (fileIds: string[]) => { | |||||
| return dispatch<any>({ | |||||
| type: 'fileManager/removeFile', | |||||
| payload: { fileIds }, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| return removeFile; | |||||
| }; | |||||
| export const useRenameFile = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const renameFile = useCallback( | |||||
| (fileId: string, name: string) => { | |||||
| return dispatch<any>({ | |||||
| type: 'fileManager/renameFile', | |||||
| payload: { fileId, name }, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| return renameFile; | |||||
| }; | |||||
| export const useFetchParentFolderList = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const fetchParentFolderList = useCallback( | |||||
| (fileId: string) => { | |||||
| return dispatch<any>({ | |||||
| type: 'fileManager/getAllParentFolder', | |||||
| payload: { fileId }, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| return fetchParentFolderList; | |||||
| }; | |||||
| export const useSelectFileList = () => { | |||||
| const fileList = useSelector((state) => state.fileManager.fileList); | |||||
| return fileList; | |||||
| }; | |||||
| export const useSelectParentFolderList = () => { | |||||
| const parentFolderList = useSelector( | |||||
| (state) => state.fileManager.parentFolderList, | |||||
| ); | |||||
| return parentFolderList.toReversed(); | |||||
| }; | 
| export interface IFile { | |||||
| create_date: string; | |||||
| create_time: number; | |||||
| created_by: string; | |||||
| id: string; | |||||
| kb_ids: string[]; | |||||
| location: string; | |||||
| name: string; | |||||
| parent_id: string; | |||||
| size: number; | |||||
| tenant_id: string; | |||||
| type: string; | |||||
| update_date: string; | |||||
| update_time: number; | |||||
| } | |||||
| export interface IFolder { | |||||
| create_date: string; | |||||
| create_time: number; | |||||
| created_by: string; | |||||
| id: string; | |||||
| location: string; | |||||
| name: string; | |||||
| parent_id: string; | |||||
| size: number; | |||||
| tenant_id: string; | |||||
| type: string; | |||||
| update_date: string; | |||||
| update_time: number; | |||||
| } | 
| export interface IPaginationRequestBody { | |||||
| keywords?: string; | |||||
| page?: number; | |||||
| page_size?: number; // name|create|doc_num|create_time|update_time,default:create_time | |||||
| orderby?: string; | |||||
| desc?: string; | |||||
| } | 
| import { IPaginationRequestBody } from './base'; | |||||
| export interface IFileListRequestBody extends IPaginationRequestBody { | |||||
| parent_id?: string; // folder id | |||||
| } | 
| import { ReactComponent as StarIon } from '@/assets/svg/chat-star.svg'; | import { ReactComponent as StarIon } from '@/assets/svg/chat-star.svg'; | ||||
| // import { ReactComponent as FileIcon } from '@/assets/svg/file-management.svg'; | |||||
| import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg'; | import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg'; | ||||
| import { ReactComponent as Logo } from '@/assets/svg/logo.svg'; | import { ReactComponent as Logo } from '@/assets/svg/logo.svg'; | ||||
| import { useTranslate } from '@/hooks/commonHooks'; | import { useTranslate } from '@/hooks/commonHooks'; | 
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [handleDelete, handleRunClick, handleCancelClick, t]); | |||||
| }, [ | |||||
| handleDelete, | |||||
| handleRunClick, | |||||
| handleCancelClick, | |||||
| t, | |||||
| handleDisableClick, | |||||
| handleEnableClick, | |||||
| ]); | |||||
| return ( | return ( | ||||
| <div className={styles.filter}> | <div className={styles.filter}> | 
| import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks'; | |||||
| import { api_host } from '@/utils/api'; | |||||
| import { downloadFile } from '@/utils/fileUtil'; | |||||
| import { | |||||
| DeleteOutlined, | |||||
| DownloadOutlined, | |||||
| EditOutlined, | |||||
| ToolOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { Button, Space, Tooltip } from 'antd'; | |||||
| import { useRemoveFile } from '@/hooks/fileManagerHooks'; | |||||
| import { IFile } from '@/interfaces/database/file-manager'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| record: IFile; | |||||
| setCurrentRecord: (record: any) => void; | |||||
| showRenameModal: (record: IFile) => void; | |||||
| } | |||||
| const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => { | |||||
| const documentId = record.id; | |||||
| const beingUsed = false; | |||||
| const { t } = useTranslate('knowledgeDetails'); | |||||
| const removeDocument = useRemoveFile(); | |||||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||||
| const onRmDocument = () => { | |||||
| if (!beingUsed) { | |||||
| showDeleteConfirm({ | |||||
| onOk: () => { | |||||
| return removeDocument([documentId]); | |||||
| }, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| const onDownloadDocument = () => { | |||||
| downloadFile({ | |||||
| url: `${api_host}/document/get/${documentId}`, | |||||
| filename: record.name, | |||||
| }); | |||||
| }; | |||||
| const setRecord = () => { | |||||
| setCurrentRecord(record); | |||||
| }; | |||||
| const onShowRenameModal = () => { | |||||
| setRecord(); | |||||
| showRenameModal(record); | |||||
| }; | |||||
| return ( | |||||
| <Space size={0}> | |||||
| <Button type="text" className={styles.iconButton}> | |||||
| <ToolOutlined size={20} /> | |||||
| </Button> | |||||
| <Tooltip title={t('rename', { keyPrefix: 'common' })}> | |||||
| <Button | |||||
| type="text" | |||||
| disabled={beingUsed} | |||||
| onClick={onShowRenameModal} | |||||
| className={styles.iconButton} | |||||
| > | |||||
| <EditOutlined size={20} /> | |||||
| </Button> | |||||
| </Tooltip> | |||||
| <Button | |||||
| type="text" | |||||
| disabled={beingUsed} | |||||
| onClick={onRmDocument} | |||||
| className={styles.iconButton} | |||||
| > | |||||
| <DeleteOutlined size={20} /> | |||||
| </Button> | |||||
| <Button | |||||
| type="text" | |||||
| disabled={beingUsed} | |||||
| onClick={onDownloadDocument} | |||||
| className={styles.iconButton} | |||||
| > | |||||
| <DownloadOutlined size={20} /> | |||||
| </Button> | |||||
| </Space> | |||||
| ); | |||||
| }; | |||||
| export default ActionCell; | 
| import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg'; | |||||
| import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks'; | |||||
| import { | |||||
| DownOutlined, | |||||
| FileOutlined, | |||||
| FileTextOutlined, | |||||
| PlusOutlined, | |||||
| SearchOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { | |||||
| Breadcrumb, | |||||
| BreadcrumbProps, | |||||
| Button, | |||||
| Dropdown, | |||||
| Flex, | |||||
| Input, | |||||
| MenuProps, | |||||
| Space, | |||||
| } from 'antd'; | |||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { | |||||
| useFetchDocumentListOnMount, | |||||
| useGetPagination, | |||||
| useHandleSearchChange, | |||||
| useSelectBreadcrumbItems, | |||||
| } from './hooks'; | |||||
| import { useRemoveFile } from '@/hooks/fileManagerHooks'; | |||||
| import { Link } from 'umi'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| selectedRowKeys: string[]; | |||||
| } | |||||
| const itemRender: BreadcrumbProps['itemRender'] = ( | |||||
| currentRoute, | |||||
| params, | |||||
| items, | |||||
| ) => { | |||||
| const isLast = currentRoute?.path === items[items.length - 1]?.path; | |||||
| return isLast ? ( | |||||
| <span>{currentRoute.title}</span> | |||||
| ) : ( | |||||
| <Link to={`${currentRoute.path}`}>{currentRoute.title}</Link> | |||||
| ); | |||||
| }; | |||||
| const FileToolbar = ({ selectedRowKeys }: IProps) => { | |||||
| const { t } = useTranslate('knowledgeDetails'); | |||||
| const { fetchDocumentList } = useFetchDocumentListOnMount(); | |||||
| const { setPagination, searchString } = useGetPagination(fetchDocumentList); | |||||
| const { handleInputChange } = useHandleSearchChange(setPagination); | |||||
| const removeDocument = useRemoveFile(); | |||||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||||
| const breadcrumbItems = useSelectBreadcrumbItems(); | |||||
| const actionItems: MenuProps['items'] = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| key: '1', | |||||
| label: ( | |||||
| <div> | |||||
| <Button type="link"> | |||||
| <Space> | |||||
| <FileTextOutlined /> | |||||
| {t('localFiles')} | |||||
| </Space> | |||||
| </Button> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| { type: 'divider' }, | |||||
| { | |||||
| key: '2', | |||||
| label: ( | |||||
| <div> | |||||
| <Button type="link"> | |||||
| <FileOutlined /> | |||||
| {t('emptyFiles')} | |||||
| </Button> | |||||
| </div> | |||||
| ), | |||||
| // disabled: true, | |||||
| }, | |||||
| ]; | |||||
| }, [t]); | |||||
| const handleDelete = useCallback(() => { | |||||
| showDeleteConfirm({ | |||||
| onOk: () => { | |||||
| return removeDocument(selectedRowKeys); | |||||
| }, | |||||
| }); | |||||
| }, [removeDocument, showDeleteConfirm, selectedRowKeys]); | |||||
| const disabled = selectedRowKeys.length === 0; | |||||
| const items: MenuProps['items'] = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| key: '4', | |||||
| onClick: handleDelete, | |||||
| label: ( | |||||
| <Flex gap={10}> | |||||
| <span className={styles.deleteIconWrapper}> | |||||
| <DeleteIcon width={18} /> | |||||
| </span> | |||||
| <b>{t('delete', { keyPrefix: 'common' })}</b> | |||||
| </Flex> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| }, [handleDelete, t]); | |||||
| return ( | |||||
| <div className={styles.filter}> | |||||
| <Breadcrumb items={breadcrumbItems} itemRender={itemRender} /> | |||||
| <Space> | |||||
| <Dropdown | |||||
| menu={{ items }} | |||||
| placement="bottom" | |||||
| arrow={false} | |||||
| disabled={disabled} | |||||
| > | |||||
| <Button> | |||||
| <Space> | |||||
| <b> {t('bulk')}</b> | |||||
| <DownOutlined /> | |||||
| </Space> | |||||
| </Button> | |||||
| </Dropdown> | |||||
| <Input | |||||
| placeholder={t('searchFiles')} | |||||
| value={searchString} | |||||
| style={{ width: 220 }} | |||||
| allowClear | |||||
| onChange={handleInputChange} | |||||
| prefix={<SearchOutlined />} | |||||
| /> | |||||
| <Dropdown menu={{ items: actionItems }} trigger={['click']}> | |||||
| <Button type="primary" icon={<PlusOutlined />}> | |||||
| {t('addFile')} | |||||
| </Button> | |||||
| </Dropdown> | |||||
| </Space> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default FileToolbar; | 
| import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; | |||||
| import { | |||||
| useFetchFileList, | |||||
| useFetchParentFolderList, | |||||
| useRenameFile, | |||||
| useSelectFileList, | |||||
| useSelectParentFolderList, | |||||
| } from '@/hooks/fileManagerHooks'; | |||||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | |||||
| import { Pagination } from '@/interfaces/common'; | |||||
| import { IFile } from '@/interfaces/database/file-manager'; | |||||
| import { PaginationProps } from 'antd'; | |||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||||
| import { useDispatch, useNavigate, useSearchParams, useSelector } from 'umi'; | |||||
| export const useGetFolderId = () => { | |||||
| const [searchParams] = useSearchParams(); | |||||
| const id = searchParams.get('folderId') as string; | |||||
| return id; | |||||
| }; | |||||
| export const useFetchDocumentListOnMount = () => { | |||||
| const fetchDocumentList = useFetchFileList(); | |||||
| const fileList = useSelectFileList(); | |||||
| const id = useGetFolderId(); | |||||
| const dispatch = useDispatch(); | |||||
| useEffect(() => { | |||||
| fetchDocumentList({ parent_id: id }); | |||||
| }, [dispatch, fetchDocumentList, id]); | |||||
| return { fetchDocumentList, fileList }; | |||||
| }; | |||||
| export const useGetPagination = ( | |||||
| fetchDocumentList: (payload: IFile) => any, | |||||
| ) => { | |||||
| const dispatch = useDispatch(); | |||||
| const kFModel = useSelector((state: any) => state.kFModel); | |||||
| const { t } = useTranslate('common'); | |||||
| const setPagination = useCallback( | |||||
| (pageNumber = 1, pageSize?: number) => { | |||||
| const pagination: Pagination = { | |||||
| current: pageNumber, | |||||
| } as Pagination; | |||||
| if (pageSize) { | |||||
| pagination.pageSize = pageSize; | |||||
| } | |||||
| dispatch({ | |||||
| type: 'kFModel/setPagination', | |||||
| payload: pagination, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| const onPageChange: PaginationProps['onChange'] = useCallback( | |||||
| (pageNumber: number, pageSize: number) => { | |||||
| setPagination(pageNumber, pageSize); | |||||
| fetchDocumentList(); | |||||
| }, | |||||
| [fetchDocumentList, setPagination], | |||||
| ); | |||||
| const pagination: PaginationProps = useMemo(() => { | |||||
| return { | |||||
| showQuickJumper: true, | |||||
| total: kFModel.total, | |||||
| showSizeChanger: true, | |||||
| current: kFModel.pagination.current, | |||||
| pageSize: kFModel.pagination.pageSize, | |||||
| pageSizeOptions: [1, 2, 10, 20, 50, 100], | |||||
| onChange: onPageChange, | |||||
| showTotal: (total) => `${t('total')} ${total}`, | |||||
| }; | |||||
| }, [kFModel, onPageChange, t]); | |||||
| return { | |||||
| pagination, | |||||
| setPagination, | |||||
| total: kFModel.total, | |||||
| searchString: kFModel.searchString, | |||||
| }; | |||||
| }; | |||||
| export const useHandleSearchChange = (setPagination: () => void) => { | |||||
| const dispatch = useDispatch(); | |||||
| const throttledGetDocumentList = useCallback(() => { | |||||
| dispatch({ | |||||
| type: 'kFModel/throttledGetDocumentList', | |||||
| }); | |||||
| }, [dispatch]); | |||||
| const handleInputChange = useCallback( | |||||
| (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { | |||||
| const value = e.target.value; | |||||
| dispatch({ type: 'kFModel/setSearchString', payload: value }); | |||||
| setPagination(); | |||||
| throttledGetDocumentList(); | |||||
| }, | |||||
| [setPagination, throttledGetDocumentList, dispatch], | |||||
| ); | |||||
| return { handleInputChange }; | |||||
| }; | |||||
| export const useGetRowSelection = () => { | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||||
| const rowSelection = { | |||||
| selectedRowKeys, | |||||
| onChange: (newSelectedRowKeys: React.Key[]) => { | |||||
| setSelectedRowKeys(newSelectedRowKeys); | |||||
| }, | |||||
| }; | |||||
| return rowSelection; | |||||
| }; | |||||
| export const useNavigateToOtherFolder = () => { | |||||
| const navigate = useNavigate(); | |||||
| const navigateToOtherFolder = useCallback( | |||||
| (folderId: string) => { | |||||
| navigate(`/file?folderId=${folderId}`); | |||||
| }, | |||||
| [navigate], | |||||
| ); | |||||
| return navigateToOtherFolder; | |||||
| }; | |||||
| export const useRenameCurrentFile = () => { | |||||
| const [file, setFile] = useState<IFile>({} as IFile); | |||||
| const { | |||||
| visible: fileRenameVisible, | |||||
| hideModal: hideFileRenameModal, | |||||
| showModal: showFileRenameModal, | |||||
| } = useSetModalState(); | |||||
| const renameFile = useRenameFile(); | |||||
| const onFileRenameOk = useCallback( | |||||
| async (name: string) => { | |||||
| const ret = await renameFile(file.id, name); | |||||
| if (ret === 0) { | |||||
| hideFileRenameModal(); | |||||
| } | |||||
| }, | |||||
| [renameFile, file, hideFileRenameModal], | |||||
| ); | |||||
| const loading = useOneNamespaceEffectsLoading('fileManager', ['renameFile']); | |||||
| const handleShowFileRenameModal = useCallback( | |||||
| async (record: IFile) => { | |||||
| setFile(record); | |||||
| showFileRenameModal(); | |||||
| }, | |||||
| [showFileRenameModal], | |||||
| ); | |||||
| return { | |||||
| fileRenameLoading: loading, | |||||
| initialFileName: file.name, | |||||
| onFileRenameOk, | |||||
| fileRenameVisible, | |||||
| hideFileRenameModal, | |||||
| showFileRenameModal: handleShowFileRenameModal, | |||||
| }; | |||||
| }; | |||||
| export const useSelectBreadcrumbItems = () => { | |||||
| const parentFolderList = useSelectParentFolderList(); | |||||
| const id = useGetFolderId(); | |||||
| const fetchParentFolderList = useFetchParentFolderList(); | |||||
| useEffect(() => { | |||||
| if (id) { | |||||
| fetchParentFolderList(id); | |||||
| } | |||||
| }, [id, fetchParentFolderList]); | |||||
| return parentFolderList.length === 1 | |||||
| ? [] | |||||
| : parentFolderList.map((x) => ({ | |||||
| title: x.name === '/' ? 'root' : x.name, | |||||
| path: `/file?folderId=${x.id}`, | |||||
| })); | |||||
| }; | 
| .fileManagerWrapper { | |||||
| flex-basis: 100%; | |||||
| padding: 32px; | |||||
| } | |||||
| .filter { | |||||
| height: 32px; | |||||
| display: flex; | |||||
| margin: 10px 0; | |||||
| justify-content: space-between; | |||||
| padding: 24px 0; | |||||
| align-items: center; | |||||
| } | |||||
| .deleteIconWrapper { | |||||
| width: 22px; | |||||
| text-align: center; | |||||
| } | 
| import { useSelectFileList } from '@/hooks/fileManagerHooks'; | |||||
| import { IFile } from '@/interfaces/database/file-manager'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { Button, Table } from 'antd'; | |||||
| import { ColumnsType } from 'antd/es/table'; | |||||
| import ActionCell from './action-cell'; | |||||
| import FileToolbar from './file-toolbar'; | |||||
| import { | |||||
| useGetRowSelection, | |||||
| useNavigateToOtherFolder, | |||||
| useRenameCurrentFile, | |||||
| } from './hooks'; | |||||
| import RenameModal from '@/components/rename-modal'; | |||||
| import styles from './index.less'; | |||||
| const FileManager = () => { | |||||
| const fileList = useSelectFileList(); | |||||
| const rowSelection = useGetRowSelection(); | |||||
| const navigateToOtherFolder = useNavigateToOtherFolder(); | |||||
| const { | |||||
| fileRenameVisible, | |||||
| fileRenameLoading, | |||||
| hideFileRenameModal, | |||||
| showFileRenameModal, | |||||
| initialFileName, | |||||
| onFileRenameOk, | |||||
| } = useRenameCurrentFile(); | |||||
| const columns: ColumnsType<IFile> = [ | |||||
| { | |||||
| title: 'Name', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| render(value, record) { | |||||
| return record.type === 'folder' ? ( | |||||
| <Button | |||||
| type={'link'} | |||||
| onClick={() => navigateToOtherFolder(record.id)} | |||||
| > | |||||
| {value} | |||||
| </Button> | |||||
| ) : ( | |||||
| value | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: 'Upload Date', | |||||
| dataIndex: 'create_date', | |||||
| key: 'create_date', | |||||
| render(text) { | |||||
| return formatDate(text); | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: 'Location', | |||||
| dataIndex: 'location', | |||||
| key: 'location', | |||||
| }, | |||||
| { | |||||
| title: 'Action', | |||||
| dataIndex: 'action', | |||||
| key: 'action', | |||||
| render: (text, record) => ( | |||||
| <ActionCell | |||||
| record={record} | |||||
| setCurrentRecord={(record: any) => { | |||||
| console.info(record); | |||||
| }} | |||||
| showRenameModal={showFileRenameModal} | |||||
| ></ActionCell> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <section className={styles.fileManagerWrapper}> | |||||
| <FileToolbar | |||||
| selectedRowKeys={rowSelection.selectedRowKeys as string[]} | |||||
| ></FileToolbar> | |||||
| <Table | |||||
| dataSource={fileList} | |||||
| columns={columns} | |||||
| rowKey={'id'} | |||||
| rowSelection={rowSelection} | |||||
| /> | |||||
| <RenameModal | |||||
| visible={fileRenameVisible} | |||||
| hideModal={hideFileRenameModal} | |||||
| onOk={onFileRenameOk} | |||||
| initialName={initialFileName} | |||||
| loading={fileRenameLoading} | |||||
| ></RenameModal> | |||||
| </section> | |||||
| ); | |||||
| }; | |||||
| export default FileManager; | 
| import { IFile, IFolder } from '@/interfaces/database/file-manager'; | |||||
| import fileManagerService from '@/services/fileManagerService'; | |||||
| import { DvaModel } from 'umi'; | |||||
| export interface FileManagerModelState { | |||||
| fileList: IFile[]; | |||||
| parentFolderList: IFolder[]; | |||||
| } | |||||
| const model: DvaModel<FileManagerModelState> = { | |||||
| namespace: 'fileManager', | |||||
| state: { fileList: [], parentFolderList: [] }, | |||||
| reducers: { | |||||
| setFileList(state, { payload }) { | |||||
| return { ...state, fileList: payload }; | |||||
| }, | |||||
| setParentFolderList(state, { payload }) { | |||||
| return { ...state, parentFolderList: payload }; | |||||
| }, | |||||
| }, | |||||
| effects: { | |||||
| *removeFile({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(fileManagerService.removeFile, payload); | |||||
| const { retcode } = data; | |||||
| if (retcode === 0) { | |||||
| yield put({ | |||||
| type: 'listFile', | |||||
| payload: data.data?.files ?? [], | |||||
| }); | |||||
| } | |||||
| }, | |||||
| *listFile({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(fileManagerService.listFile, payload); | |||||
| const { retcode, data: res } = data; | |||||
| if (retcode === 0 && Array.isArray(res.files)) { | |||||
| yield put({ | |||||
| type: 'setFileList', | |||||
| payload: res.files, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| *renameFile({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(fileManagerService.renameFile, payload); | |||||
| if (data.retcode === 0) { | |||||
| yield put({ type: 'listFile' }); | |||||
| } | |||||
| return data.retcode; | |||||
| }, | |||||
| *getAllParentFolder({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call( | |||||
| fileManagerService.getAllParentFolder, | |||||
| payload, | |||||
| ); | |||||
| if (data.retcode === 0) { | |||||
| yield put({ | |||||
| type: 'setParentFolderList', | |||||
| payload: data.data?.parent_folders ?? [], | |||||
| }); | |||||
| } | |||||
| return data.retcode; | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| export default model; | 
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Upload } from 'antd'; | |||||
| import React, { useEffect, useState } from 'react'; | |||||
| const File: React.FC = () => { | |||||
| const [fileList, setFileList] = useState([ | |||||
| { | |||||
| uid: '0', | |||||
| name: 'xxx.png', | |||||
| status: 'uploading', | |||||
| percent: 10, | |||||
| }, | |||||
| ]); | |||||
| const obj = { | |||||
| uid: '-1', | |||||
| name: 'yyy.png', | |||||
| status: 'done', | |||||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||||
| thumbUrl: | |||||
| 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||||
| }; | |||||
| useEffect(() => { | |||||
| const timer = setInterval(() => { | |||||
| setFileList((fileList: any) => { | |||||
| const percent = fileList[0]?.percent; | |||||
| if (percent + 10 >= 100) { | |||||
| clearInterval(timer); | |||||
| return [obj]; | |||||
| } | |||||
| const list = [{ ...fileList[0], percent: percent + 10 }]; | |||||
| console.log(list); | |||||
| return list; | |||||
| }); | |||||
| }, 300); | |||||
| }, []); | |||||
| return ( | |||||
| <> | |||||
| <Upload | |||||
| action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188" | |||||
| listType="picture" | |||||
| fileList={[...fileList]} | |||||
| multiple | |||||
| > | |||||
| <Button icon={<UploadOutlined />}>Upload</Button> | |||||
| </Upload> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default File; | 
| Sign in with Google | Sign in with Google | ||||
| </div> | </div> | ||||
| </Button> */} | </Button> */} | ||||
| <Button | |||||
| block | |||||
| size="large" | |||||
| onClick={toGoogle} | |||||
| style={{ marginTop: 15 }} | |||||
| > | |||||
| <div> | |||||
| <Icon | |||||
| icon="local:github" | |||||
| style={{ verticalAlign: 'middle', marginRight: 5 }} | |||||
| /> | |||||
| Sign in with Github | |||||
| </div> | |||||
| </Button> | |||||
| {location.host === 'demo.ragflow.io' && ( | |||||
| <Button | |||||
| block | |||||
| size="large" | |||||
| onClick={toGoogle} | |||||
| style={{ marginTop: 15 }} | |||||
| > | |||||
| <div> | |||||
| <Icon | |||||
| icon="local:github" | |||||
| style={{ verticalAlign: 'middle', marginRight: 5 }} | |||||
| /> | |||||
| Sign in with Github | |||||
| </div> | |||||
| </Button> | |||||
| )} | |||||
| </> | </> | ||||
| )} | )} | ||||
| </Form> | </Form> | 
| }, | }, | ||||
| { | { | ||||
| path: '/file', | path: '/file', | ||||
| component: '@/pages/file', | |||||
| component: '@/pages/file-manager', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | 
| import api from '@/utils/api'; | |||||
| import registerServer from '@/utils/registerServer'; | |||||
| import request from '@/utils/request'; | |||||
| const { listFile, removeFile, uploadFile, renameFile, getAllParentFolder } = | |||||
| api; | |||||
| const methods = { | |||||
| listFile: { | |||||
| url: listFile, | |||||
| method: 'get', | |||||
| }, | |||||
| removeFile: { | |||||
| url: removeFile, | |||||
| method: 'post', | |||||
| }, | |||||
| uploadFile: { | |||||
| url: uploadFile, | |||||
| method: 'post', | |||||
| }, | |||||
| renameFile: { | |||||
| url: renameFile, | |||||
| method: 'post', | |||||
| }, | |||||
| getAllParentFolder: { | |||||
| url: getAllParentFolder, | |||||
| method: 'get', | |||||
| }, | |||||
| } as const; | |||||
| const fileManagerService = registerServer<keyof typeof methods>( | |||||
| methods, | |||||
| request, | |||||
| ); | |||||
| export default fileManagerService; | 
| createExternalConversation: `${api_host}/api/new_conversation`, | createExternalConversation: `${api_host}/api/new_conversation`, | ||||
| getExternalConversation: `${api_host}/api/conversation`, | getExternalConversation: `${api_host}/api/conversation`, | ||||
| completeExternalConversation: `${api_host}/api/completion`, | completeExternalConversation: `${api_host}/api/completion`, | ||||
| // file manager | |||||
| listFile: `${api_host}/file/list`, | |||||
| uploadFile: `${api_host}/file/upload`, | |||||
| removeFile: `${api_host}/file/rm`, | |||||
| renameFile: `${api_host}/file/rename`, | |||||
| getAllParentFolder: `${api_host}/file/all_parent_folder`, | |||||
| }; | }; | 
| return data instanceof FormData; | return data instanceof FormData; | ||||
| }; | }; | ||||
| const excludedFields = ['img2txt_id']; | |||||
| const isExcludedField = (key: string) => { | |||||
| return excludedFields.includes(key); | |||||
| }; | |||||
| export const convertTheKeysOfTheObjectToSnake = (data: unknown) => { | export const convertTheKeysOfTheObjectToSnake = (data: unknown) => { | ||||
| if (isObject(data) && !isFormData(data)) { | if (isObject(data) && !isFormData(data)) { | ||||
| return Object.keys(data).reduce<Record<string, any>>((pre, cur) => { | return Object.keys(data).reduce<Record<string, any>>((pre, cur) => { | ||||
| const value = (data as Record<string, any>)[cur]; | const value = (data as Record<string, any>)[cur]; | ||||
| pre[isFormData(value) ? cur : snakeCase(cur)] = value; | |||||
| pre[isFormData(value) || isExcludedField(cur) ? cur : snakeCase(cur)] = | |||||
| value; | |||||
| return pre; | return pre; | ||||
| }, {}); | }, {}); | ||||
| } | } | 
| import 'umi/typings'; | |||||
| import { ChunkModelState } from '@/pages/add-knowledge/components/knowledge-chunk/model'; | |||||
| import { KFModelState } from '@/pages/add-knowledge/components/knowledge-file/model'; | |||||
| import { KSModelState } from '@/pages/add-knowledge/components/knowledge-setting/model'; | |||||
| import { TestingModelState } from '@/pages/add-knowledge/components/knowledge-testing/model'; | |||||
| import { kAModelState } from '@/pages/add-knowledge/model'; | |||||
| import { ChatModelState } from '@/pages/chat/model'; | |||||
| import { FileManagerModelState } from '@/pages/file-manager/model'; | |||||
| import { KnowledgeModelState } from '@/pages/knowledge/model'; | |||||
| import { LoginModelState } from '@/pages/login/model'; | |||||
| import { SettingModelState } from '@/pages/user-setting/model'; | |||||
| declare module 'lodash'; | declare module 'lodash'; | ||||
| // declare type Nullable<T> = T | null; invalid | |||||
| function useSelector<TState = RootState, TSelected = unknown>( | |||||
| selector: (state: TState) => TSelected, | |||||
| equalityFn?: (left: TSelected, right: TSelected) => boolean, | |||||
| ): TSelected; | |||||
| export interface RootState { | |||||
| // loading: Loading; | |||||
| fileManager: FileManagerModelState; | |||||
| chatModel: ChatModelState; | |||||
| loginModel: LoginModelState; | |||||
| knowledgeModel: KnowledgeModelState; | |||||
| settingModel: SettingModelState; | |||||
| kFModel: KFModelState; | |||||
| kAModel: kAModelState; | |||||
| chunkModel: ChunkModelState; | |||||
| kSModel: KSModelState; | |||||
| testingModel: TestingModelState; | |||||
| } | |||||
| declare global { | declare global { | ||||
| type Nullable<T> = T | null; | type Nullable<T> = T | null; | ||||
| } | } | ||||
| declare module 'umi' { | |||||
| export { useSelector }; | |||||
| } |