balibabu commited on
Commit
8c06509
·
1 Parent(s): 4b407a8

fix: cannot save the system model setting #468 (#508)

Browse files

### 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)

web/.umirc.ts CHANGED
@@ -27,7 +27,7 @@ export default defineConfig({
27
  devtool: 'source-map',
28
  proxy: {
29
  '/v1': {
30
- target: 'http://123.60.95.134:9380/',
31
  changeOrigin: true,
32
  // pathRewrite: { '^/v1': '/v1' },
33
  },
 
27
  devtool: 'source-map',
28
  proxy: {
29
  '/v1': {
30
+ target: 'http://192.168.200.233:9380/',
31
  changeOrigin: true,
32
  // pathRewrite: { '^/v1': '/v1' },
33
  },
web/externals.d.ts ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This file is generated by Umi automatically
2
+ // DO NOT CHANGE IT MANUALLY!
3
+ type CSSModuleClasses = { readonly [key: string]: string };
4
+ declare module '*.css' {
5
+ const classes: CSSModuleClasses;
6
+ export default classes;
7
+ }
8
+ declare module '*.scss' {
9
+ const classes: CSSModuleClasses;
10
+ export default classes;
11
+ }
12
+ declare module '*.sass' {
13
+ const classes: CSSModuleClasses;
14
+ export default classes;
15
+ }
16
+ declare module '*.less' {
17
+ const classes: CSSModuleClasses;
18
+ export default classes;
19
+ }
20
+ declare module '*.styl' {
21
+ const classes: CSSModuleClasses;
22
+ export default classes;
23
+ }
24
+ declare module '*.stylus' {
25
+ const classes: CSSModuleClasses;
26
+ export default classes;
27
+ }
28
+
29
+ // images
30
+ declare module '*.jpg' {
31
+ const src: string;
32
+ export default src;
33
+ }
34
+ declare module '*.jpeg' {
35
+ const src: string;
36
+ export default src;
37
+ }
38
+ declare module '*.png' {
39
+ const src: string;
40
+ export default src;
41
+ }
42
+ declare module '*.gif' {
43
+ const src: string;
44
+ export default src;
45
+ }
46
+ declare module '*.svg' {
47
+ import * as React from 'react';
48
+ export const ReactComponent: React.FunctionComponent<
49
+ React.SVGProps<SVGSVGElement> & { title?: string }
50
+ >;
51
+
52
+ const src: string;
53
+ export default src;
54
+ }
55
+ declare module '*.ico' {
56
+ const src: string;
57
+ export default src;
58
+ }
59
+ declare module '*.webp' {
60
+ const src: string;
61
+ export default src;
62
+ }
63
+ declare module '*.avif' {
64
+ const src: string;
65
+ export default src;
66
+ }
67
+
68
+ // media
69
+ declare module '*.mp4' {
70
+ const src: string;
71
+ export default src;
72
+ }
73
+ declare module '*.webm' {
74
+ const src: string;
75
+ export default src;
76
+ }
77
+ declare module '*.ogg' {
78
+ const src: string;
79
+ export default src;
80
+ }
81
+ declare module '*.mp3' {
82
+ const src: string;
83
+ export default src;
84
+ }
85
+ declare module '*.wav' {
86
+ const src: string;
87
+ export default src;
88
+ }
89
+ declare module '*.flac' {
90
+ const src: string;
91
+ export default src;
92
+ }
93
+ declare module '*.aac' {
94
+ const src: string;
95
+ export default src;
96
+ }
97
+
98
+ // fonts
99
+ declare module '*.woff' {
100
+ const src: string;
101
+ export default src;
102
+ }
103
+ declare module '*.woff2' {
104
+ const src: string;
105
+ export default src;
106
+ }
107
+ declare module '*.eot' {
108
+ const src: string;
109
+ export default src;
110
+ }
111
+ declare module '*.ttf' {
112
+ const src: string;
113
+ export default src;
114
+ }
115
+ declare module '*.otf' {
116
+ const src: string;
117
+ export default src;
118
+ }
119
+
120
+ // other
121
+ declare module '*.wasm' {
122
+ const initWasm: (
123
+ options: WebAssembly.Imports,
124
+ ) => Promise<WebAssembly.Exports>;
125
+ export default initWasm;
126
+ }
127
+ declare module '*.webmanifest' {
128
+ const src: string;
129
+ export default src;
130
+ }
131
+ declare module '*.pdf' {
132
+ const src: string;
133
+ export default src;
134
+ }
135
+ declare module '*.txt' {
136
+ const src: string;
137
+ export default src;
138
+ }
web/src/hooks/fileManagerHooks.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IFileListRequestBody } from '@/interfaces/request/file-manager';
2
+ import { useCallback } from 'react';
3
+ import { useDispatch, useSelector } from 'umi';
4
+
5
+ export const useFetchFileList = () => {
6
+ const dispatch = useDispatch();
7
+
8
+ const fetchFileList = useCallback(
9
+ (payload: IFileListRequestBody) => {
10
+ return dispatch<any>({
11
+ type: 'fileManager/listFile',
12
+ payload,
13
+ });
14
+ },
15
+ [dispatch],
16
+ );
17
+
18
+ return fetchFileList;
19
+ };
20
+
21
+ export const useRemoveFile = () => {
22
+ const dispatch = useDispatch();
23
+
24
+ const removeFile = useCallback(
25
+ (fileIds: string[]) => {
26
+ return dispatch<any>({
27
+ type: 'fileManager/removeFile',
28
+ payload: { fileIds },
29
+ });
30
+ },
31
+ [dispatch],
32
+ );
33
+
34
+ return removeFile;
35
+ };
36
+
37
+ export const useRenameFile = () => {
38
+ const dispatch = useDispatch();
39
+
40
+ const renameFile = useCallback(
41
+ (fileId: string, name: string) => {
42
+ return dispatch<any>({
43
+ type: 'fileManager/renameFile',
44
+ payload: { fileId, name },
45
+ });
46
+ },
47
+ [dispatch],
48
+ );
49
+
50
+ return renameFile;
51
+ };
52
+
53
+ export const useFetchParentFolderList = () => {
54
+ const dispatch = useDispatch();
55
+
56
+ const fetchParentFolderList = useCallback(
57
+ (fileId: string) => {
58
+ return dispatch<any>({
59
+ type: 'fileManager/getAllParentFolder',
60
+ payload: { fileId },
61
+ });
62
+ },
63
+ [dispatch],
64
+ );
65
+
66
+ return fetchParentFolderList;
67
+ };
68
+
69
+ export const useSelectFileList = () => {
70
+ const fileList = useSelector((state) => state.fileManager.fileList);
71
+
72
+ return fileList;
73
+ };
74
+
75
+ export const useSelectParentFolderList = () => {
76
+ const parentFolderList = useSelector(
77
+ (state) => state.fileManager.parentFolderList,
78
+ );
79
+ return parentFolderList.toReversed();
80
+ };
web/src/interfaces/database/file-manager.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface IFile {
2
+ create_date: string;
3
+ create_time: number;
4
+ created_by: string;
5
+ id: string;
6
+ kb_ids: string[];
7
+ location: string;
8
+ name: string;
9
+ parent_id: string;
10
+ size: number;
11
+ tenant_id: string;
12
+ type: string;
13
+ update_date: string;
14
+ update_time: number;
15
+ }
16
+
17
+ export interface IFolder {
18
+ create_date: string;
19
+ create_time: number;
20
+ created_by: string;
21
+ id: string;
22
+ location: string;
23
+ name: string;
24
+ parent_id: string;
25
+ size: number;
26
+ tenant_id: string;
27
+ type: string;
28
+ update_date: string;
29
+ update_time: number;
30
+ }
web/src/interfaces/request/base.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ export interface IPaginationRequestBody {
2
+ keywords?: string;
3
+ page?: number;
4
+ page_size?: number; // name|create|doc_num|create_time|update_time,default:create_time
5
+ orderby?: string;
6
+ desc?: string;
7
+ }
web/src/interfaces/request/file-manager.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { IPaginationRequestBody } from './base';
2
+
3
+ export interface IFileListRequestBody extends IPaginationRequestBody {
4
+ parent_id?: string; // folder id
5
+ }
web/src/layouts/components/header/index.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { ReactComponent as StarIon } from '@/assets/svg/chat-star.svg';
 
2
  import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg';
3
  import { ReactComponent as Logo } from '@/assets/svg/logo.svg';
4
  import { useTranslate } from '@/hooks/commonHooks';
 
1
  import { ReactComponent as StarIon } from '@/assets/svg/chat-star.svg';
2
+ // import { ReactComponent as FileIcon } from '@/assets/svg/file-management.svg';
3
  import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg';
4
  import { ReactComponent as Logo } from '@/assets/svg/logo.svg';
5
  import { useTranslate } from '@/hooks/commonHooks';
web/src/pages/add-knowledge/components/knowledge-file/document-toolbar.tsx CHANGED
@@ -182,7 +182,14 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
182
  ),
183
  },
184
  ];
