balibabu commited on
Commit
7c6cf75
·
1 Parent(s): 21cd893

feat: translate FileManager #345 (#558)

Browse files

### What problem does this PR solve?
#345
feat: translate FileManager
feat: batch delete files from the file table in the knowledge base

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

web/.umirc.ts CHANGED
@@ -27,7 +27,7 @@ export default defineConfig({
27
  devtool: 'source-map',
28
  proxy: {
29
  '/v1': {
30
- target: 'http://192.168.200.233:9380/',
31
  changeOrigin: true,
32
  // pathRewrite: { '^/v1': '/v1' },
33
  },
 
27
  devtool: 'source-map',
28
  proxy: {
29
  '/v1': {
30
+ target: 'http://123.60.95.134:9380/',
31
  changeOrigin: true,
32
  // pathRewrite: { '^/v1': '/v1' },
33
  },
web/src/hooks/documentHooks.ts CHANGED
@@ -160,12 +160,12 @@ export const useRemoveDocument = () => {
160
  const { knowledgeId } = useGetKnowledgeSearchParams();
161
 
162
  const removeDocument = useCallback(
163
- (documentId: string) => {
164
  try {
165
  return dispatch<any>({
166
  type: 'kFModel/document_rm',
167
  payload: {
168
- doc_id: documentId,
169
  kb_id: knowledgeId,
170
  },
171
  });
 
160
  const { knowledgeId } = useGetKnowledgeSearchParams();
161
 
162
  const removeDocument = useCallback(
163
+ (documentIds: string[]) => {
164
  try {
165
  return dispatch<any>({
166
  type: 'kFModel/document_rm',
167
  payload: {
168
+ doc_id: documentIds,
169
  kb_id: knowledgeId,
170
  },
171
  });
web/src/interfaces/common.ts CHANGED
@@ -14,5 +14,5 @@ export interface IModalProps<T> {
14
  hideModal(): void;
15
  visible: boolean;
16
  loading?: boolean;
17
- onOk?(payload?: T): Promise<void> | void;
18
  }
 
14
  hideModal(): void;
15
  visible: boolean;
16
  loading?: boolean;
17
+ onOk?(payload?: T): Promise<any> | void;
18
  }
web/src/layouts/components/header/index.tsx CHANGED
@@ -1,5 +1,5 @@
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';
@@ -25,7 +25,7 @@ const RagHeader = () => {
25
  () => [
26
  { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon },
27
  { path: '/chat', name: t('chat'), icon: StarIon },
28
- // { path: '/file', name: 'File Management', icon: FileIcon },
29
  ],
30
  [t],
31
  );
 
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';
 
25
  () => [
26
  { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon },
27
  { path: '/chat', name: t('chat'), icon: StarIon },
28
+ { path: '/file', name: t('fileManager'), icon: FileIcon },
29
  ],
30
  [t],
31
  );
web/src/locales/en.ts CHANGED
@@ -22,6 +22,7 @@ export default {
22
  languagePlaceholder: 'select your language',
23
  copy: 'Copy',
24
  copied: 'Copied',
 
25
  },
26
  login: {
27
  login: 'Sign in',
@@ -52,6 +53,7 @@ export default {
52
  home: 'Home',
53
  setting: '用户设置',
54
  logout: '登出',
 
55
  },
56
  knowledgeList: {
57
  welcome: 'Welcome back',
@@ -459,6 +461,7 @@ export default {
459
  renamed: 'Renamed',
460
  operated: 'Operated',
461
  updated: 'Updated',
 
462
  200: 'The server successfully returns the requested data.',
463
  201: 'Create or modify data successfully.',
464
  202: 'A request has been queued in the background (asynchronous task).',
@@ -480,6 +483,24 @@ export default {
480
  networkAnomaly: 'network anomaly',
481
  hint: 'hint',
482
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  footer: {
484
  profile: 'All rights reserved @ React',
485
  },
 
22
  languagePlaceholder: 'select your language',
23
  copy: 'Copy',
24
  copied: 'Copied',
25
+ comingSoon: 'Coming Soon',
26
  },
27
  login: {
28
  login: 'Sign in',
 
53
  home: 'Home',
54
  setting: '用户设置',
55
  logout: '登出',
56
+ fileManager: 'File Management',
57
  },
58
  knowledgeList: {
59
  welcome: 'Welcome back',
 
461
  renamed: 'Renamed',
462
  operated: 'Operated',
463
  updated: 'Updated',
464
+ uploaded: 'Uploaded',
465
  200: 'The server successfully returns the requested data.',
466
  201: 'Create or modify data successfully.',
467
  202: 'A request has been queued in the background (asynchronous task).',
 
483
  networkAnomaly: 'network anomaly',
484
  hint: 'hint',
485
  },
486
+ fileManager: {
487
+ name: 'Name',
488
+ uploadDate: 'Upload Date',
489
+ knowledgeBase: 'Knowledge Base',
490
+ size: 'Size',
491
+ action: 'Action',
492
+ addToKnowledge: 'Add to Knowledge Base',
493
+ pleaseSelect: 'Please select',
494
+ newFolder: 'New Folder',
495
+ file: 'File',
496
+ uploadFile: 'Upload File',
497
+ directory: 'Directory',
498
+ uploadTitle: 'Click or drag file to this area to upload',
499
+ uploadDescription:
500
+ 'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.',
501
+ local: 'Local uploads',
502
+ s3: 'S3 uploads',
503
+ },
504
  footer: {
505
  profile: 'All rights reserved @ React',
506
  },
web/src/locales/zh-traditional.ts CHANGED
@@ -22,6 +22,7 @@ export default {
22
  languagePlaceholder: '請選擇語言',
23
  copy: '複製',
24
  copied: '複製成功',
 
25
  },
26
  login: {
27
  login: '登入',
@@ -52,6 +53,7 @@ export default {
52
  home: '首頁',
53
  setting: '用戶設置',
54
  logout: '登出',
 
55
  },
56
  knowledgeList: {
57
  welcome: '歡迎回來',
@@ -218,7 +220,7 @@ export default {
218
  您只需與<i>'ragflow'</i>交談即可列出所有符合資格的候選人。
219
  </p>
220
  `,
221
- table: `支持<p><b>excel</b>和<b>csv/txt</b>格式文件。</p><p>以下是一些提示: <ul> <li>对于Csv或Txt文件,列之间的分隔符为 <em><b>tab</b></em>。</li> <li>第一行必须是列标题。</li> <li>列标题必须是有意义的术语,以便我们的法学硕士能够理解。列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好使用方括号枚举值,例如 <i>“性別/性別(男性,女性)”</i>.<p>以下是标题的一些示例:<ol> <li>供应商/供货商<b>'tab'</b>顏色(黃色、紅色、棕色)<b>'tab'</b>性別(男、女)<b>'tab'</B>尺码(m、l、xl、xxl)</li> <li>姓名/名字<b>'tab'</b>電話/手機/微信<b>'tab'</b>最高学历(高中,职高,硕士,本科,博士,初中,中技,中专,专科,专升本,mpa,mba,emba)</li> </ol> </p> </li> <li>表中的每一行都将被视为一个块。</li> </ul>`,
222
  picture: `
223
  <p>支持圖像文件。視頻即將推出。</p><p>
224
  如果圖片中有文字,則應用 OCR 提取文字作為其文字描述。
@@ -424,6 +426,7 @@ export default {
424
  renamed: '重命名成功',
425
  operated: '操作成功',
426
  updated: '更新成功',
 
427
  200: '服務器成功返回請求的數據。',
428
  201: '新建或修改數據成功。',
429
  202: '一個請求已經進入後台排隊(異步任務)。',
@@ -444,6 +447,23 @@ export default {
444
  networkAnomaly: '網絡異常',
445
  hint: '提示',
446
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  footer: {
448
  profile: '“保留所有權利 @ react”',
449
  },
 
22
  languagePlaceholder: '請選擇語言',
23
  copy: '複製',
24
  copied: '複製成功',
25
+ comingSoon: '即將推出',
26
  },
27
  login: {
28
  login: '登入',
 
53
  home: '首頁',
54
  setting: '用戶設置',
55
  logout: '登出',
56
+ fileManager: '文件管理',
57
  },
58
  knowledgeList: {
59
  welcome: '歡迎回來',
 
220
  您只需與<i>'ragflow'</i>交談即可列出所有符合資格的候選人。
221
  </p>
222
  `,
223
+ table: `支持<p><b>excel</b>和<b>csv/txt</b>格式文件。</p><p>以下是一些提示: <ul> <li>对于Csv或Txt文件,列之间的分隔符为 <em><b>tab</b></em>。</li> <li>第一行必须是列标题。</li> <li>列标题必须是有意义的术语,以便我们的大語言模型能够理解。列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好使用方括号枚举值,例如 <i>“性別/性別(男性,女性)”</i>.<p>以下是标题的一些示例:<ol> <li>供应商/供货商<b>'tab'</b>顏色(黃色、紅色、棕色)<b>'tab'</b>性別(男、女)<b>'tab'</B>尺码(m、l、xl、xxl)</li> <li>姓名/名字<b>'tab'</b>電話/手機/微信<b>'tab'</b>最高学历(高中,职高,硕士,本科,博士,初中,中技,中专,专科,专升本,mpa,mba,emba)</li> </ol> </p> </li> <li>表中的每一行都将被视为一个块。</li> </ul>`,
224
  picture: `
225
  <p>支持圖像文件。視頻即將推出。</p><p>
226
  如果圖片中有文字,則應用 OCR 提取文字作為其文字描述。
 
426
  renamed: '重命名成功',
427
  operated: '操作成功',
428
  updated: '更新成功',
429
+ uploaded: '上傳成功',
430
  200: '服務器成功返回請求的數據。',
431
  201: '新建或修改數據成功。',
432
  202: '一個請求已經進入後台排隊(異步任務)。',
 
447
  networkAnomaly: '網絡異常',
448
  hint: '提示',
449
  },
450
+ fileManager: {
451
+ name: '名稱',
452
+ uploadDate: '上傳日期',
453
+ knowledgeBase: '知識庫',
454
+ size: '大小',
455
+ action: '操作',
456
+ addToKnowledge: '添加到知識庫',
457
+ pleaseSelect: '請選擇',
458
+ newFolder: '新建文件夾',
459
+ uploadFile: '上傳文件',
460
+ uploadTitle: '點擊或拖拽文件至此區域即可上傳',
461
+ uploadDescription: '支持單次或批量上傳。嚴禁上傳公司數據或其他違禁文件。',
462
+ file: '文件',
463
+ directory: '文件夾',
464
+ local: '本地上傳',
465
+ s3: 'S3 上傳',
466
+ },
467
  footer: {
468
  profile: '“保留所有權利 @ react”',
469
  },
web/src/locales/zh.ts CHANGED
@@ -22,6 +22,7 @@ export default {
22
  languagePlaceholder: '请选择语言',
23
  copy: '复制',
24
  copied: '复制成功',
 
25
  },
26
  login: {
27
  login: '登录',
@@ -52,6 +53,7 @@ export default {
52
  home: '首页',
53
  setting: '用户设置',
54
  logout: '登出',
 
55
  },
56
  knowledgeList: {
57
  welcome: '欢迎回来',
@@ -225,7 +227,7 @@ export default {
225
  <ul>
226
  <li>对于 csv 或 txt 文件,列之间的分隔符为 <em><b>TAB</b></em>。</li>
227
  <li>第一行必须是列标题。</li>
228
- <li>列标题必须是有意义的术语,以便我们的法学硕士能够理解。
229
  列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好
230
  使用方括号枚举值,例如 <i>'gender/sex(male,female)'</i>.<p>
231
  以下是标题的一些示例:<ol>
@@ -298,7 +300,7 @@ export default {
298
  systemTip:
299
  '当LLM回答问题时,你需要LLM遵循的说明,比如角色设计、答案长度和答案语言等。',
300
  topN: 'Top N',
301
- topNTip: `并非所有相似度得分高于“相似度阈值”的块都会被提供给法学硕士。 LLM 只能看到这些“Top N”块。`,
302
  variable: '变量',
303
  variableTip: `如果您使用对话 API,变量可能会帮助您使用不同的策略与客户聊天。
304
  这些变量用于填写提示中的“系统”部分,以便给LLM一个提示。
@@ -315,7 +317,7 @@ export default {
315
  improvise: '即兴创作',
316
  precise: '精确',
317
  balance: '平衡',
318
- freedomTip: `“精确”意味着法学硕士会保守并谨慎地回答你的问题。 “即兴发挥”意味着你希望法学硕士能够自由地畅所欲言。 “平衡”是谨慎与自由之间的平衡。`,
319
  temperature: '温度',
320
  temperatureMessage: '温度是必填项',
321
  temperatureTip:
@@ -441,6 +443,7 @@ export default {
441
  renamed: '重命名成功',
442
  operated: '操作成功',
443
  updated: '更新成功',
 
444
  200: '服务器成功返回请求的数据。',
445
  201: '新建或修改数据成功。',
446
  202: '一个请求已经进入后台排队(异步任务)。',
@@ -461,6 +464,24 @@ export default {
461
  networkAnomaly: '网络异常',
462
  hint: '提示',
463
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  footer: {
465
  profile: 'All rights reserved @ React',
466
  },
 
22
  languagePlaceholder: '请选择语言',
23
  copy: '复制',
24
  copied: '复制成功',
25
+ comingSoon: '即将推出',
26
  },
27
  login: {
28
  login: '登录',
 
53
  home: '首页',
54
  setting: '用户设置',
55
  logout: '登出',
56
+ fileManager: '文件管理',
57
  },
58
  knowledgeList: {
59
  welcome: '欢迎回来',
 
227
  <ul>
228
  <li>对于 csv 或 txt 文件,列之间的分隔符为 <em><b>TAB</b></em>。</li>
229
  <li>第一行必须是列标题。</li>
230
+ <li>列标题必须是有意义的术语,以便我们的大语言模型能够理解。
231
  列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好
232
  使用方括号枚举值,例如 <i>'gender/sex(male,female)'</i>.<p>
233
  以下是标题的一些示例:<ol>
 
300
  systemTip:
301
  '当LLM回答问题时,你需要LLM遵循的说明,比如角色设计、答案长度和答案语言等。',
302
  topN: 'Top N',
303
+ topNTip: `并非所有相似度得分高于“相似度阈值”的块都会被提供给大语言模型。 LLM 只能看到这些“Top N”块。`,
304
  variable: '变量',
305
  variableTip: `如果您使用对话 API,变量可能会帮助您使用不同的策略与客户聊天。
306
  这些变量用于填写提示中的“系统”部分,以便给LLM一个提示。
 
317
  improvise: '即兴创作',
318
  precise: '精确',
319
  balance: '平衡',
320
+ freedomTip: `“精确”意味着大语言模型会保守并谨慎地回答你的问题。 “即兴发挥”意味着你希望大语言模型能够自由地畅所欲言。 “平衡”是谨慎与自由之间的平衡。`,
321
  temperature: '温度',
322
  temperatureMessage: '温度是必填项',
323
  temperatureTip:
 
443
  renamed: '重命名成功',
444
  operated: '操作成功',
445
  updated: '更新成功',
446
+ uploaded: '上传成功',
447
  200: '服务器成功返回请求的数据。',
448
  201: '新建或修改数据成功。',
449
  202: '一个请求已经进入后台排队(异步任务)。',
 
464
  networkAnomaly: '网络异常',
465
  hint: '提示',
466
  },
467
+ fileManager: {
468
+ name: '名称',
469
+ uploadDate: '上传日期',
470
+ knowledgeBase: '知识库',
471
+ size: '大小',
472
+ action: '操作',
473
+ addToKnowledge: '添加到知识库',
474
+ pleaseSelect: '请选择',
475
+ newFolder: '新建文件夹',
476
+ uploadFile: '上传文件',
477
+ uploadTitle: '点击或拖拽文件至此区域即可上传',
478
+ uploadDescription:
479
+ '支持单次或批量上传。 严禁上传公司数据或其他违禁文件。',
480
+ file: '文件',
481
+ directory: '文件夹',
482
+ local: '本地上传',
483
+ s3: 'S3 上传',
484
+ },
485
  footer: {
486
  profile: 'All rights reserved @ React',
487
  },
web/src/pages/add-knowledge/components/knowledge-file/document-toolbar.tsx CHANGED
@@ -80,9 +80,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
80
  const handleDelete = useCallback(() => {
81
  showDeleteConfirm({
82
  onOk: () => {
83
- selectedRowKeys.forEach((id) => {
84
- removeDocument(id);
85
- });
86
  },
87
  });
88
  }, [removeDocument, showDeleteConfirm, selectedRowKeys]);
 
80
  const handleDelete = useCallback(() => {
81
  showDeleteConfirm({
82
  onOk: () => {
83
+ removeDocument(selectedRowKeys);
 
 
84
  },
85
  });
86
  }, [removeDocument, showDeleteConfirm, selectedRowKeys]);
web/src/pages/add-knowledge/components/knowledge-file/parsing-action-cell/index.tsx CHANGED
@@ -35,7 +35,7 @@ const ParsingActionCell = ({
35
 
36
  const onRmDocument = () => {
37
  if (!isRunning) {
38
- showDeleteConfirm({ onOk: () => removeDocument(documentId) });
39
  }
40
  };
41
 
 
35
 
36
  const onRmDocument = () => {
37
  if (!isRunning) {
38
+ showDeleteConfirm({ onOk: () => removeDocument([documentId]) });
39
  }
40
  };
41
 
web/src/pages/file-manager/action-cell/index.tsx CHANGED
@@ -38,7 +38,7 @@ const ActionCell = ({
38
 
39
  const onDownloadDocument = () => {
40
  downloadFile({
41
- url: `${api_host}/document/get/${documentId}`,
42
  filename: record.name,
43
  });
44
  };
 
38
 
39
  const onDownloadDocument = () => {
40
  downloadFile({
41
+ url: `${api_host}/file/get/${documentId}`,
42
  filename: record.name,
43
  });
44
  };
web/src/pages/file-manager/connect-to-knowledge-modal/index.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
2
  import { IModalProps } from '@/interfaces/common';
3
  import { Form, Modal, Select, SelectProps } from 'antd';
@@ -8,9 +9,11 @@ const ConnectToKnowledgeModal = ({
8
  hideModal,
9
  onOk,
10
  initialValue,
 
11
  }: IModalProps<string[]> & { initialValue: string[] }) => {
12
  const [form] = Form.useForm();
13
  const { list, fetchList } = useFetchKnowledgeList();
 
14
 
15
  const options: SelectProps['options'] = list?.map((item) => ({
16
  label: item.name,
@@ -32,10 +35,11 @@ const ConnectToKnowledgeModal = ({
32
 
33
  return (
34
  <Modal
35
- title="Add to Knowledge Base"
36
  open={visible}
37
  onOk={handleOk}
38
  onCancel={hideModal}
 
39
  >
40
  <Form form={form}>
41
  <Form.Item name="knowledgeIds" noStyle>
@@ -43,7 +47,7 @@ const ConnectToKnowledgeModal = ({
43
  mode="multiple"
44
  allowClear
45
  style={{ width: '100%' }}
46
- placeholder="Please select"
47
  options={options}
48
  />
49
  </Form.Item>
 
1
+ import { useTranslate } from '@/hooks/commonHooks';
2
  import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
3
  import { IModalProps } from '@/interfaces/common';
4
  import { Form, Modal, Select, SelectProps } from 'antd';
 
9
  hideModal,
10
  onOk,
11
  initialValue,
12
+ loading,
13
  }: IModalProps<string[]> & { initialValue: string[] }) => {
14
  const [form] = Form.useForm();
15
  const { list, fetchList } = useFetchKnowledgeList();
16
+ const { t } = useTranslate('fileManager');
17
 
18
  const options: SelectProps['options'] = list?.map((item) => ({
19
  label: item.name,
 
35
 
36
  return (
37
  <Modal
38
+ title={t('addToKnowledge')}
39
  open={visible}
40
  onOk={handleOk}
41
  onCancel={hideModal}
42
+ confirmLoading={loading}
43
  >
44
  <Form form={form}>
45
  <Form.Item name="knowledgeIds" noStyle>
 
47
  mode="multiple"
48
  allowClear
49
  style={{ width: '100%' }}
50
+ placeholder={t('pleaseSelect')}
51
  options={options}
52
  />
53
  </Form.Item>
web/src/pages/file-manager/file-toolbar.tsx CHANGED
@@ -20,12 +20,12 @@ import {
20
  import { useMemo } from 'react';
21
  import {
22
  useFetchDocumentListOnMount,
 
23
  useHandleDeleteFile,
24
  useHandleSearchChange,
25
  useSelectBreadcrumbItems,
26
  } from './hooks';
27
 
28
- import { Link } from 'umi';
29
  import styles from './index.less';
30
 
31
  interface IProps {
@@ -35,20 +35,6 @@ interface IProps {
35
  setSelectedRowKeys: (keys: string[]) => void;
36
  }
37
 
38
- const itemRender: BreadcrumbProps['itemRender'] = (
39
- currentRoute,
40
- params,
41
- items,
42
- ) => {
43
- const isLast = currentRoute?.path === items[items.length - 1]?.path;
44
-
45
- return isLast ? (
46
- <span>{currentRoute.title}</span>
47
- ) : (
48
- <Link to={`${currentRoute.path}`}>{currentRoute.title}</Link>
49
- );
50
- };
51
-
52
  const FileToolbar = ({
53
  selectedRowKeys,
54
  showFolderCreateModal,
@@ -59,6 +45,26 @@ const FileToolbar = ({
59
  useFetchDocumentListOnMount();
60
  const { handleInputChange, searchString } = useHandleSearchChange();
61
  const breadcrumbItems = useSelectBreadcrumbItems();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  const actionItems: MenuProps['items'] = useMemo(() => {
64
  return [
@@ -70,7 +76,7 @@ const FileToolbar = ({
70
  <Button type="link">
71
  <Space>
72
  <FileTextOutlined />
73
- {t('localFiles')}
74
  </Space>
75
  </Button>
76
  </div>
@@ -83,12 +89,13 @@ const FileToolbar = ({
83
  label: (
84
  <div>
85
  <Button type="link">
86
- <FolderOpenOutlined />
87
- New Folder
 
 
88
  </Button>
89
  </div>
90
  ),
91
- // disabled: true,
92
  },
93
  ];
94
  }, [t, showFolderCreateModal, showFileUploadModal]);
 
20
  import { useMemo } from 'react';
21
  import {
22
  useFetchDocumentListOnMount,
23
+ useHandleBreadcrumbClick,
24
  useHandleDeleteFile,
25
  useHandleSearchChange,
26
  useSelectBreadcrumbItems,
27
  } from './hooks';
28
 
 
29
  import styles from './index.less';
30
 
31
  interface IProps {
 
35
  setSelectedRowKeys: (keys: string[]) => void;
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  const FileToolbar = ({
39
  selectedRowKeys,
40
  showFolderCreateModal,
 
45
  useFetchDocumentListOnMount();
46
  const { handleInputChange, searchString } = useHandleSearchChange();
47
  const breadcrumbItems = useSelectBreadcrumbItems();
48
+ const { handleBreadcrumbClick } = useHandleBreadcrumbClick();
49
+
50
+ const itemRender: BreadcrumbProps['itemRender'] = (
51
+ currentRoute,
52
+ params,
53
+ items,
54
+ ) => {
55
+ const isLast = currentRoute?.path === items[items.length - 1]?.path;
56
+
57
+ return isLast ? (
58
+ <span>{currentRoute.title}</span>
59
+ ) : (
60
+ <span
61
+ className={styles.breadcrumbItemButton}
62
+ onClick={() => handleBreadcrumbClick(currentRoute.path)}
63
+ >
64
+ {currentRoute.title}
65
+ </span>
66
+ );
67
+ };
68
 
69
  const actionItems: MenuProps['items'] = useMemo(() => {
70
  return [
 
76
  <Button type="link">
77
  <Space>
78
  <FileTextOutlined />
79
+ {t('uploadFile', { keyPrefix: 'fileManager' })}
80
  </Space>
81
  </Button>
82
  </div>
 
89
  label: (
90
  <div>
91
  <Button type="link">
92
+ <Space>
93
+ <FolderOpenOutlined />
94
+ {t('newFolder', { keyPrefix: 'fileManager' })}
95
+ </Space>
96
  </Button>
97
  </div>
98
  ),
 
99
  },
100
  ];
101
  }, [t, showFolderCreateModal, showFileUploadModal]);
web/src/pages/file-manager/file-upload-modal/index.less ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ .uploader {
2
+ :global {
3
+ .ant-upload-list {
4
+ max-height: 40vh;
5
+ overflow-y: auto;
6
+ }
7
+ }
8
+ }
web/src/pages/file-manager/file-upload-modal/index.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { IModalProps } from '@/interfaces/common';
2
  import { InboxOutlined } from '@ant-design/icons';
3
  import {
@@ -12,6 +13,8 @@ import {
12
  } from 'antd';
13
  import { Dispatch, SetStateAction, useState } from 'react';
14
 
 
 
15
  const { Dragger } = Upload;
16
 
17
  const FileUpload = ({
@@ -23,6 +26,7 @@ const FileUpload = ({
23
  fileList: UploadFile[];
24
  setFileList: Dispatch<SetStateAction<UploadFile[]>>;
25
  }) => {
 
26
  const props: UploadProps = {
27
  multiple: true,
28
  onRemove: (file) => {
@@ -43,17 +47,12 @@ const FileUpload = ({
43
  };
44
 
45
  return (
46
- <Dragger {...props}>
47
  <p className="ant-upload-drag-icon">
48
  <InboxOutlined />
49
  </p>
50
- <p className="ant-upload-text">
51
- Click or drag file to this area to upload
52
- </p>
53
- <p className="ant-upload-hint">
54
- Support for a single or bulk upload. Strictly prohibited from uploading
55
- company data or other banned files.
56
- </p>
57
  </Dragger>
58
  );
59
  };
@@ -64,18 +63,25 @@ const FileUploadModal = ({
64
  loading,
65
  onOk: onFileUploadOk,
66
  }: IModalProps<UploadFile[]>) => {
 
67
  const [value, setValue] = useState<string | number>('local');
68
  const [fileList, setFileList] = useState<UploadFile[]>([]);
69
  const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
70
 
71
- const onOk = () => {
72
- return onFileUploadOk?.([...fileList, ...directoryFileList]);
 
 
 
 
 
 
73
  };
74
 
75
  const items: TabsProps['items'] = [
76
  {
77
  key: '1',
78
- label: 'File',
79
  children: (
80
  <FileUpload
81
  directory={false}
@@ -86,7 +92,7 @@ const FileUploadModal = ({
86
  },
87
  {
88
  key: '2',
89
- label: 'Directory',
90
  children: (
91
  <FileUpload
92
  directory
@@ -100,7 +106,7 @@ const FileUploadModal = ({
100
  return (
101
  <>
102
  <Modal
103
- title="File upload"
104
  open={visible}
105
  onOk={onOk}
106
  onCancel={hideModal}
@@ -109,8 +115,8 @@ const FileUploadModal = ({
109
  <Flex gap={'large'} vertical>
110
  <Segmented
111
  options={[
112
- { label: 'Local uploads', value: 'local' },
113
- { label: 'S3 uploads', value: 's3' },
114
  ]}
115
  block
116
  value={value}
@@ -119,7 +125,7 @@ const FileUploadModal = ({
119
  {value === 'local' ? (
120
  <Tabs defaultActiveKey="1" items={items} />
121
  ) : (
122
- 'coming soon'
123
  )}
124
  </Flex>
125
  </Modal>
 
1
+ import { useTranslate } from '@/hooks/commonHooks';
2
  import { IModalProps } from '@/interfaces/common';
3
  import { InboxOutlined } from '@ant-design/icons';
4
  import {
 
13
  } from 'antd';
14
  import { Dispatch, SetStateAction, useState } from 'react';
15
 
16
+ import styles from './index.less';
17
+
18
  const { Dragger } = Upload;
19
 
20
  const FileUpload = ({
 
26
  fileList: UploadFile[];
27
  setFileList: Dispatch<SetStateAction<UploadFile[]>>;
28
  }) => {
29
+ const { t } = useTranslate('fileManager');
30
  const props: UploadProps = {
31
  multiple: true,
32
  onRemove: (file) => {
 
47
  };
48
 
49
  return (
50
+ <Dragger {...props} className={styles.uploader}>
51
  <p className="ant-upload-drag-icon">
52
  <InboxOutlined />
53
  </p>
54
+ <p className="ant-upload-text">{t('uploadTitle')}</p>
55
+ <p className="ant-upload-hint">{t('uploadDescription')}</p>
 
 
 
 
 
56
  </Dragger>
57
  );
58
  };
 
63
  loading,
64
  onOk: onFileUploadOk,
65
  }: IModalProps<UploadFile[]>) => {
66
+ const { t } = useTranslate('fileManager');
67
  const [value, setValue] = useState<string | number>('local');
68
  const [fileList, setFileList] = useState<UploadFile[]>([]);
69
  const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
70
 
71
+ const onOk = async () => {
72
+ const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
73
+ console.info(ret);
74
+ if (ret !== undefined && ret === 0) {
75
+ setFileList([]);
76
+ setDirectoryFileList([]);
77
+ }
78
+ return ret;
79
  };
80
 
81
  const items: TabsProps['items'] = [
82
  {
83
  key: '1',
84
+ label: t('file'),
85
  children: (
86
  <FileUpload
87
  directory={false}
 
92
  },
93
  {
94
  key: '2',
95
+ label: t('directory'),
96
  children: (
97
  <FileUpload
98
  directory
 
106
  return (
107
  <>
108
  <Modal
109
+ title={t('uploadFile')}
110
  open={visible}
111
  onOk={onOk}
112
  onCancel={hideModal}
 
115
  <Flex gap={'large'} vertical>
116
  <Segmented
117
  options={[
118
+ { label: t('local'), value: 'local' },
119
+ { label: t('s3'), value: 's3' },
120
  ]}
121
  block
122
  value={value}
 
125
  {value === 'local' ? (
126
  <Tabs defaultActiveKey="1" items={items} />
127
  ) : (
128
+ t('comingSoon', { keyPrefix: 'common' })
129
  )}
130
  </Flex>
131
  </Modal>
web/src/pages/file-manager/folder-create-modal/index.tsx CHANGED
@@ -35,7 +35,7 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => {
35
 
36
  return (
37
  <Modal
38
- title={'New Folder'}
39
  open={visible}
40
  onOk={handleOk}
41
  onCancel={handleCancel}
 
35
 
36
  return (
37
  <Modal
38
+ title={t('newFolder', { keyPrefix: 'fileManager' })}
39
  open={visible}
40
  onOk={handleOk}
41
  onCancel={handleCancel}
web/src/pages/file-manager/hooks.ts CHANGED
@@ -244,14 +244,14 @@ export const useHandleUploadFile = () => {
244
  const id = useGetFolderId();
245
 
246
  const onFileUploadOk = useCallback(
247
- async (fileList: UploadFile[]) => {
248
- console.info('fileList', fileList);
249
  if (fileList.length > 0) {
250
- const ret = await uploadFile(fileList, id);
251
  console.info(ret);
252
  if (ret === 0) {
253
  hideFileUploadModal();
254
  }
 
255
  }
256
  },
257
  [uploadFile, hideFileUploadModal, id],
@@ -295,6 +295,7 @@ export const useHandleConnectToKnowledge = () => {
295
  if (ret === 0) {
296
  hideConnectToKnowledgeModal();
297
  }
 
298
  },
299
  [connectToKnowledge, hideConnectToKnowledgeModal, id, record.id],
300
  );
@@ -320,3 +321,20 @@ export const useHandleConnectToKnowledge = () => {
320
  showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal,
321
  };
322
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  const id = useGetFolderId();
245
 
246
  const onFileUploadOk = useCallback(
247
+ async (fileList: UploadFile[]): Promise<number | undefined> => {
 
248
  if (fileList.length > 0) {
249
+ const ret: number = await uploadFile(fileList, id);
250
  console.info(ret);
251
  if (ret === 0) {
252
  hideFileUploadModal();
253
  }
254
+ return ret;
255
  }
256
  },
257
  [uploadFile, hideFileUploadModal, id],
 
295
  if (ret === 0) {
296
  hideConnectToKnowledgeModal();
297
  }
298
+ return ret;
299
  },
300
  [connectToKnowledge, hideConnectToKnowledgeModal, id, record.id],
301
  );
 
321
  showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal,
322
  };
323
  };
324
+
325
+ export const useHandleBreadcrumbClick = () => {
326
+ const navigate = useNavigate();
327
+ const setPagination = useSetPagination('fileManager');
328
+
329
+ const handleBreadcrumbClick = useCallback(
330
+ (path?: string) => {
331
+ if (path) {
332
+ setPagination();
333
+ navigate(path);
334
+ }
335
+ },
336
+ [setPagination, navigate],
337
+ );
338
+
339
+ return { handleBreadcrumbClick };
340
+ };
web/src/pages/file-manager/index.less CHANGED
@@ -20,3 +20,10 @@
20
  .linkButton {
21
  padding: 0;
22
  }
 
 
 
 
 
 
 
 
20
  .linkButton {
21
  padding: 0;
22
  }
23
+
24
+ .breadcrumbItemButton {
25
+ cursor: pointer;
26
+ color: #1677ff;
27
+ padding: 0;
28
+ height: auto;
29
+ }
web/src/pages/file-manager/index.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useSelectFileList } from '@/hooks/fileManagerHooks';
2
  import { IFile } from '@/interfaces/database/file-manager';
3
  import { formatDate } from '@/utils/date';
4
- import { Button, Flex, Table } from 'antd';
5
  import { ColumnsType } from 'antd/es/table';
6
  import ActionCell from './action-cell';
7
  import FileToolbar from './file-toolbar';
@@ -18,6 +18,8 @@ import {
18
 
19
  import RenameModal from '@/components/rename-modal';
20
  import SvgIcon from '@/components/svg-icon';
 
 
21
  import { getExtension } from '@/utils/documentUtils';
22
  import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
23
  import FileUploadModal from './file-upload-modal';
@@ -25,6 +27,7 @@ import FolderCreateModal from './folder-create-modal';
25
  import styles from './index.less';
26
 
27
  const FileManager = () => {
 
28
  const fileList = useSelectFileList();
29
  const { rowSelection, setSelectedRowKeys } = useGetRowSelection();
30
  const loading = useSelectFileListLoading();
@@ -57,12 +60,13 @@ const FileManager = () => {
57
  showConnectToKnowledgeModal,
58
  onConnectToKnowledgeOk,
59
  initialValue,
 
60
  } = useHandleConnectToKnowledge();
61
  const { pagination } = useGetFilesPagination();
62
 
63
  const columns: ColumnsType<IFile> = [
64
  {
65
- title: 'Name',
66
  dataIndex: 'name',
67
  key: 'name',
68
  render(value, record) {
@@ -88,7 +92,7 @@ const FileManager = () => {
88
  },
89
  },
90
  {
91
- title: 'Upload Date',
92
  dataIndex: 'create_date',
93
  key: 'create_date',
94
  render(text) {
@@ -96,22 +100,35 @@ const FileManager = () => {
96
  },
97
  },
98
  {
99
- title: 'Knowledge Base',
100
- dataIndex: 'kbs_info',
101
- key: 'kbs_info',
102
  render(value) {
103
- return Array.isArray(value)
104
- ? value?.map((x) => x.kb_name).join(',')
105
- : '';
106
  },
107
  },
108
  {
109
- title: 'Location',
110
- dataIndex: 'location',
111
- key: 'location',
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  },
113
  {
114
- title: 'Action',
115
  dataIndex: 'action',
116
  key: 'action',
117
  render: (text, record) => (
@@ -168,6 +185,7 @@ const FileManager = () => {
168
  visible={connectToKnowledgeVisible}
169
  hideModal={hideConnectToKnowledgeModal}
170
  onOk={onConnectToKnowledgeOk}
 
171
  ></ConnectToKnowledgeModal>
172
  </section>
173
  );
 
1
  import { useSelectFileList } from '@/hooks/fileManagerHooks';
2
  import { IFile } from '@/interfaces/database/file-manager';
3
  import { formatDate } from '@/utils/date';
4
+ import { Button, Flex, Space, Table, Tag } from 'antd';
5
  import { ColumnsType } from 'antd/es/table';
6
  import ActionCell from './action-cell';
7
  import FileToolbar from './file-toolbar';
 
18
 
19
  import RenameModal from '@/components/rename-modal';
20
  import SvgIcon from '@/components/svg-icon';
21
+ import { useTranslate } from '@/hooks/commonHooks';
22
+ import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil';
23
  import { getExtension } from '@/utils/documentUtils';
24
  import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
25
  import FileUploadModal from './file-upload-modal';
 
27
  import styles from './index.less';
28
 
29
  const FileManager = () => {
30
+ const { t } = useTranslate('fileManager');
31
  const fileList = useSelectFileList();
32
  const { rowSelection, setSelectedRowKeys } = useGetRowSelection();
33
  const loading = useSelectFileListLoading();
 
60
  showConnectToKnowledgeModal,
61
  onConnectToKnowledgeOk,
62
  initialValue,
63
+ connectToKnowledgeLoading,
64
  } = useHandleConnectToKnowledge();
65
  const { pagination } = useGetFilesPagination();
66
 
67
  const columns: ColumnsType<IFile> = [
68
  {
69
+ title: t('name'),
70
  dataIndex: 'name',
71
  key: 'name',
72
  render(value, record) {
 
92
  },
93
  },
94
  {
95
+ title: t('uploadDate'),
96
  dataIndex: 'create_date',
97
  key: 'create_date',
98
  render(text) {
 
100
  },
101
  },
102
  {
103
+ title: t('size'),
104
+ dataIndex: 'size',
105
+ key: 'size',
106
  render(value) {
107
+ return (
108
+ formatNumberWithThousandsSeparator((value / 1024).toFixed(2)) + ' KB'
109
+ );
110
  },
111
  },
112
  {
113
+ title: t('knowledgeBase'),
114
+ dataIndex: 'kbs_info',
115
+ key: 'kbs_info',
116
+ render(value) {
117
+ return Array.isArray(value) ? (
118
+ <Space wrap>
119
+ {value?.map((x) => (
120
+ <Tag color="blue" key={x.kb_id}>
121
+ {x.kb_name}
122
+ </Tag>
123
+ ))}
124
+ </Space>
125
+ ) : (
126
+ ''
127
+ );
128
+ },
129
  },
130
  {
131
+ title: t('action'),
132
  dataIndex: 'action',
133
  key: 'action',
134
  render: (text, record) => (
 
185
  visible={connectToKnowledgeVisible}
186
  hideModal={hideConnectToKnowledgeModal}
187
  onOk={onConnectToKnowledgeOk}
188
+ loading={connectToKnowledgeLoading}
189
  ></ConnectToKnowledgeModal>
190
  </section>
191
  );
web/src/pages/file-manager/model.ts CHANGED
@@ -1,7 +1,9 @@
1
  import { paginationModel } from '@/base';
2
  import { BaseState } from '@/interfaces/common';
3
  import { IFile, IFolder } from '@/interfaces/database/file-manager';
 
4
  import fileManagerService from '@/services/fileManagerService';
 
5
  import omit from 'lodash/omit';
6
  import { DvaModel } from 'umi';
7
 
@@ -33,6 +35,7 @@ const model: DvaModel<FileManagerModelState> = {
33
  });
34
  const { retcode } = data;
35
  if (retcode === 0) {
 
36
  yield put({
37
  type: 'listFile',
38
  payload: { parentId: payload.parentId },
@@ -69,6 +72,7 @@ const model: DvaModel<FileManagerModelState> = {
69
  omit(payload, ['parentId']),
70
  );
71
  if (data.retcode === 0) {
 
72
  yield put({
73
  type: 'listFile',
74
  payload: { parentId: payload.parentId },
@@ -89,6 +93,8 @@ const model: DvaModel<FileManagerModelState> = {
89
  });
90
  const { data } = yield call(fileManagerService.uploadFile, formData);
91
  if (data.retcode === 0) {
 
 
92
  yield put({
93
  type: 'listFile',
94
  payload: { parentId: payload.parentId },
@@ -99,6 +105,8 @@ const model: DvaModel<FileManagerModelState> = {
99
  *createFolder({ payload = {} }, { call, put }) {
100
  const { data } = yield call(fileManagerService.createFolder, payload);
101
  if (data.retcode === 0) {
 
 
102
  yield put({
103
  type: 'listFile',
104
  payload: { parentId: payload.parentId },
@@ -125,6 +133,7 @@ const model: DvaModel<FileManagerModelState> = {
125
  omit(payload, 'parentId'),
126
  );
127
  if (data.retcode === 0) {
 
128
  yield put({
129
  type: 'listFile',
130
  payload: { parentId: payload.parentId },
 
1
  import { paginationModel } from '@/base';
2
  import { BaseState } from '@/interfaces/common';
3
  import { IFile, IFolder } from '@/interfaces/database/file-manager';
4
+ import i18n from '@/locales/config';
5
  import fileManagerService from '@/services/fileManagerService';
6
+ import { message } from 'antd';
7
  import omit from 'lodash/omit';
8
  import { DvaModel } from 'umi';
9
 
 
35
  });
36
  const { retcode } = data;
37
  if (retcode === 0) {
38
+ message.success(i18n.t('message.deleted'));
39
  yield put({
40
  type: 'listFile',
41
  payload: { parentId: payload.parentId },
 
72
  omit(payload, ['parentId']),
73
  );
74
  if (data.retcode === 0) {
75
+ message.success(i18n.t('message.renamed'));
76
  yield put({
77
  type: 'listFile',
78
  payload: { parentId: payload.parentId },
 
93
  });
94
  const { data } = yield call(fileManagerService.uploadFile, formData);
95
  if (data.retcode === 0) {
96
+ message.success(i18n.t('message.uploaded'));
97
+
98
  yield put({
99
  type: 'listFile',
100
  payload: { parentId: payload.parentId },
 
105
  *createFolder({ payload = {} }, { call, put }) {
106
  const { data } = yield call(fileManagerService.createFolder, payload);
107
  if (data.retcode === 0) {
108
+ message.success(i18n.t('message.created'));
109
+
110
  yield put({
111
  type: 'listFile',
112
  payload: { parentId: payload.parentId },
 
133
  omit(payload, 'parentId'),
134
  );
135
  if (data.retcode === 0) {
136
+ message.success(i18n.t('message.operated'));
137
  yield put({
138
  type: 'listFile',
139
  payload: { parentId: payload.parentId },
web/src/utils/commonUtil.ts CHANGED
@@ -27,3 +27,9 @@ export const getSearchValue = (key: string) => {
27
  const params = new URL(document.location as any).searchParams;
28
  return params.get(key);
29
  };
 
 
 
 
 
 
 
27
  const params = new URL(document.location as any).searchParams;
28
  return params.get(key);
29
  };
30
+
31
+ // Formatize numbers, add thousands of separators
32
+ export const formatNumberWithThousandsSeparator = (numberStr: string) => {
33
+ const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
34
+ return formattedNumber;
35
+ };