balibabu commited on
Commit
4c6dadf
·
1 Parent(s): abeee5e

fix: #567 use modal to upload files in the knowledge base (#601)

Browse files

### What problem does this PR solve?

fix: #567 use modal to upload files in the knowledge base

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

web/src/components/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/components/file-upload-modal/index.tsx ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslate } from '@/hooks/commonHooks';
2
+ import { IModalProps } from '@/interfaces/common';
3
+ import { InboxOutlined } from '@ant-design/icons';
4
+ import {
5
+ Flex,
6
+ Modal,
7
+ Segmented,
8
+ Tabs,
9
+ TabsProps,
10
+ Upload,
11
+ UploadFile,
12
+ UploadProps,
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 = ({
21
+ directory,
22
+ fileList,
23
+ setFileList,
24
+ }: {
25
+ directory: boolean;
26
+ fileList: UploadFile[];
27
+ setFileList: Dispatch<SetStateAction<UploadFile[]>>;
28
+ }) => {
29
+ const { t } = useTranslate('fileManager');
30
+ const props: UploadProps = {
31
+ multiple: true,
32
+ onRemove: (file) => {
33
+ const index = fileList.indexOf(file);
34
+ const newFileList = fileList.slice();
35
+ newFileList.splice(index, 1);
36
+ setFileList(newFileList);
37
+ },
38
+ beforeUpload: (file) => {
39
+ setFileList((pre) => {
40
+ return [...pre, file];
41
+ });
42
+
43
+ return false;
44
+ },
45
+ directory,
46
+ fileList,
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
+ };
59
+
60
+ const FileUploadModal = ({
61
+ visible,
62
+ hideModal,
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
+ if (ret !== undefined && ret === 0) {
74
+ setFileList([]);
75
+ setDirectoryFileList([]);
76
+ }
77
+ return ret;
78
+ };
79
+
80
+ const items: TabsProps['items'] = [
81
+ {
82
+ key: '1',
83
+ label: t('file'),
84
+ children: (
85
+ <FileUpload
86
+ directory={false}
87
+ fileList={fileList}
88
+ setFileList={setFileList}
89
+ ></FileUpload>
90
+ ),
91
+ },
92
+ {
93
+ key: '2',
94
+ label: t('directory'),
95
+ children: (
96
+ <FileUpload
97
+ directory
98
+ fileList={directoryFileList}
99
+ setFileList={setDirectoryFileList}
100
+ ></FileUpload>
101
+ ),
102
+ },
103
+ ];
104
+
105
+ return (
106
+ <>
107
+ <Modal
108
+ title={t('uploadFile')}
109
+ open={visible}
110
+ onOk={onOk}
111
+ onCancel={hideModal}
112
+ confirmLoading={loading}
113
+ >
114
+ <Flex gap={'large'} vertical>
115
+ <Segmented
116
+ options={[
117
+ { label: t('local'), value: 'local' },
118
+ { label: t('s3'), value: 's3' },
119
+ ]}
120
+ block
121
+ value={value}
122
+ onChange={setValue}
123
+ />
124
+ {value === 'local' ? (
125
+ <Tabs defaultActiveKey="1" items={items} />
126
+ ) : (
127
+ t('comingSoon', { keyPrefix: 'common' })
128
+ )}
129
+ </Flex>
130
+ </Modal>
131
+ </>
132
+ );
133
+ };
134
+
135
+ export default FileUploadModal;
web/src/hooks/documentHooks.ts CHANGED
@@ -184,12 +184,12 @@ export const useUploadDocument = () => {
184
  const { knowledgeId } = useGetKnowledgeSearchParams();
185
 
186
  const uploadDocument = useCallback(
187
- (file: UploadFile) => {
188
  try {
189
  return dispatch<any>({
190
  type: 'kFModel/upload_document',
191
  payload: {
192
- file,
193
  kb_id: knowledgeId,
194
  },
195
  });
 
184
  const { knowledgeId } = useGetKnowledgeSearchParams();
185
 
186
  const uploadDocument = useCallback(
187
+ (fileList: UploadFile[]) => {
188
  try {
189
  return dispatch<any>({
190
  type: 'kFModel/upload_document',
191
  payload: {
192
+ fileList,
193
  kb_id: knowledgeId,
194
  },
195
  });
web/src/hooks/llmHooks.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
  IThirdOAIModelCollection,
6
  } from '@/interfaces/database/llm';
7
  import { IAddLlmRequestBody } from '@/interfaces/request/llm';
 
8
  import { useCallback, useEffect, useMemo } from 'react';
9
  import { useDispatch, useSelector } from 'umi';
10
 
@@ -110,13 +111,12 @@ export const useFetchLlmFactoryListOnMount = () => {
110
  const factoryList = useSelectLlmFactoryList();
111
  const myLlmList = useSelectMyLlmList();
112
 
113
- const list = useMemo(
114
- () =>
115
- factoryList.filter((x) =>
116
- Object.keys(myLlmList).every((y) => y !== x.name),
117
- ),
118
- [factoryList, myLlmList],
119
- );
120
 
121
  const fetchLlmFactoryList = useCallback(() => {
122
  dispatch({
 
5
  IThirdOAIModelCollection,
6
  } from '@/interfaces/database/llm';
7
  import { IAddLlmRequestBody } from '@/interfaces/request/llm';
8
+ import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/commonUtil';
9
  import { useCallback, useEffect, useMemo } from 'react';
10
  import { useDispatch, useSelector } from 'umi';
11
 
 
111
  const factoryList = useSelectLlmFactoryList();
112
  const myLlmList = useSelectMyLlmList();
113
 
114
+ const list = useMemo(() => {
115
+ const currentList = factoryList.filter((x) =>
116
+ Object.keys(myLlmList).every((y) => y !== x.name),
117
+ );
118
+ return sortLLmFactoryListBySpecifiedOrder(currentList);
119
+ }, [factoryList, myLlmList]);
 
120
 
121
  const fetchLlmFactoryList = useCallback(() => {
122
  dispatch({
web/src/pages/add-knowledge/components/knowledge-file/document-toolbar.tsx CHANGED
@@ -30,9 +30,14 @@ import styles from './index.less';
30
  interface IProps {
31
  selectedRowKeys: string[];
32
  showCreateModal(): void;
 
33
  }
34
 
35
- const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
 
 
 
 
36
  const { t } = useTranslate('knowledgeDetails');
37
  const { fetchDocumentList } = useFetchDocumentListOnMount();
38
  const { setPagination, searchString } = useGetPagination(fetchDocumentList);
@@ -48,7 +53,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
48
  return [
49
  {
50
  key: '1',
51
- onClick: linkToUploadPage,
52
  label: (
53
  <div>
54
  <Button type="link">
@@ -75,7 +80,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
75
  // disabled: true,
76
  },
77
  ];
78
- }, [linkToUploadPage, showCreateModal, t]);
79
 
80
  const handleDelete = useCallback(() => {
81
  showDeleteConfirm({
 
30
  interface IProps {
31
  selectedRowKeys: string[];
32
  showCreateModal(): void;
33
+ showDocumentUploadModal(): void;
34
  }
35
 
36
+ const DocumentToolbar = ({
37
+ selectedRowKeys,
38
+ showCreateModal,
39
+ showDocumentUploadModal,
40
+ }: IProps) => {
41
  const { t } = useTranslate('knowledgeDetails');
42
  const { fetchDocumentList } = useFetchDocumentListOnMount();
43
  const { setPagination, searchString } = useGetPagination(fetchDocumentList);
 
53
  return [
54
  {
55
  key: '1',
56
+ onClick: showDocumentUploadModal,
57
  label: (
58
  <div>
59
  <Button type="link">
 
80
  // disabled: true,
81
  },
82
  ];
83
+ }, [showDocumentUploadModal, showCreateModal, t]);
84
 
85
  const handleDelete = useCallback(() => {
86
  showDeleteConfirm({
web/src/pages/add-knowledge/components/knowledge-file/hooks.ts CHANGED
@@ -4,13 +4,15 @@ import {
4
  useFetchDocumentList,
5
  useSaveDocumentName,
6
  useSetDocumentParser,
 
7
  } from '@/hooks/documentHooks';
8
  import { useGetKnowledgeSearchParams } from '@/hooks/routeHook';
9
  import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
10
  import { useFetchTenantInfo } from '@/hooks/userSettingHook';
11
  import { Pagination } from '@/interfaces/common';
12
  import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
13
- import { PaginationProps } from 'antd';
 
14
  import { useCallback, useEffect, useMemo, useState } from 'react';
15
  import { useDispatch, useNavigate, useSelector } from 'umi';
16
  import { KnowledgeRouteKey } from './constant';
@@ -242,3 +244,42 @@ export const useGetRowSelection = () => {
242
 
243
  return rowSelection;
244
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  useFetchDocumentList,
5
  useSaveDocumentName,
6
  useSetDocumentParser,
7
+ useUploadDocument,
8
  } from '@/hooks/documentHooks';
9
  import { useGetKnowledgeSearchParams } from '@/hooks/routeHook';
10
  import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
11
  import { useFetchTenantInfo } from '@/hooks/userSettingHook';
12
  import { Pagination } from '@/interfaces/common';
13
  import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
14
+ import { getUnSupportedFilesCount } from '@/utils/documentUtils';
15
+ import { PaginationProps, UploadFile } from 'antd';
16
  import { useCallback, useEffect, useMemo, useState } from 'react';
17
  import { useDispatch, useNavigate, useSelector } from 'umi';
18
  import { KnowledgeRouteKey } from './constant';
 
244
 
245
  return rowSelection;
246
  };
247
+
248
+ export const useHandleUploadDocument = () => {
249
+ const {
250
+ visible: documentUploadVisible,
251
+ hideModal: hideDocumentUploadModal,
252
+ showModal: showDocumentUploadModal,
253
+ } = useSetModalState();
254
+ const uploadDocument = useUploadDocument();
255
+
256
+ const onDocumentUploadOk = useCallback(
257
+ async (fileList: UploadFile[]): Promise<number | undefined> => {
258
+ if (fileList.length > 0) {
259
+ const ret: any = await uploadDocument(fileList);
260
+ const count = getUnSupportedFilesCount(ret.retmsg);
261
+ /// 500 error code indicates that some file types are not supported
262
+ let retcode = ret.retcode;
263
+ if (
264
+ ret.retcode === 0 ||
265
+ (ret.retcode === 500 && count !== fileList.length) // Some files were not uploaded successfully, but some were uploaded successfully.
266
+ ) {
267
+ retcode = 0;
268
+ hideDocumentUploadModal();
269
+ }
270
+ return retcode;
271
+ }
272
+ },
273
+ [uploadDocument, hideDocumentUploadModal],
274
+ );
275
+
276
+ const loading = useOneNamespaceEffectsLoading('kFModel', ['upload_document']);
277
+
278
+ return {
279
+ documentUploadLoading: loading,
280
+ onDocumentUploadOk,
281
+ documentUploadVisible,
282
+ hideDocumentUploadModal,
283
+ showDocumentUploadModal,
284
+ };
285
+ };
web/src/pages/add-knowledge/components/knowledge-file/index.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
  useFetchDocumentListOnMount,
20
  useGetPagination,
21
  useGetRowSelection,
 
22
  useNavigateToOtherPage,
23
  useRenameDocument,
24
  } from './hooks';
@@ -26,6 +27,7 @@ import ParsingActionCell from './parsing-action-cell';
26
  import ParsingStatusCell from './parsing-status-cell';
27
  import RenameModal from './rename-modal';
28
 
 
29
  import { formatDate } from '@/utils/date';
30
  import styles from './index.less';
31
 
@@ -58,6 +60,13 @@ const KnowledgeFile = () => {
58
  hideChangeParserModal,
59
  showChangeParserModal,
60
  } = useChangeDocumentParser(currentRecord.id);
 
 
 
 
 
 
 
61
  const { t } = useTranslation('translation', {
62
  keyPrefix: 'knowledgeDetails',
63
  });
@@ -157,6 +166,7 @@ const KnowledgeFile = () => {
157
  <DocumentToolbar
158
  selectedRowKeys={rowSelection.selectedRowKeys as string[]}
159
  showCreateModal={showCreateModal}
 
160
  ></DocumentToolbar>
161
  <Table
162
  rowKey="id"
@@ -190,6 +200,12 @@ const KnowledgeFile = () => {
190
  hideModal={hideRenameModal}
191
  initialName={currentRecord.name}
192
  ></RenameModal>
 
 
 
 
 
 
193
  </div>
194
  );
195
  };
 
19
  useFetchDocumentListOnMount,
20
  useGetPagination,
21
  useGetRowSelection,
22
+ useHandleUploadDocument,
23
  useNavigateToOtherPage,
24
  useRenameDocument,
25
  } from './hooks';
 
27
  import ParsingStatusCell from './parsing-status-cell';
28
  import RenameModal from './rename-modal';
29
 
30
+ import FileUploadModal from '@/components/file-upload-modal';
31
  import { formatDate } from '@/utils/date';
32
  import styles from './index.less';
33
 
 
60
  hideChangeParserModal,
61
  showChangeParserModal,
62
  } = useChangeDocumentParser(currentRecord.id);
63
+ const {
64
+ documentUploadVisible,
65
+ hideDocumentUploadModal,
66
+ showDocumentUploadModal,
67
+ onDocumentUploadOk,
68
+ documentUploadLoading,
69
+ } = useHandleUploadDocument();
70
  const { t } = useTranslation('translation', {
71
  keyPrefix: 'knowledgeDetails',
72
  });
 
166
  <DocumentToolbar
167
  selectedRowKeys={rowSelection.selectedRowKeys as string[]}
168
  showCreateModal={showCreateModal}
169
+ showDocumentUploadModal={showDocumentUploadModal}
170
  ></DocumentToolbar>
171
  <Table
172
  rowKey="id"
 
200
  hideModal={hideRenameModal}
201
  initialName={currentRecord.name}
202
  ></RenameModal>
203
+ <FileUploadModal
204
+ visible={documentUploadVisible}
205
+ hideModal={hideDocumentUploadModal}
206
+ loading={documentUploadLoading}
207
+ onOk={onDocumentUploadOk}
208
+ ></FileUploadModal>
209
  </div>
210
  );
211
  };
web/src/pages/add-knowledge/components/knowledge-file/model.ts CHANGED
@@ -210,11 +210,15 @@ const model: DvaModel<KFModelState> = {
210
  }
211
  },
212
  *upload_document({ payload = {} }, { call, put }) {
 
213
  const formData = new FormData();
214
- formData.append('file', payload.file);
215
  formData.append('kb_id', payload.kb_id);
 
 
 
 
216
  const { data } = yield call(kbService.document_upload, formData);
217
- if (data.retcode === 0) {
218
  yield put({
219
  type: 'getKfList',
220
  payload: { kb_id: payload.kb_id },
 
210
  }
211
  },
212
  *upload_document({ payload = {} }, { call, put }) {
213
+ const fileList = payload.fileList;
214
  const formData = new FormData();
 
215
  formData.append('kb_id', payload.kb_id);
216
+ fileList.forEach((file: any) => {
217
+ formData.append('file', file);
218
+ });
219
+
220
  const { data } = yield call(kbService.document_upload, formData);
221
+ if (data.retcode === 0 || data.retcode === 500) {
222
  yield put({
223
  type: 'getKfList',
224
  payload: { kb_id: payload.kb_id },
web/src/pages/file-manager/model.ts CHANGED
@@ -85,8 +85,6 @@ const model: DvaModel<FileManagerModelState> = {
85
  const pathList = payload.path;
86
  const formData = new FormData();
87
  formData.append('parent_id', payload.parentId);
88
- // formData.append('file', payload.file);
89
- // formData.append('path', payload.path);
90
  fileList.forEach((file: any, index: number) => {
91
  formData.append('file', file);
92
  formData.append('path', pathList[index]);
 
85
  const pathList = payload.path;
86
  const formData = new FormData();
87
  formData.append('parent_id', payload.parentId);
 
 
88
  fileList.forEach((file: any, index: number) => {
89
  formData.append('file', file);
90
  formData.append('path', pathList[index]);
web/src/pages/user-setting/setting-model/index.less CHANGED
@@ -1,5 +1,9 @@
1
  .modelWrapper {
2
  width: 100%;
 
 
 
 
3
  .factoryOperationWrapper {
4
  text-align: right;
5
  }
 
1
  .modelWrapper {
2
  width: 100%;
3
+ }
4
+
5
+ .modelContainer {
6
+ width: 100%;
7
  .factoryOperationWrapper {
8
  text-align: right;
9
  }
web/src/pages/user-setting/setting-model/index.tsx CHANGED
@@ -223,9 +223,9 @@ const UserSettingModel = () => {
223
  ];
224
 
225
  return (
226
- <>
227
  <Spin spinning={loading}>
228
- <section className={styles.modelWrapper}>
229
  <SettingTitle
230
  title={t('model')}
231
  description={t('modelDescription')}
@@ -257,7 +257,7 @@ const UserSettingModel = () => {
257
  loading={llmAddingLoading}
258
  llmFactory={selectedLlmFactory}
259
  ></OllamaModal>
260
- </>
261
  );
262
  };
263
 
 
223
  ];
224
 
225
  return (
226
+ <section id="xx" className={styles.modelWrapper}>
227
  <Spin spinning={loading}>
228
+ <section className={styles.modelContainer}>
229
  <SettingTitle
230
  title={t('model')}
231
  description={t('modelDescription')}
 
257
  loading={llmAddingLoading}
258
  llmFactory={selectedLlmFactory}
259
  ></OllamaModal>
260
+ </section>
261
  );
262
  };
263
 
web/src/utils/commonUtil.ts CHANGED
@@ -1,3 +1,4 @@
 
1
  import isObject from 'lodash/isObject';
2
  import snakeCase from 'lodash/snakeCase';
3
 
@@ -33,3 +34,29 @@ export const formatNumberWithThousandsSeparator = (numberStr: string) => {
33
  const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
34
  return formattedNumber;
35
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IFactory } from '@/interfaces/database/llm';
2
  import isObject from 'lodash/isObject';
3
  import snakeCase from 'lodash/snakeCase';
4
 
 
34
  const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
35
  return formattedNumber;
36
  };
37
+
38
+ const orderFactoryList = [
39
+ 'OpenAI',
40
+ 'Moonshot',
41
+ 'ZHIPU-AI',
42
+ 'Ollama',
43
+ 'Xinference',
44
+ ];
45
+
46
+ export const sortLLmFactoryListBySpecifiedOrder = (list: IFactory[]) => {
47
+ const finalList: IFactory[] = [];
48
+ orderFactoryList.forEach((orderItem) => {
49
+ const index = list.findIndex((item) => item.name === orderItem);
50
+ if (index !== -1) {
51
+ finalList.push(list[index]);
52
+ }
53
+ });
54
+
55
+ list.forEach((item) => {
56
+ if (finalList.every((x) => x.name !== item.name)) {
57
+ finalList.push(item);
58
+ }
59
+ });
60
+
61
+ return finalList;
62
+ };
web/src/utils/documentUtils.ts CHANGED
@@ -42,3 +42,7 @@ export const getExtension = (name: string) =>
42
  export const isPdf = (name: string) => {
43
  return getExtension(name) === 'pdf';
44
  };
 
 
 
 
 
42
  export const isPdf = (name: string) => {
43
  return getExtension(name) === 'pdf';
44
  };
45
+
46
+ export const getUnSupportedFilesCount = (message: string) => {
47
+ return message.split('\n').length;
48
+ };