185
- }, [handleDelete, handleRunClick, handleCancelClick, t]);
 
 
 
 
 
 
 
186
 
187
  return (
188
  <div className={styles.filter}>
 
182
  ),
183
  },
184
  ];
185
+ }, [
186
+ handleDelete,
187
+ handleRunClick,
188
+ handleCancelClick,
189
+ t,
190
+ handleDisableClick,
191
+ handleEnableClick,
192
+ ]);
193
 
194
  return (
195
  <div className={styles.filter}>
web/src/pages/file-manager/action-cell/index.less ADDED
File without changes
web/src/pages/file-manager/action-cell/index.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks';
2
+ import { api_host } from '@/utils/api';
3
+ import { downloadFile } from '@/utils/fileUtil';
4
+ import {
5
+ DeleteOutlined,
6
+ DownloadOutlined,
7
+ EditOutlined,
8
+ ToolOutlined,
9
+ } from '@ant-design/icons';
10
+ import { Button, Space, Tooltip } from 'antd';
11
+
12
+ import { useRemoveFile } from '@/hooks/fileManagerHooks';
13
+ import { IFile } from '@/interfaces/database/file-manager';
14
+ import styles from './index.less';
15
+
16
+ interface IProps {
17
+ record: IFile;
18
+ setCurrentRecord: (record: any) => void;
19
+ showRenameModal: (record: IFile) => void;
20
+ }
21
+
22
+ const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => {
23
+ const documentId = record.id;
24
+ const beingUsed = false;
25
+ const { t } = useTranslate('knowledgeDetails');
26
+ const removeDocument = useRemoveFile();
27
+ const showDeleteConfirm = useShowDeleteConfirm();
28
+
29
+ const onRmDocument = () => {
30
+ if (!beingUsed) {
31
+ showDeleteConfirm({
32
+ onOk: () => {
33
+ return removeDocument([documentId]);
34
+ },
35
+ });
36
+ }
37
+ };
38
+
39
+ const onDownloadDocument = () => {
40
+ downloadFile({
41
+ url: `${api_host}/document/get/${documentId}`,
42
+ filename: record.name,
43
+ });
44
+ };
45
+
46
+ const setRecord = () => {
47
+ setCurrentRecord(record);
48
+ };
49
+
50
+ const onShowRenameModal = () => {
51
+ setRecord();
52
+ showRenameModal(record);
53
+ };
54
+
55
+ return (
56
+ <Space size={0}>
57
+ <Button type="text" className={styles.iconButton}>
58
+ <ToolOutlined size={20} />
59
+ </Button>
60
+
61
+ <Tooltip title={t('rename', { keyPrefix: 'common' })}>
62
+ <Button
63
+ type="text"
64
+ disabled={beingUsed}
65
+ onClick={onShowRenameModal}
66
+ className={styles.iconButton}
67
+ >
68
+ <EditOutlined size={20} />
69
+ </Button>
70
+ </Tooltip>
71
+ <Button
72
+ type="text"
73
+ disabled={beingUsed}
74
+ onClick={onRmDocument}
75
+ className={styles.iconButton}
76
+ >
77
+ <DeleteOutlined size={20} />
78
+ </Button>
79
+ <Button
80
+ type="text"
81
+ disabled={beingUsed}
82
+ onClick={onDownloadDocument}
83
+ className={styles.iconButton}
84
+ >
85
+ <DownloadOutlined size={20} />
86
+ </Button>
87
+ </Space>
88
+ );
89
+ };
90
+
91
+ export default ActionCell;
web/src/pages/file-manager/file-toolbar.tsx ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg';
2
+ import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks';
3
+ import {
4
+ DownOutlined,
5
+ FileOutlined,
6
+ FileTextOutlined,
7
+ PlusOutlined,
8
+ SearchOutlined,
9
+ } from '@ant-design/icons';
10
+ import {
11
+ Breadcrumb,
12
+ BreadcrumbProps,
13
+ Button,
14
+ Dropdown,
15
+ Flex,
16
+ Input,
17
+ MenuProps,
18
+ Space,
19
+ } from 'antd';
20
+ import { useCallback, useMemo } from 'react';
21
+ import {
22
+ useFetchDocumentListOnMount,
23
+ useGetPagination,
24
+ useHandleSearchChange,
25
+ useSelectBreadcrumbItems,
26
+ } from './hooks';
27
+
28
+ import { useRemoveFile } from '@/hooks/fileManagerHooks';
29
+ import { Link } from 'umi';
30
+ import styles from './index.less';
31
+
32
+ interface IProps {
33
+ selectedRowKeys: string[];
34
+ }
35
+
36
+ const itemRender: BreadcrumbProps['itemRender'] = (
37
+ currentRoute,
38
+ params,
39
+ items,
40
+ ) => {
41
+ const isLast = currentRoute?.path === items[items.length - 1]?.path;
42
+
43
+ return isLast ? (
44
+ <span>{currentRoute.title}</span>
45
+ ) : (
46
+ <Link to={`${currentRoute.path}`}>{currentRoute.title}</Link>
47
+ );
48
+ };
49
+
50
+ const FileToolbar = ({ selectedRowKeys }: IProps) => {
51
+ const { t } = useTranslate('knowledgeDetails');
52
+ const { fetchDocumentList } = useFetchDocumentListOnMount();
53
+ const { setPagination, searchString } = useGetPagination(fetchDocumentList);
54
+ const { handleInputChange } = useHandleSearchChange(setPagination);
55
+ const removeDocument = useRemoveFile();
56
+ const showDeleteConfirm = useShowDeleteConfirm();
57
+ const breadcrumbItems = useSelectBreadcrumbItems();
58
+
59
+ const actionItems: MenuProps['items'] = useMemo(() => {
60
+ return [
61
+ {
62
+ key: '1',
63
+ label: (
64
+ <div>
65
+ <Button type="link">
66
+ <Space>
67
+ <FileTextOutlined />
68
+ {t('localFiles')}
69
+ </Space>
70
+ </Button>
71
+ </div>
72
+ ),
73
+ },
74
+ { type: 'divider' },
75
+ {
76
+ key: '2',
77
+ label: (
78
+ <div>
79
+ <Button type="link">
80
+ <FileOutlined />
81
+ {t('emptyFiles')}
82
+ </Button>
83
+ </div>
84
+ ),
85
+ // disabled: true,
86
+ },
87
+ ];
88
+ }, [t]);
89
+
90
+ const handleDelete = useCallback(() => {
91
+ showDeleteConfirm({
92
+ onOk: () => {
93
+ return removeDocument(selectedRowKeys);
94
+ },
95
+ });
96
+ }, [removeDocument, showDeleteConfirm, selectedRowKeys]);
97
+
98
+ const disabled = selectedRowKeys.length === 0;
99
+
100
+ const items: MenuProps['items'] = useMemo(() => {
101
+ return [
102
+ {
103
+ key: '4',
104
+ onClick: handleDelete,
105
+ label: (
106
+ <Flex gap={10}>
107
+ <span className={styles.deleteIconWrapper}>
108
+ <DeleteIcon width={18} />
109
+ </span>
110
+ <b>{t('delete', { keyPrefix: 'common' })}</b>
111
+ </Flex>
112
+ ),
113
+ },
114
+ ];
115
+ }, [handleDelete, t]);
116
+
117
+ return (
118
+ <div className={styles.filter}>
119
+ <Breadcrumb items={breadcrumbItems} itemRender={itemRender} />
120
+ <Space>
121
+ <Dropdown
122
+ menu={{ items }}
123
+ placement="bottom"
124
+ arrow={false}
125
+ disabled={disabled}
126
+ >
127
+ <Button>
128
+ <Space>
129
+ <b> {t('bulk')}</b>
130
+ <DownOutlined />
131
+ </Space>
132
+ </Button>
133
+ </Dropdown>
134
+ <Input
135
+ placeholder={t('searchFiles')}
136
+ value={searchString}
137
+ style={{ width: 220 }}
138
+ allowClear
139
+ onChange={handleInputChange}
140
+ prefix={<SearchOutlined />}
141
+ />
142
+
143
+ <Dropdown menu={{ items: actionItems }} trigger={['click']}>
144
+ <Button type="primary" icon={<PlusOutlined />}>
145
+ {t('addFile')}
146
+ </Button>
147
+ </Dropdown>
148
+ </Space>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default FileToolbar;
web/src/pages/file-manager/hooks.ts ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
2
+ import {
3
+ useFetchFileList,
4
+ useFetchParentFolderList,
5
+ useRenameFile,
6
+ useSelectFileList,
7
+ useSelectParentFolderList,
8
+ } from '@/hooks/fileManagerHooks';
9
+ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
10
+ import { Pagination } from '@/interfaces/common';
11
+ import { IFile } from '@/interfaces/database/file-manager';
12
+ import { PaginationProps } from 'antd';
13
+ import { useCallback, useEffect, useMemo, useState } from 'react';
14
+ import { useDispatch, useNavigate, useSearchParams, useSelector } from 'umi';
15
+
16
+ export const useGetFolderId = () => {
17
+ const [searchParams] = useSearchParams();
18
+ const id = searchParams.get('folderId') as string;
19
+
20
+ return id;
21
+ };
22
+
23
+ export const useFetchDocumentListOnMount = () => {
24
+ const fetchDocumentList = useFetchFileList();
25
+ const fileList = useSelectFileList();
26
+ const id = useGetFolderId();
27
+
28
+ const dispatch = useDispatch();
29
+
30
+ useEffect(() => {
31
+ fetchDocumentList({ parent_id: id });
32
+ }, [dispatch, fetchDocumentList, id]);
33
+
34
+ return { fetchDocumentList, fileList };
35
+ };
36
+
37
+ export const useGetPagination = (
38
+ fetchDocumentList: (payload: IFile) => any,
39
+ ) => {
40
+ const dispatch = useDispatch();
41
+ const kFModel = useSelector((state: any) => state.kFModel);
42
+ const { t } = useTranslate('common');
43
+
44
+ const setPagination = useCallback(
45
+ (pageNumber = 1, pageSize?: number) => {
46
+ const pagination: Pagination = {
47
+ current: pageNumber,
48
+ } as Pagination;
49
+ if (pageSize) {
50
+ pagination.pageSize = pageSize;
51
+ }
52
+ dispatch({
53
+ type: 'kFModel/setPagination',
54
+ payload: pagination,
55
+ });
56
+ },
57
+ [dispatch],
58
+ );
59
+
60
+ const onPageChange: PaginationProps['onChange'] = useCallback(
61
+ (pageNumber: number, pageSize: number) => {
62
+ setPagination(pageNumber, pageSize);
63
+ fetchDocumentList();
64
+ },
65
+ [fetchDocumentList, setPagination],
66
+ );
67
+
68
+ const pagination: PaginationProps = useMemo(() => {
69
+ return {
70
+ showQuickJumper: true,
71
+ total: kFModel.total,
72
+ showSizeChanger: true,
73
+ current: kFModel.pagination.current,
74
+ pageSize: kFModel.pagination.pageSize,
75
+ pageSizeOptions: [1, 2, 10, 20, 50, 100],
76
+ onChange: onPageChange,
77
+ showTotal: (total) => `${t('total')} ${total}`,
78
+ };
79
+ }, [kFModel, onPageChange, t]);
80
+
81
+ return {
82
+ pagination,
83
+ setPagination,
84
+ total: kFModel.total,
85
+ searchString: kFModel.searchString,
86
+ };
87
+ };
88
+
89
+ export const useHandleSearchChange = (setPagination: () => void) => {
90
+ const dispatch = useDispatch();
91
+
92
+ const throttledGetDocumentList = useCallback(() => {
93
+ dispatch({
94
+ type: 'kFModel/throttledGetDocumentList',
95
+ });
96
+ }, [dispatch]);
97
+
98
+ const handleInputChange = useCallback(
99
+ (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
100
+ const value = e.target.value;
101
+ dispatch({ type: 'kFModel/setSearchString', payload: value });
102
+ setPagination();
103
+ throttledGetDocumentList();
104
+ },
105
+ [setPagination, throttledGetDocumentList, dispatch],
106
+ );
107
+
108
+ return { handleInputChange };
109
+ };
110
+
111
+ export const useGetRowSelection = () => {
112
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
113
+
114
+ const rowSelection = {
115
+ selectedRowKeys,
116
+ onChange: (newSelectedRowKeys: React.Key[]) => {
117
+ setSelectedRowKeys(newSelectedRowKeys);
118
+ },
119
+ };
120
+
121
+ return rowSelection;
122
+ };
123
+
124
+ export const useNavigateToOtherFolder = () => {
125
+ const navigate = useNavigate();
126
+ const navigateToOtherFolder = useCallback(
127
+ (folderId: string) => {
128
+ navigate(`/file?folderId=${folderId}`);
129
+ },
130
+ [navigate],
131
+ );
132
+
133
+ return navigateToOtherFolder;
134
+ };
135
+
136
+ export const useRenameCurrentFile = () => {
137
+ const [file, setFile] = useState<IFile>({} as IFile);
138
+ const {
139
+ visible: fileRenameVisible,
140
+ hideModal: hideFileRenameModal,
141
+ showModal: showFileRenameModal,
142
+ } = useSetModalState();
143
+ const renameFile = useRenameFile();
144
+
145
+ const onFileRenameOk = useCallback(
146
+ async (name: string) => {
147
+ const ret = await renameFile(file.id, name);
148
+
149
+ if (ret === 0) {
150
+ hideFileRenameModal();
151
+ }
152
+ },
153
+ [renameFile, file, hideFileRenameModal],
154
+ );
155
+
156
+ const loading = useOneNamespaceEffectsLoading('fileManager', ['renameFile']);
157
+
158
+ const handleShowFileRenameModal = useCallback(
159
+ async (record: IFile) => {
160
+ setFile(record);
161
+ showFileRenameModal();
162
+ },
163
+ [showFileRenameModal],
164
+ );
165
+
166
+ return {
167
+ fileRenameLoading: loading,
168
+ initialFileName: file.name,
169
+ onFileRenameOk,
170
+ fileRenameVisible,
171
+ hideFileRenameModal,
172
+ showFileRenameModal: handleShowFileRenameModal,
173
+ };
174
+ };
175
+
176
+ export const useSelectBreadcrumbItems = () => {
177
+ const parentFolderList = useSelectParentFolderList();
178
+ const id = useGetFolderId();
179
+ const fetchParentFolderList = useFetchParentFolderList();
180
+
181
+ useEffect(() => {
182
+ if (id) {
183
+ fetchParentFolderList(id);
184
+ }
185
+ }, [id, fetchParentFolderList]);
186
+
187
+ return parentFolderList.length === 1
188
+ ? []
189
+ : parentFolderList.map((x) => ({
190
+ title: x.name === '/' ? 'root' : x.name,
191
+ path: `/file?folderId=${x.id}`,
192
+ }));
193
+ };
web/src/pages/file-manager/index.less ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .fileManagerWrapper {
2
+ flex-basis: 100%;
3
+ padding: 32px;
4
+ }
5
+
6
+ .filter {
7
+ height: 32px;
8
+ display: flex;
9
+ margin: 10px 0;
10
+ justify-content: space-between;
11
+ padding: 24px 0;
12
+ align-items: center;
13
+ }
14
+
15
+ .deleteIconWrapper {
16
+ width: 22px;
17
+ text-align: center;
18
+ }
web/src/pages/file-manager/index.tsx ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useSelectFileList } from '@/hooks/fileManagerHooks';
2
+ import { IFile } from '@/interfaces/database/file-manager';
3
+ import { formatDate } from '@/utils/date';
4
+ import { Button, Table } from 'antd';
5
+ import { ColumnsType } from 'antd/es/table';
6
+ import ActionCell from './action-cell';
7
+ import FileToolbar from './file-toolbar';
8
+ import {
9
+ useGetRowSelection,
10
+ useNavigateToOtherFolder,
11
+ useRenameCurrentFile,
12
+ } from './hooks';
13
+
14
+ import RenameModal from '@/components/rename-modal';
15
+ import styles from './index.less';
16
+
17
+ const FileManager = () => {
18
+ const fileList = useSelectFileList();
19
+ const rowSelection = useGetRowSelection();
20
+ const navigateToOtherFolder = useNavigateToOtherFolder();
21
+ const {
22
+ fileRenameVisible,
23
+ fileRenameLoading,
24
+ hideFileRenameModal,
25
+ showFileRenameModal,
26
+ initialFileName,
27
+ onFileRenameOk,
28
+ } = useRenameCurrentFile();
29
+
30
+ const columns: ColumnsType<IFile> = [
31
+ {
32
+ title: 'Name',
33
+ dataIndex: 'name',
34
+ key: 'name',
35
+ render(value, record) {
36
+ return record.type === 'folder' ? (
37
+ <Button
38
+ type={'link'}
39
+ onClick={() => navigateToOtherFolder(record.id)}
40
+ >
41
+ {value}
42
+ </Button>
43
+ ) : (
44
+ value
45
+ );
46
+ },
47
+ },
48
+ {
49
+ title: 'Upload Date',
50
+ dataIndex: 'create_date',
51
+ key: 'create_date',
52
+ render(text) {
53
+ return formatDate(text);
54
+ },
55
+ },
56
+ {
57
+ title: 'Location',
58
+ dataIndex: 'location',
59
+ key: 'location',
60
+ },
61
+ {
62
+ title: 'Action',
63
+ dataIndex: 'action',
64
+ key: 'action',
65
+ render: (text, record) => (
66
+ <ActionCell
67
+ record={record}
68
+ setCurrentRecord={(record: any) => {
69
+ console.info(record);
70
+ }}
71
+ showRenameModal={showFileRenameModal}
72
+ ></ActionCell>
73
+ ),
74
+ },
75
+ ];
76
+
77
+ return (
78
+ <section className={styles.fileManagerWrapper}>
79
+ <FileToolbar
80
+ selectedRowKeys={rowSelection.selectedRowKeys as string[]}
81
+ ></FileToolbar>
82
+ <Table
83
+ dataSource={fileList}
84
+ columns={columns}
85
+ rowKey={'id'}
86
+ rowSelection={rowSelection}
87
+ />
88
+ <RenameModal
89
+ visible={fileRenameVisible}
90
+ hideModal={hideFileRenameModal}
91
+ onOk={onFileRenameOk}
92
+ initialName={initialFileName}
93
+ loading={fileRenameLoading}
94
+ ></RenameModal>
95
+ </section>
96
+ );
97
+ };
98
+
99
+ export default FileManager;
web/src/pages/file-manager/model.ts ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IFile, IFolder } from '@/interfaces/database/file-manager';
2
+ import fileManagerService from '@/services/fileManagerService';
3
+ import { DvaModel } from 'umi';
4
+
5
+ export interface FileManagerModelState {
6
+ fileList: IFile[];
7
+ parentFolderList: IFolder[];
8
+ }
9
+
10
+ const model: DvaModel<FileManagerModelState> = {
11
+ namespace: 'fileManager',
12
+ state: { fileList: [], parentFolderList: [] },
13
+ reducers: {
14
+ setFileList(state, { payload }) {
15
+ return { ...state, fileList: payload };
16
+ },
17
+ setParentFolderList(state, { payload }) {
18
+ return { ...state, parentFolderList: payload };
19
+ },
20
+ },
21
+ effects: {
22
+ *removeFile({ payload = {} }, { call, put }) {
23
+ const { data } = yield call(fileManagerService.removeFile, payload);
24
+ const { retcode } = data;
25
+ if (retcode === 0) {
26
+ yield put({
27
+ type: 'listFile',
28
+ payload: data.data?.files ?? [],
29
+ });
30
+ }
31
+ },
32
+ *listFile({ payload = {} }, { call, put }) {
33
+ const { data } = yield call(fileManagerService.listFile, payload);
34
+ const { retcode, data: res } = data;
35
+
36
+ if (retcode === 0 && Array.isArray(res.files)) {
37
+ yield put({
38
+ type: 'setFileList',
39
+ payload: res.files,
40
+ });
41
+ }
42
+ },
43
+ *renameFile({ payload = {} }, { call, put }) {
44
+ const { data } = yield call(fileManagerService.renameFile, payload);
45
+ if (data.retcode === 0) {
46
+ yield put({ type: 'listFile' });
47
+ }
48
+ return data.retcode;
49
+ },
50
+ *getAllParentFolder({ payload = {} }, { call, put }) {
51
+ const { data } = yield call(
52
+ fileManagerService.getAllParentFolder,
53
+ payload,
54
+ );
55
+ if (data.retcode === 0) {
56
+ yield put({
57
+ type: 'setParentFolderList',
58
+ payload: data.data?.parent_folders ?? [],
59
+ });
60
+ }
61
+ return data.retcode;
62
+ },
63
+ },
64
+ };
65
+ export default model;
web/src/pages/file/index.tsx DELETED
@@ -1,50 +0,0 @@
1
- import { UploadOutlined } from '@ant-design/icons';
2
- import { Button, Upload } from 'antd';
3
- import React, { useEffect, useState } from 'react';
4
-
5
- const File: React.FC = () => {
6
- const [fileList, setFileList] = useState([
7
- {
8
- uid: '0',
9
- name: 'xxx.png',
10
- status: 'uploading',
11
- percent: 10,
12
- },
13
- ]);
14
- const obj = {
15
- uid: '-1',
16
- name: 'yyy.png',
17
- status: 'done',
18
- url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
19
- thumbUrl:
20
- 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
21
- };
22
- useEffect(() => {
23
- const timer = setInterval(() => {
24
- setFileList((fileList: any) => {
25
- const percent = fileList[0]?.percent;
26
- if (percent + 10 >= 100) {
27
- clearInterval(timer);
28
- return [obj];
29
- }
30
- const list = [{ ...fileList[0], percent: percent + 10 }];
31
- console.log(list);
32
- return list;
33
- });
34
- }, 300);
35
- }, []);
36
- return (
37
- <>
38
- <Upload
39
- action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
40
- listType="picture"
41
- fileList={[...fileList]}
42
- multiple
43
- >
44
- <Button icon={<UploadOutlined />}>Upload</Button>
45
- </Upload>
46
- </>
47
- );
48
- };
49
-
50
- export default File;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/pages/login/index.tsx CHANGED
@@ -167,20 +167,22 @@ const Login = () => {
167
  Sign in with Google
168
  </div>
169
  </Button> */}
170
- <Button
171
- block
172
- size="large"
173
- onClick={toGoogle}
174
- style={{ marginTop: 15 }}
175
- >
176
- <div>
177
- <Icon
178
- icon="local:github"
179
- style={{ verticalAlign: 'middle', marginRight: 5 }}
180
- />
181
- Sign in with Github
182
- </div>
183
- </Button>
 
 
184
  </>
185
  )}
