mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-06-04 11:24:00 +08:00
### 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)
This commit is contained in:
parent
aa71462a9f
commit
6405041b4d
@ -27,7 +27,7 @@ export default defineConfig({
|
||||
devtool: 'source-map',
|
||||
proxy: {
|
||||
'/v1': {
|
||||
target: 'http://123.60.95.134:9380/',
|
||||
target: 'http://192.168.200.233:9380/',
|
||||
changeOrigin: true,
|
||||
// pathRewrite: { '^/v1': '/v1' },
|
||||
},
|
||||
|
138
web/externals.d.ts
vendored
Normal file
138
web/externals.d.ts
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
// 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;
|
||||
}
|
80
web/src/hooks/fileManagerHooks.ts
Normal file
80
web/src/hooks/fileManagerHooks.ts
Normal file
@ -0,0 +1,80 @@
|
||||
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();
|
||||
};
|
30
web/src/interfaces/database/file-manager.ts
Normal file
30
web/src/interfaces/database/file-manager.ts
Normal file
@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
7
web/src/interfaces/request/base.ts
Normal file
7
web/src/interfaces/request/base.ts
Normal file
@ -0,0 +1,7 @@
|
||||
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;
|
||||
}
|
5
web/src/interfaces/request/file-manager.ts
Normal file
5
web/src/interfaces/request/file-manager.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { IPaginationRequestBody } from './base';
|
||||
|
||||
export interface IFileListRequestBody extends IPaginationRequestBody {
|
||||
parent_id?: string; // folder id
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
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 Logo } from '@/assets/svg/logo.svg';
|
||||
import { useTranslate } from '@/hooks/commonHooks';
|
||||
|
@ -182,7 +182,14 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [handleDelete, handleRunClick, handleCancelClick, t]);
|
||||
}, [
|
||||
handleDelete,
|
||||
handleRunClick,
|
||||
handleCancelClick,
|
||||
t,
|
||||
handleDisableClick,
|
||||
handleEnableClick,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.filter}>
|
||||
|
0
web/src/pages/file-manager/action-cell/index.less
Normal file
0
web/src/pages/file-manager/action-cell/index.less
Normal file
91
web/src/pages/file-manager/action-cell/index.tsx
Normal file
91
web/src/pages/file-manager/action-cell/index.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
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;
|
153
web/src/pages/file-manager/file-toolbar.tsx
Normal file
153
web/src/pages/file-manager/file-toolbar.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
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;
|
193
web/src/pages/file-manager/hooks.ts
Normal file
193
web/src/pages/file-manager/hooks.ts
Normal file
@ -0,0 +1,193 @@
|
||||
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}`,
|
||||
}));
|
||||
};
|
18
web/src/pages/file-manager/index.less
Normal file
18
web/src/pages/file-manager/index.less
Normal file
@ -0,0 +1,18 @@
|
||||
.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;
|
||||
}
|
99
web/src/pages/file-manager/index.tsx
Normal file
99
web/src/pages/file-manager/index.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
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;
|
65
web/src/pages/file-manager/model.ts
Normal file
65
web/src/pages/file-manager/model.ts
Normal file
@ -0,0 +1,65 @@
|
||||
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;
|
@ -1,50 +0,0 @@
|
||||
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;
|
@ -167,20 +167,22 @@ const Login = () => {
|
||||
Sign in with Google
|
||||
</div>
|
||||
</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>
|
||||
|
@ -82,7 +82,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/file',
|
||||
component: '@/pages/file',
|
||||
component: '@/pages/file-manager',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
36
web/src/services/fileManagerService.ts
Normal file
36
web/src/services/fileManagerService.ts
Normal file
@ -0,0 +1,36 @@
|
||||
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;
|
@ -66,4 +66,11 @@ export default {
|
||||
createExternalConversation: `${api_host}/api/new_conversation`,
|
||||
getExternalConversation: `${api_host}/api/conversation`,
|
||||
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`,
|
||||
};
|
||||
|
@ -5,11 +5,18 @@ export const isFormData = (data: unknown): data is FormData => {
|
||||
return data instanceof FormData;
|
||||
};
|
||||
|
||||
const excludedFields = ['img2txt_id'];
|
||||
|
||||
const isExcludedField = (key: string) => {
|
||||
return excludedFields.includes(key);
|
||||
};
|
||||
|
||||
export const convertTheKeysOfTheObjectToSnake = (data: unknown) => {
|
||||
if (isObject(data) && !isFormData(data)) {
|
||||
return Object.keys(data).reduce<Record<string, any>>((pre, 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;
|
||||
}, {});
|
||||
}
|
||||
|
35
web/typings.d.ts
vendored
35
web/typings.d.ts
vendored
@ -1,8 +1,39 @@
|
||||
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 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 {
|
||||
type Nullable<T> = T | null;
|
||||
}
|
||||
|
||||
declare module 'umi' {
|
||||
export { useSelector };
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user