186
  </Form>
 
167
  Sign in with Google
168
  </div>
169
  </Button> */}
170
+ {location.host === 'demo.ragflow.io' && (
171
+ <Button
172
+ block
173
+ size="large"
174
+ onClick={toGoogle}
175
+ style={{ marginTop: 15 }}
176
+ >
177
+ <div>
178
+ <Icon
179
+ icon="local:github"
180
+ style={{ verticalAlign: 'middle', marginRight: 5 }}
181
+ />
182
+ Sign in with Github
183
+ </div>
184
+ </Button>
185
+ )}
186
  </>
187
  )}
188
  </Form>
web/src/routes.ts CHANGED
@@ -82,7 +82,7 @@ const routes = [
82
  },
83
  {
84
  path: '/file',
85
- component: '@/pages/file',
86
  },
87
  ],
88
  },
 
82
  },
83
  {
84
  path: '/file',
85
+ component: '@/pages/file-manager',
86
  },
87
  ],
88
  },
web/src/services/fileManagerService.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import api from '@/utils/api';
2
+ import registerServer from '@/utils/registerServer';
3
+ import request from '@/utils/request';
4
+
5
+ const { listFile, removeFile, uploadFile, renameFile, getAllParentFolder } =
6
+ api;
7
+
8
+ const methods = {
9
+ listFile: {
10
+ url: listFile,
11
+ method: 'get',
12
+ },
13
+ removeFile: {
14
+ url: removeFile,
15
+ method: 'post',
16
+ },
17
+ uploadFile: {
18
+ url: uploadFile,
19
+ method: 'post',
20
+ },
21
+ renameFile: {
22
+ url: renameFile,
23
+ method: 'post',
24
+ },
25
+ getAllParentFolder: {
26
+ url: getAllParentFolder,
27
+ method: 'get',
28
+ },
29
+ } as const;
30
+
31
+ const fileManagerService = registerServer<keyof typeof methods>(
32
+ methods,
33
+ request,
34
+ );
35
+
36
+ export default fileManagerService;
web/src/utils/api.ts CHANGED
@@ -66,4 +66,11 @@ export default {
66
  createExternalConversation: `${api_host}/api/new_conversation`,
67
  getExternalConversation: `${api_host}/api/conversation`,
68
  completeExternalConversation: `${api_host}/api/completion`,
 
 
 
 
 
 
 
69
  };
 
66
  createExternalConversation: `${api_host}/api/new_conversation`,
67
  getExternalConversation: `${api_host}/api/conversation`,
68
  completeExternalConversation: `${api_host}/api/completion`,
69
+
70
+ // file manager
71
+ listFile: `${api_host}/file/list`,
72
+ uploadFile: `${api_host}/file/upload`,
73
+ removeFile: `${api_host}/file/rm`,
74
+ renameFile: `${api_host}/file/rename`,
75
+ getAllParentFolder: `${api_host}/file/all_parent_folder`,
76
  };
web/src/utils/commonUtil.ts CHANGED
@@ -5,11 +5,18 @@ export const isFormData = (data: unknown): data is FormData => {
5
  return data instanceof FormData;
6
  };
7
 
 
 
 
 
 
 
8
  export const convertTheKeysOfTheObjectToSnake = (data: unknown) => {
9
  if (isObject(data) && !isFormData(data)) {
10
  return Object.keys(data).reduce<Record<string, any>>((pre, cur) => {
11
  const value = (data as Record<string, any>)[cur];
12
- pre[isFormData(value) ? cur : snakeCase(cur)] = value;
 
13
  return pre;
14
  }, {});
15
  }
 
5
  return data instanceof FormData;
6
  };
7
 
8
+ const excludedFields = ['img2txt_id'];
9
+
10
+ const isExcludedField = (key: string) => {
11
+ return excludedFields.includes(key);
12
+ };
13
+
14
  export const convertTheKeysOfTheObjectToSnake = (data: unknown) => {
15
  if (isObject(data) && !isFormData(data)) {
16
  return Object.keys(data).reduce<Record<string, any>>((pre, cur) => {
17
  const value = (data as Record<string, any>)[cur];
18
+ pre[isFormData(value) || isExcludedField(cur) ? cur : snakeCase(cur)] =
19
+ value;
20
  return pre;
21
  }, {});
22
  }
web/typings.d.ts CHANGED
@@ -1,8 +1,39 @@
1
- import 'umi/typings';
 
 
 
 
 
 
 
 
 
 
2
  declare module 'lodash';
3
 
4
- // declare type Nullable<T> = T | null; invalid
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  declare global {
7
  type Nullable<T> = T | null;
8
  }
 
 
 
 
 
1
+ import { ChunkModelState } from '@/pages/add-knowledge/components/knowledge-chunk/model';
2
+ import { KFModelState } from '@/pages/add-knowledge/components/knowledge-file/model';
3
+ import { KSModelState } from '@/pages/add-knowledge/components/knowledge-setting/model';
4
+ import { TestingModelState } from '@/pages/add-knowledge/components/knowledge-testing/model';
5
+ import { kAModelState } from '@/pages/add-knowledge/model';
6
+ import { ChatModelState } from '@/pages/chat/model';
7
+ import { FileManagerModelState } from '@/pages/file-manager/model';
8
+ import { KnowledgeModelState } from '@/pages/knowledge/model';
9
+ import { LoginModelState } from '@/pages/login/model';
10
+ import { SettingModelState } from '@/pages/user-setting/model';
11
+
12
  declare module 'lodash';
13
 
14
+ function useSelector<TState = RootState, TSelected = unknown>(
15
+ selector: (state: TState) => TSelected,
16
+ equalityFn?: (left: TSelected, right: TSelected) => boolean,
17
+ ): TSelected;
18
+
19
+ export interface RootState {
20
+ // loading: Loading;
21
+ fileManager: FileManagerModelState;
22
+ chatModel: ChatModelState;
23
+ loginModel: LoginModelState;
24
+ knowledgeModel: KnowledgeModelState;
25
+ settingModel: SettingModelState;
26
+ kFModel: KFModelState;
27
+ kAModel: kAModelState;
28
+ chunkModel: ChunkModelState;
29
+ kSModel: KSModelState;
30
+ testingModel: TestingModelState;
31
+ }
32
 
33
  declare global {
34
  type Nullable<T> = T | null;
35
  }
36
+
37
+ declare module 'umi' {
38
+ export { useSelector };
39
+ }