balibabu commited on
Commit
e55650e
·
1 Parent(s): 7043f18

fix: disable sending messages if both application and conversation are empty and add loading to all pages (#134)

Browse files

* feat: add loading to all pages

* fix: disable sending messages if both application and conversation are empty

* feat: add chatSpin class to Spin of chat

web/src/components/rename-modal/index.tsx CHANGED
@@ -41,8 +41,10 @@ const RenameModal = ({
41
  };
42
 
43
  useEffect(() => {
44
- form.setFieldValue('name', initialName);
45
- }, [initialName, form]);
 
 
46
 
47
  return (
48
  <Modal
 
41
  };
42
 
43
  useEffect(() => {
44
+ if (visible) {
45
+ form.setFieldValue('name', initialName);
46
+ }
47
+ }, [initialName, form, visible]);
48
 
49
  return (
50
  <Modal
web/src/hooks/chunkHooks.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+ import { useDispatch } from 'umi';
3
+ import { useGetKnowledgeSearchParams } from './routeHook';
4
+
5
+ interface PayloadType {
6
+ doc_id: string;
7
+ keywords?: string;
8
+ }
9
+
10
+ export const useFetchChunkList = () => {
11
+ const dispatch = useDispatch();
12
+ const { documentId } = useGetKnowledgeSearchParams();
13
+
14
+ const fetchChunkList = useCallback(() => {
15
+ dispatch({
16
+ type: 'chunkModel/chunk_list',
17
+ payload: {
18
+ doc_id: documentId,
19
+ },
20
+ });
21
+ }, [dispatch, documentId]);
22
+
23
+ return fetchChunkList;
24
+ };
web/src/hooks/knowledgeHook.ts CHANGED
@@ -1,8 +1,9 @@
1
  import showDeleteConfirm from '@/components/deleting-confirm';
2
- import { KnowledgeSearchParams } from '@/constants/knowledge';
3
  import { IKnowledge } from '@/interfaces/database/knowledge';
4
  import { useCallback, useEffect, useMemo } from 'react';
5
  import { useDispatch, useSearchParams, useSelector } from 'umi';
 
 
6
 
7
  export const useKnowledgeBaseId = (): string => {
8
  const [searchParams] = useSearchParams();
@@ -11,17 +12,6 @@ export const useKnowledgeBaseId = (): string => {
11
  return knowledgeBaseId || '';
12
  };
13
 
14
- export const useGetKnowledgeSearchParams = () => {
15
- const [currentQueryParameters] = useSearchParams();
16
-
17
- return {
18
- documentId:
19
- currentQueryParameters.get(KnowledgeSearchParams.DocumentId) || '',
20
- knowledgeId:
21
- currentQueryParameters.get(KnowledgeSearchParams.KnowledgeId) || '',
22
- };
23
- };
24
-
25
  export const useDeleteDocumentById = (): {
26
  removeDocument: (documentId: string) => Promise<number>;
27
  } => {
@@ -135,8 +125,9 @@ export const useFetchKnowledgeBaseConfiguration = () => {
135
 
136
  export const useFetchKnowledgeList = (
137
  shouldFilterListWithoutDocument: boolean = false,
138
- ): IKnowledge[] => {
139
  const dispatch = useDispatch();
 
140
 
141
  const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
142
  const { data = [] } = knowledgeModel;
@@ -156,7 +147,7 @@ export const useFetchKnowledgeList = (
156
  fetchList();
157
  }, [fetchList]);
158
 
159
- return list;
160
  };
161
 
162
  export const useSelectFileThumbnails = () => {
@@ -189,3 +180,29 @@ export const useFetchFileThumbnails = (docIds?: Array<string>) => {
189
 
190
  return { fileThumbnails, fetchFileThumbnails };
191
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import showDeleteConfirm from '@/components/deleting-confirm';
 
2
  import { IKnowledge } from '@/interfaces/database/knowledge';
3
  import { useCallback, useEffect, useMemo } from 'react';
4
  import { useDispatch, useSearchParams, useSelector } from 'umi';
5
+ import { useGetKnowledgeSearchParams } from './routeHook';
6
+ import { useOneNamespaceEffectsLoading } from './storeHooks';
7
 
8
  export const useKnowledgeBaseId = (): string => {
9
  const [searchParams] = useSearchParams();
 
12
  return knowledgeBaseId || '';
13
  };
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  export const useDeleteDocumentById = (): {
16
  removeDocument: (documentId: string) => Promise<number>;
17
  } => {
 
125
 
126
  export const useFetchKnowledgeList = (
127
  shouldFilterListWithoutDocument: boolean = false,
128
+ ): { list: IKnowledge[]; loading: boolean } => {
129
  const dispatch = useDispatch();
130
+ const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']);
131
 
132
  const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
133
  const { data = [] } = knowledgeModel;
 
147
  fetchList();
148
  }, [fetchList]);
149
 
150
+ return { list, loading };
151
  };
152
 
153
  export const useSelectFileThumbnails = () => {
 
180
 
181
  return { fileThumbnails, fetchFileThumbnails };
182
  };
183
+
184
+ //#region knowledge configuration
185
+
186
+ export const useUpdateKnowledge = () => {
187
+ const dispatch = useDispatch();
188
+
189
+ const saveKnowledgeConfiguration = useCallback(
190
+ (payload: any) => {
191
+ dispatch({
192
+ type: 'kSModel/updateKb',
193
+ payload,
194
+ });
195
+ },
196
+ [dispatch],
197
+ );
198
+
199
+ return saveKnowledgeConfiguration;
200
+ };
201
+
202
+ export const useSelectKnowledgeDetails = () => {
203
+ const knowledgeDetails: IKnowledge = useSelector(
204
+ (state: any) => state.kSModel.knowledgeDetails,
205
+ );
206
+ return knowledgeDetails;
207
+ };
208
+ //#endregion
web/src/hooks/llmHooks.ts CHANGED
@@ -8,8 +8,8 @@ import { useCallback, useEffect, useMemo } from 'react';
8
  import { useDispatch, useSelector } from 'umi';
9
 
10
  export const useFetchLlmList = (
11
- isOnMountFetching: boolean = true,
12
  modelType?: LlmModelType,
 
13
  ) => {
14
  const dispatch = useDispatch();
15
 
 
8
  import { useDispatch, useSelector } from 'umi';
9
 
10
  export const useFetchLlmList = (
 
11
  modelType?: LlmModelType,
12
+ isOnMountFetching: boolean = true,
13
  ) => {
14
  const dispatch = useDispatch();
15
 
web/src/hooks/routeHook.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { useLocation } from 'umi';
 
2
 
3
  export enum SegmentIndex {
4
  Second = '2',
@@ -19,3 +20,14 @@ export const useSecondPathName = () => {
19
  export const useThirdPathName = () => {
20
  return useSegmentedPathName(SegmentIndex.Third);
21
  };
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KnowledgeSearchParams } from '@/constants/knowledge';
2
+ import { useLocation, useSearchParams } from 'umi';
3
 
4
  export enum SegmentIndex {
5
  Second = '2',
 
20
  export const useThirdPathName = () => {
21
  return useSegmentedPathName(SegmentIndex.Third);
22
  };
23
+
24
+ export const useGetKnowledgeSearchParams = () => {
25
+ const [currentQueryParameters] = useSearchParams();
26
+
27
+ return {
28
+ documentId:
29
+ currentQueryParameters.get(KnowledgeSearchParams.DocumentId) || '',
30
+ knowledgeId:
31
+ currentQueryParameters.get(KnowledgeSearchParams.KnowledgeId) || '',
32
+ };
33
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { useGetKnowledgeSearchParams } from '@/hooks/knowledgeHook';
2
  import { api_host } from '@/utils/api';
3
  import { useSize } from 'ahooks';
4
  import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
 
1
+ import { useGetKnowledgeSearchParams } from '@/hooks/routeHook';
2
  import { api_host } from '@/utils/api';
3
  import { useSize } from 'ahooks';
4
  import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts CHANGED
@@ -1,3 +1,4 @@
 
1
  import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
2
  import { buildChunkHighlights } from '@/utils/documentUtils';
3
  import { useCallback, useMemo, useState } from 'react';
@@ -46,3 +47,11 @@ export const useGetChunkHighlights = (
46
 
47
  return highlights;
48
  };
 
 
 
 
 
 
 
 
 
1
+ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
2
  import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
3
  import { buildChunkHighlights } from '@/utils/documentUtils';
4
  import { useCallback, useMemo, useState } from 'react';
 
47
 
48
  return highlights;
49
  };
50
+
51
+ export const useSelectChunkListLoading = () => {
52
+ return useOneNamespaceEffectsLoading('chunkModel', [
53
+ 'create_hunk',
54
+ 'chunk_list',
55
+ 'switch_chunk',
56
+ ]);
57
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx CHANGED
@@ -1,23 +1,22 @@
 
1
  import { useDeleteChunkByIds } from '@/hooks/knowledgeHook';
2
- import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil';
3
  import type { PaginationProps } from 'antd';
4
  import { Divider, Flex, Pagination, Space, Spin, message } from 'antd';
 
5
  import { useCallback, useEffect, useState } from 'react';
6
  import { useDispatch, useSearchParams, useSelector } from 'umi';
7
  import ChunkCard from './components/chunk-card';
8
  import CreatingModal from './components/chunk-creating-modal';
9
  import ChunkToolBar from './components/chunk-toolbar';
10
- // import DocumentPreview from './components/document-preview';
11
- import classNames from 'classnames';
12
  import DocumentPreview from './components/document-preview/preview';
13
- import { useHandleChunkCardClick, useSelectDocumentInfo } from './hooks';
 
 
 
 
14
  import { ChunkModelState } from './model';
15
 
16
  import styles from './index.less';
17
- interface PayloadType {
18
- doc_id: string;
19
- keywords?: string;
20
- }
21
 
22
  const Chunk = () => {
23
  const dispatch = useDispatch();
@@ -27,12 +26,7 @@ const Chunk = () => {
27
  const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
28
  const [searchParams] = useSearchParams();
29
  const { data = [], total, pagination } = chunkModel;
30
- const effects = useSelector((state: any) => state.loading.effects);
31
- const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [
32
- 'create_hunk',
33
- 'chunk_list',
34
- 'switch_chunk',
35
- ]);
36
  const documentId: string = searchParams.get('doc_id') || '';
37
  const [chunkId, setChunkId] = useState<string | undefined>();
38
  const { removeChunk } = useDeleteChunkByIds();
@@ -40,18 +34,7 @@ const Chunk = () => {
40
  const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
41
  const isPdf = documentInfo.type === 'pdf';
42
 
43
- const getChunkList = useCallback(() => {
44
- const payload: PayloadType = {
45
- doc_id: documentId,
46
- };
47
-
48
- dispatch({
49
- type: 'chunkModel/chunk_list',
50
- payload: {
51
- ...payload,
52
- },
53
- });
54
- }, [dispatch, documentId]);
55
 
56
  const handleEditChunk = useCallback(
57
  (chunk_id?: string) => {
@@ -169,8 +152,8 @@ const Chunk = () => {
169
  vertical
170
  className={isPdf ? styles.pagePdfWrapper : styles.pageWrapper}
171
  >
172
- <div className={styles.pageContent}>
173
- <Spin spinning={loading} className={styles.spin} size="large">
174
  <Space
175
  direction="vertical"
176
  size={'middle'}
@@ -193,8 +176,8 @@ const Chunk = () => {
193
  ></ChunkCard>
194
  ))}
195
  </Space>
196
- </Spin>
197
- </div>
198
  <div className={styles.pageFooter}>
199
  <Pagination
200
  responsive
 
1
+ import { useFetchChunkList } from '@/hooks/chunkHooks';
2
  import { useDeleteChunkByIds } from '@/hooks/knowledgeHook';
 
3
  import type { PaginationProps } from 'antd';
4
  import { Divider, Flex, Pagination, Space, Spin, message } from 'antd';
5
+ import classNames from 'classnames';
6
  import { useCallback, useEffect, useState } from 'react';
7
  import { useDispatch, useSearchParams, useSelector } from 'umi';
8
  import ChunkCard from './components/chunk-card';
9
  import CreatingModal from './components/chunk-creating-modal';
10
  import ChunkToolBar from './components/chunk-toolbar';
 
 
11
  import DocumentPreview from './components/document-preview/preview';
12
+ import {
13
+ useHandleChunkCardClick,
14
+ useSelectChunkListLoading,
15
+ useSelectDocumentInfo,
16
+ } from './hooks';
17
  import { ChunkModelState } from './model';
18
 
19
  import styles from './index.less';
 
 
 
 
20
 
21
  const Chunk = () => {
22
  const dispatch = useDispatch();
 
26
  const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
27
  const [searchParams] = useSearchParams();
28
  const { data = [], total, pagination } = chunkModel;
29
+ const loading = useSelectChunkListLoading();
 
 
 
 
 
30
  const documentId: string = searchParams.get('doc_id') || '';
31
  const [chunkId, setChunkId] = useState<string | undefined>();
32
  const { removeChunk } = useDeleteChunkByIds();
 
34
  const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
35
  const isPdf = documentInfo.type === 'pdf';
36
 
37
+ const getChunkList = useFetchChunkList();
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  const handleEditChunk = useCallback(
40
  (chunk_id?: string) => {
 
152
  vertical
153
  className={isPdf ? styles.pagePdfWrapper : styles.pageWrapper}
154
  >
155
+ <Spin spinning={loading} className={styles.spin} size="large">
156
+ <div className={styles.pageContent}>
157
  <Space
158
  direction="vertical"
159
  size={'middle'}
 
176
  ></ChunkCard>
177
  ))}
178
  </Space>
179
+ </div>
180
+ </Spin>
181
  <div className={styles.pageFooter}>
182
  <Pagination
183
  responsive
web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx CHANGED
@@ -52,8 +52,10 @@ const RenameModal = () => {
52
  };
53
 
54
  useEffect(() => {
55
- form.setFieldValue('name', initialName);
56
- }, [initialName, documentId, form]);
 
 
57
 
58
  return (
59
  <Modal
 
52
  };
53
 
54
  useEffect(() => {
55
+ if (isModalOpen) {
56
+ form.setFieldValue('name', initialName);
57
+ }
58
+ }, [initialName, documentId, form, isModalOpen]);
59
 
60
  return (
61
  <Modal
web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx CHANGED
@@ -1,19 +1,4 @@
1
- import {
2
- useFetchKnowledgeBaseConfiguration,
3
- useKnowledgeBaseId,
4
- } from '@/hooks/knowledgeHook';
5
- import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
6
- import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
7
- import {
8
- useFetchTenantInfo,
9
- useSelectParserList,
10
- } from '@/hooks/userSettingHook';
11
- import { IKnowledge } from '@/interfaces/database/knowledge';
12
- import {
13
- getBase64FromUploadFileList,
14
- getUploadFileListFromBase64,
15
- normFile,
16
- } from '@/utils/fileUtil';
17
  import { PlusOutlined } from '@ant-design/icons';
18
  import {
19
  Button,
@@ -26,14 +11,14 @@ import {
26
  Select,
27
  Slider,
28
  Space,
 
29
  Typography,
30
  Upload,
31
- UploadFile,
32
  } from 'antd';
33
- import pick from 'lodash/pick';
34
- import { useEffect } from 'react';
35
- import { useDispatch, useSelector } from 'umi';
36
- import { LlmModelType } from '../../constant';
37
 
38
  import styles from './index.less';
39
 
@@ -41,205 +26,165 @@ const { Title } = Typography;
41
  const { Option } = Select;
42
 
43
  const Configuration = () => {
44
- const [form] = Form.useForm();
45
- const dispatch = useDispatch();
46
- const knowledgeBaseId = useKnowledgeBaseId();
47
- const loading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']);
48
-
49
- const knowledgeDetails: IKnowledge = useSelector(
50
- (state: any) => state.kSModel.knowledgeDetails,
51
- );
52
-
53
- const parserList = useSelectParserList();
54
-
55
- const embeddingModelOptions = useSelectLlmOptions();
56
-
57
- const onFinish = async (values: any) => {
58
- const avatar = await getBase64FromUploadFileList(values.avatar);
59
- dispatch({
60
- type: 'kSModel/updateKb',
61
- payload: {
62
- ...values,
63
- avatar,
64
- kb_id: knowledgeBaseId,
65
- },
66
- });
67
- };
68
 
69
  const onFinishFailed = (errorInfo: any) => {
70
  console.log('Failed:', errorInfo);
71
  };
72
 
73
- useEffect(() => {
74
- const fileList: UploadFile[] = getUploadFileListFromBase64(
75
- knowledgeDetails.avatar,
76
- );
77
-
78
- form.setFieldsValue({
79
- ...pick(knowledgeDetails, [
80
- 'description',
81
- 'name',
82
- 'permission',
83
- 'embd_id',
84
- 'parser_id',
85
- 'language',
86
- 'parser_config.chunk_token_num',
87
- ]),
88
- avatar: fileList,
89
- });
90
- }, [form, knowledgeDetails]);
91
-
92
- useFetchTenantInfo();
93
- useFetchKnowledgeBaseConfiguration();
94
-
95
- useFetchLlmList(LlmModelType.Embedding);
96
-
97
  return (
98
  <div className={styles.configurationWrapper}>
99
  <Title level={5}>Configuration</Title>
100
  <p>Update your knowledge base details especially parsing method here.</p>
101
  <Divider></Divider>
102
- <Form
103
- form={form}
104
- name="validateOnly"
105
- layout="vertical"
106
- autoComplete="off"
107
- onFinish={onFinish}
108
- onFinishFailed={onFinishFailed}
109
- >
110
- <Form.Item
111
- name="name"
112
- label="Knowledge base name"
113
- rules={[{ required: true }]}
114
- >
115
- <Input />
116
- </Form.Item>
117
- <Form.Item
118
- name="avatar"
119
- label="Knowledge base photo"
120
- valuePropName="fileList"
121
- getValueFromEvent={normFile}
122
  >
123
- <Upload
124
- listType="picture-card"
125
- maxCount={1}
126
- beforeUpload={() => false}
127
- showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
128
  >
129
- <button style={{ border: 0, background: 'none' }} type="button">
130
- <PlusOutlined />
131
- <div style={{ marginTop: 8 }}>Upload</div>
132
- </button>
133
- </Upload>
134
- </Form.Item>
135
- <Form.Item name="description" label="Description">
136
- <Input />
137
- </Form.Item>
138
- <Form.Item
139
- label="Language"
140
- name="language"
141
- initialValue={'Chinese'}
142
- rules={[{ required: true, message: 'Please input your language!' }]}
143
- >
144
- <Select placeholder="select your language">
145
- <Option value="English">English</Option>
146
- <Option value="Chinese">Chinese</Option>
147
- </Select>
148
- </Form.Item>
149
- <Form.Item
150
- name="permission"
151
- label="Permissions"
152
- rules={[{ required: true }]}
153
- >
154
- <Radio.Group>
155
- <Radio value="me">Only me</Radio>
156
- <Radio value="team">Team</Radio>
157
- </Radio.Group>
158
- </Form.Item>
159
- <Form.Item
160
- name="embd_id"
161
- label="Embedding Model"
162
- rules={[{ required: true }]}
163
- tooltip="xx"
164
- >
165
- <Select
166
- placeholder="Please select a country"
167
- options={embeddingModelOptions}
168
- ></Select>
169
- </Form.Item>
170
- <Form.Item
171
- name="parser_id"
172
- label="Knowledge base category"
173
- tooltip="xx"
174
- rules={[{ required: true }]}
175
- >
176
- <Select placeholder="Please select a country">
177
- {parserList.map((x) => (
178
- <Option value={x.value} key={x.value}>
179
- {x.label}
180
- </Option>
181
- ))}
182
- </Select>
183
- </Form.Item>
184
- <Form.Item noStyle dependencies={['parser_id']}>
185
- {({ getFieldValue }) => {
186
- const parserId = getFieldValue('parser_id');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- if (parserId === 'naive') {
189
- return (
190
- <Form.Item label="Chunk token number" tooltip="xxx">
191
- <Flex gap={20} align="center">
192
- <Flex flex={1}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  <Form.Item
194
  name={['parser_config', 'chunk_token_num']}
195
  noStyle
196
- initialValue={128}
197
  rules={[
198
- { required: true, message: 'Province is required' },
199
  ]}
200
  >
201
- <Slider className={styles.variableSlider} max={2048} />
 
 
 
 
202
  </Form.Item>
203
  </Flex>
204
- <Form.Item
205
- name={['parser_config', 'chunk_token_num']}
206
- noStyle
207
- initialValue={128}
208
- rules={[
209
- { required: true, message: 'Street is required' },
210
- ]}
211
- >
212
- <InputNumber
213
- className={styles.sliderInputNumber}
214
- max={2048}
215
- min={0}
216
- />
217
- </Form.Item>
218
- </Flex>
219
- </Form.Item>
220
- );
221
- }
222
- return null;
223
- }}
224
- </Form.Item>
225
- <Form.Item>
226
- <div className={styles.buttonWrapper}>
227
- <Space>
228
- <Button htmlType="reset" size={'middle'}>
229
- Cancel
230
- </Button>
231
- <Button
232
- htmlType="submit"
233
- type="primary"
234
- size={'middle'}
235
- loading={loading}
236
- >
237
- Save
238
- </Button>
239
- </Space>
240
- </div>
241
- </Form.Item>
242
- </Form>
243
  </div>
244
  );
245
  };
 
1
+ import { normFile } from '@/utils/fileUtil';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import { PlusOutlined } from '@ant-design/icons';
3
  import {
4
  Button,
 
11
  Select,
12
  Slider,
13
  Space,
14
+ Spin,
15
  Typography,
16
  Upload,
 
17
  } from 'antd';
18
+ import {
19
+ useFetchKnowledgeConfigurationOnMount,
20
+ useSubmitKnowledgeConfiguration,
21
+ } from './hooks';
22
 
23
  import styles from './index.less';
24
 
 
26
  const { Option } = Select;
27
 
28
  const Configuration = () => {
29
+ const { submitKnowledgeConfiguration, submitLoading } =
30
+ useSubmitKnowledgeConfiguration();
31
+ const { form, parserList, embeddingModelOptions, loading } =
32
+ useFetchKnowledgeConfigurationOnMount();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  const onFinishFailed = (errorInfo: any) => {
35
  console.log('Failed:', errorInfo);
36
  };
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  return (
39
  <div className={styles.configurationWrapper}>
40
  <Title level={5}>Configuration</Title>
41
  <p>Update your knowledge base details especially parsing method here.</p>
42
  <Divider></Divider>
43
+ <Spin spinning={loading}>
44
+ <Form
45
+ form={form}
46
+ name="validateOnly"
47
+ layout="vertical"
48
+ autoComplete="off"
49
+ onFinish={submitKnowledgeConfiguration}
50
+ onFinishFailed={onFinishFailed}
 
 
 
 
 
 
 
 
 
 
 
 
51
  >
52
+ <Form.Item
53
+ name="name"
54
+ label="Knowledge base name"
55
+ rules={[{ required: true }]}
 
56
  >
57
+ <Input />
58
+ </Form.Item>
59
+ <Form.Item
60
+ name="avatar"
61
+ label="Knowledge base photo"
62
+ valuePropName="fileList"
63
+ getValueFromEvent={normFile}
64
+ >
65
+ <Upload
66
+ listType="picture-card"
67
+ maxCount={1}
68
+ beforeUpload={() => false}
69
+ showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
70
+ >
71
+ <button style={{ border: 0, background: 'none' }} type="button">
72
+ <PlusOutlined />
73
+ <div style={{ marginTop: 8 }}>Upload</div>
74
+ </button>
75
+ </Upload>
76
+ </Form.Item>
77
+ <Form.Item name="description" label="Description">
78
+ <Input />
79
+ </Form.Item>
80
+ <Form.Item
81
+ label="Language"
82
+ name="language"
83
+ initialValue={'Chinese'}
84
+ rules={[{ required: true, message: 'Please input your language!' }]}
85
+ >
86
+ <Select placeholder="select your language">
87
+ <Option value="English">English</Option>
88
+ <Option value="Chinese">Chinese</Option>
89
+ </Select>
90
+ </Form.Item>
91
+ <Form.Item
92
+ name="permission"
93
+ label="Permissions"
94
+ rules={[{ required: true }]}
95
+ >
96
+ <Radio.Group>
97
+ <Radio value="me">Only me</Radio>
98
+ <Radio value="team">Team</Radio>
99
+ </Radio.Group>
100
+ </Form.Item>
101
+ <Form.Item
102
+ name="embd_id"
103
+ label="Embedding Model"
104
+ rules={[{ required: true }]}
105
+ tooltip="xx"
106
+ >
107
+ <Select
108
+ placeholder="Please select a country"
109
+ options={embeddingModelOptions}
110
+ ></Select>
111
+ </Form.Item>
112
+ <Form.Item
113
+ name="parser_id"
114
+ label="Knowledge base category"
115
+ tooltip="xx"
116
+ rules={[{ required: true }]}
117
+ >
118
+ <Select placeholder="Please select a country">
119
+ {parserList.map((x) => (
120
+ <Option value={x.value} key={x.value}>
121
+ {x.label}
122
+ </Option>
123
+ ))}
124
+ </Select>
125
+ </Form.Item>
126
+ <Form.Item noStyle dependencies={['parser_id']}>
127
+ {({ getFieldValue }) => {
128
+ const parserId = getFieldValue('parser_id');
129
 
130
+ if (parserId === 'naive') {
131
+ return (
132
+ <Form.Item label="Chunk token number" tooltip="xxx">
133
+ <Flex gap={20} align="center">
134
+ <Flex flex={1}>
135
+ <Form.Item
136
+ name={['parser_config', 'chunk_token_num']}
137
+ noStyle
138
+ initialValue={128}
139
+ rules={[
140
+ { required: true, message: 'Province is required' },
141
+ ]}
142
+ >
143
+ <Slider
144
+ className={styles.variableSlider}
145
+ max={2048}
146
+ />
147
+ </Form.Item>
148
+ </Flex>
149
  <Form.Item
150
  name={['parser_config', 'chunk_token_num']}
151
  noStyle
 
152
  rules={[
153
+ { required: true, message: 'Street is required' },
154
  ]}
155
  >
156
+ <InputNumber
157
+ className={styles.sliderInputNumber}
158
+ max={2048}
159
+ min={0}
160
+ />
161
  </Form.Item>
162
  </Flex>
163
+ </Form.Item>
164
+ );
165
+ }
166
+ return null;
167
+ }}
168
+ </Form.Item>
169
+ <Form.Item>
170
+ <div className={styles.buttonWrapper}>
171
+ <Space>
172
+ <Button htmlType="reset" size={'middle'}>
173
+ Cancel
174
+ </Button>
175
+ <Button
176
+ htmlType="submit"
177
+ type="primary"
178
+ size={'middle'}
179
+ loading={submitLoading}
180
+ >
181
+ Save
182
+ </Button>
183
+ </Space>
184
+ </div>
185
+ </Form.Item>
186
+ </Form>
187
+ </Spin>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  </div>
189
  );
190
  };
web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ useFetchKnowledgeBaseConfiguration,
3
+ useKnowledgeBaseId,
4
+ useSelectKnowledgeDetails,
5
+ useUpdateKnowledge,
6
+ } from '@/hooks/knowledgeHook';
7
+ import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
8
+ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
9
+ import {
10
+ useFetchTenantInfo,
11
+ useSelectParserList,
12
+ } from '@/hooks/userSettingHook';
13
+ import {
14
+ getBase64FromUploadFileList,
15
+ getUploadFileListFromBase64,
16
+ } from '@/utils/fileUtil';
17
+ import { Form, UploadFile } from 'antd';
18
+ import pick from 'lodash/pick';
19
+ import { useCallback, useEffect } from 'react';
20
+ import { LlmModelType } from '../../constant';
21
+
22
+ export const useSubmitKnowledgeConfiguration = () => {
23
+ const save = useUpdateKnowledge();
24
+ const knowledgeBaseId = useKnowledgeBaseId();
25
+ const submitLoading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']);
26
+
27
+ const submitKnowledgeConfiguration = useCallback(
28
+ async (values: any) => {
29
+ const avatar = await getBase64FromUploadFileList(values.avatar);
30
+ save({
31
+ ...values,
32
+ avatar,
33
+ kb_id: knowledgeBaseId,
34
+ });
35
+ },
36
+ [save, knowledgeBaseId],
37
+ );
38
+
39
+ return { submitKnowledgeConfiguration, submitLoading };
40
+ };
41
+
42
+ export const useFetchKnowledgeConfigurationOnMount = () => {
43
+ const [form] = Form.useForm();
44
+ const loading = useOneNamespaceEffectsLoading('kSModel', ['getKbDetail']);
45
+
46
+ const knowledgeDetails = useSelectKnowledgeDetails();
47
+ const parserList = useSelectParserList();
48
+ const embeddingModelOptions = useSelectLlmOptions();
49
+
50
+ useFetchTenantInfo();
51
+ useFetchKnowledgeBaseConfiguration();
52
+ useFetchLlmList(LlmModelType.Embedding);
53
+
54
+ useEffect(() => {
55
+ const fileList: UploadFile[] = getUploadFileListFromBase64(
56
+ knowledgeDetails.avatar,
57
+ );
58
+ form.setFieldsValue({
59
+ ...pick(knowledgeDetails, [
60
+ 'description',
61
+ 'name',
62
+ 'permission',
63
+ 'embd_id',
64
+ 'parser_id',
65
+ 'language',
66
+ 'parser_config.chunk_token_num',
67
+ ]),
68
+ avatar: fileList,
69
+ });
70
+ }, [form, knowledgeDetails]);
71
+
72
+ return { form, parserList, embeddingModelOptions, loading };
73
+ };
web/src/pages/add-knowledge/components/knowledge-setting/model.ts CHANGED
@@ -34,7 +34,7 @@ const model: DvaModel<KSModelState> = {
34
  const { data } = yield call(kbService.createKb, payload);
35
  const { retcode } = data;
36
  if (retcode === 0) {
37
- message.success('Created successfully!');
38
  }
39
  return data;
40
  },
@@ -43,7 +43,7 @@ const model: DvaModel<KSModelState> = {
43
  const { retcode } = data;
44
  if (retcode === 0) {
45
  yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } });
46
- message.success('Updated successfully!');
47
  }
48
  },
49
  *getKbDetail({ payload = {} }, { call, put }) {
 
34
  const { data } = yield call(kbService.createKb, payload);
35
  const { retcode } = data;
36
  if (retcode === 0) {
37
+ message.success('Created!');
38
  }
39
  return data;
40
  },
 
43
  const { retcode } = data;
44
  if (retcode === 0) {
45
  yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } });
46
+ message.success('Updated!');
47
  }
48
  },
49
  *getKbDetail({ payload = {} }, { call, put }) {
web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx CHANGED
@@ -37,9 +37,9 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
37
 
38
  return (
39
  <section className={styles.testingControlWrapper}>
40
- <p>
41
  <b>Retrieval testing</b>
42
- </p>
43
  <p>Final step! After success, leave the rest to Infiniflow AI.</p>
44
  <Divider></Divider>
45
  <section>
@@ -48,8 +48,6 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
48
  layout="vertical"
49
  form={form}
50
  initialValues={{
51
- similarity_threshold: 0.2,
52
- vector_similarity_weight: 0.3,
53
  top_k: 1024,
54
  }}
55
  >
@@ -81,12 +79,12 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
81
  </Form>
82
  </section>
83
  <section>
84
- <p className={styles.historyTitle}>
85
  <Space size={'middle'}>
86
  <HistoryOutlined className={styles.historyIcon} />
87
  <b>Test history</b>
88
  </Space>
89
- </p>
90
  <Space
91
  direction="vertical"
92
  size={'middle'}
 
37
 
38
  return (
39
  <section className={styles.testingControlWrapper}>
40
+ <div>
41
  <b>Retrieval testing</b>
42
+ </div>
43
  <p>Final step! After success, leave the rest to Infiniflow AI.</p>
44
  <Divider></Divider>
45
  <section>
 
48
  layout="vertical"
49
  form={form}
50
  initialValues={{
 
 
51
  top_k: 1024,
52
  }}
53
  >
 
79
  </Form>
80
  </section>
81
  <section>
82
+ <div className={styles.historyTitle}>
83
  <Space size={'middle'}>
84
  <HistoryOutlined className={styles.historyIcon} />
85
  <b>Test history</b>
86
  </Space>
87
+ </div>
88
  <Space
89
  direction="vertical"
90
  size={'middle'}
web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx CHANGED
@@ -1,14 +1,13 @@
 
 
1
  import { Form, Input, Select, Upload } from 'antd';
2
-
3
  import classNames from 'classnames';
4
  import { ISegmentedContentProps } from '../interface';
5
 
6
- import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
7
- import { PlusOutlined } from '@ant-design/icons';
8
  import styles from './index.less';
9
 
10
  const AssistantSetting = ({ show }: ISegmentedContentProps) => {
11
- const knowledgeList = useFetchKnowledgeList(true);
12
  const knowledgeOptions = knowledgeList.map((x) => ({
13
  label: x.name,
14
  value: x.id,
 
1
+ import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
2
+ import { PlusOutlined } from '@ant-design/icons';
3
  import { Form, Input, Select, Upload } from 'antd';
 
4
  import classNames from 'classnames';
5
  import { ISegmentedContentProps } from '../interface';
6
 
 
 
7
  import styles from './index.less';
8
 
9
  const AssistantSetting = ({ show }: ISegmentedContentProps) => {
10
+ const { list: knowledgeList } = useFetchKnowledgeList(true);
11
  const knowledgeOptions = knowledgeList.map((x) => ({
12
  label: x.name,
13
  value: x.id,
web/src/pages/chat/chat-container/index.tsx CHANGED
@@ -30,6 +30,7 @@ import {
30
  useClickDrawer,
31
  useFetchConversationOnMount,
32
  useGetFileIcon,
 
33
  useSendMessage,
34
  } from '../hooks';
35
 
@@ -248,11 +249,15 @@ const ChatContainer = () => {
248
  addNewestConversation,
249
  removeLatestMessage,
250
  } = useFetchConversationOnMount();
251
- const { handleInputChange, handlePressEnter, value, loading } =
252
- useSendMessage(conversation, addNewestConversation, removeLatestMessage);
 
 
 
 
253
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
254
  useClickDrawer();
255
-
256
  useGetFileIcon();
257
 
258
  return (
@@ -284,8 +289,14 @@ const ChatContainer = () => {
284
  size="large"
285
  placeholder="Message Resume Assistant..."
286
  value={value}
 
287
  suffix={
288
- <Button type="primary" onClick={handlePressEnter} loading={loading}>
 
 
 
 
 
289
  Send
290
  </Button>
291
  }
 
30
  useClickDrawer,
31
  useFetchConversationOnMount,
32
  useGetFileIcon,
33
+ useGetSendButtonDisabled,
34
  useSendMessage,
35
  } from '../hooks';
36
 
 
249
  addNewestConversation,
250
  removeLatestMessage,
251
  } = useFetchConversationOnMount();
252
+ const {
253
+ handleInputChange,
254
+ handlePressEnter,
255
+ value,
256
+ loading: sendLoading,
257
+ } = useSendMessage(conversation, addNewestConversation, removeLatestMessage);
258
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
259
  useClickDrawer();
260
+ const disabled = useGetSendButtonDisabled();
261
  useGetFileIcon();
262
 
263
  return (
 
289
  size="large"
290
  placeholder="Message Resume Assistant..."
291
  value={value}
292
+ disabled={disabled}
293
  suffix={
294
+ <Button
295
+ type="primary"
296
+ onClick={handlePressEnter}
297
+ loading={sendLoading}
298
+ disabled={disabled}
299
+ >
300
  Send
301
  </Button>
302
  }
web/src/pages/chat/hooks.ts CHANGED
@@ -767,4 +767,16 @@ export const useClickDrawer = () => {
767
  };
768
  };
769
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  //#endregion
 
767
  };
768
  };
769
 
770
+ export const useSelectDialogListLoading = () => {
771
+ return useOneNamespaceEffectsLoading('chatModel', ['listDialog']);
772
+ };
773
+ export const useSelectConversationListLoading = () => {
774
+ return useOneNamespaceEffectsLoading('chatModel', ['listConversation']);
775
+ };
776
+
777
+ export const useGetSendButtonDisabled = () => {
778
+ const { dialogId, conversationId } = useGetChatSearchParams();
779
+
780
+ return dialogId === '' && conversationId === '';
781
+ };
782
  //#endregion
web/src/pages/chat/index.less CHANGED
@@ -41,6 +41,14 @@
41
  overflow: auto;
42
  }
43
 
 
 
 
 
 
 
 
 
44
  .chatTitleCard {
45
  :global(.ant-card-body) {
46
  padding: 8px;
 
41
  overflow: auto;
42
  }
43
 
44
+ .chatSpin {
45
+ :global(.ant-spin-container) {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 10px;
49
+ }
50
+ }
51
+
52
  .chatTitleCard {
53
  :global(.ant-card-body) {
54
  padding: 8px;
web/src/pages/chat/index.tsx CHANGED
@@ -1,5 +1,4 @@
1
  import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
2
- import { useSetModalState } from '@/hooks/commonHooks';
3
  import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
4
  import {
5
  Avatar,
@@ -10,6 +9,7 @@ import {
10
  Flex,
11
  MenuProps,
12
  Space,
 
13
  Tag,
14
  } from 'antd';
15
  import { MenuItemProps } from 'antd/lib/menu/MenuItem';
@@ -29,8 +29,9 @@ import {
29
  useRemoveDialog,
30
  useRenameConversation,
31
  useSelectConversationList,
 
 
32
  useSelectFirstDialogOnMount,
33
- useSetCurrentDialog,
34
  } from './hooks';
35
 
36
  import RenameModal from '@/components/rename-modal';
@@ -38,8 +39,6 @@ import styles from './index.less';
38
 
39
  const Chat = () => {
40
  const dialogList = useSelectFirstDialogOnMount();
41
- const { visible, hideModal, showModal } = useSetModalState();
42
- const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
43
  const { onRemoveDialog } = useRemoveDialog();
44
  const { onRemoveConversation } = useRemoveConversation();
45
  const { handleClickDialog } = useClickDialogCard();
@@ -70,6 +69,8 @@ const Chat = () => {
70
  hideDialogEditModal,
71
  showDialogEditModal,
72
  } = useEditDialog();
 
 
73
 
74
  useFetchDialogOnMount(dialogId, true);
75
 
@@ -204,35 +205,39 @@ const Chat = () => {
204
  </Button>
205
  <Divider></Divider>
206
  <Flex className={styles.chatAppContent} vertical gap={10}>
207
- {dialogList.map((x) => (
208
- <Card
209
- key={x.id}
210
- hoverable
211
- className={classNames(styles.chatAppCard, {
212
- [styles.chatAppCardSelected]: dialogId === x.id,
213
- })}
214
- onMouseEnter={handleAppCardEnter(x.id)}
215
- onMouseLeave={handleItemLeave}
216
- onClick={handleDialogCardClick(x.id)}
217
- >
218
- <Flex justify="space-between" align="center">
219
- <Space size={15}>
220
- <Avatar src={x.icon} shape={'square'} />
221
- <section>
222
- <b>{x.name}</b>
223
- <div>{x.description}</div>
224
- </section>
225
- </Space>
226
- {activated === x.id && (
227
- <section>
228
- <Dropdown menu={{ items: buildAppItems(x.id) }}>
229
- <ChatAppCube className={styles.cubeIcon}></ChatAppCube>
230
- </Dropdown>
231
- </section>
232
- )}
233
- </Flex>
234
- </Card>
235
- ))}
 
 
 
 
236
  </Flex>
237
  </Flex>
238
  </Flex>
@@ -254,29 +259,38 @@ const Chat = () => {
254
  </Flex>
255
  <Divider></Divider>
256
  <Flex vertical gap={10} className={styles.chatTitleContent}>
257
- {conversationList.map((x) => (
258
- <Card
259
- key={x.id}
260
- hoverable
261
- onClick={handleConversationCardClick(x.id)}
262
- onMouseEnter={handleConversationCardEnter(x.id)}
263
- onMouseLeave={handleConversationItemLeave}
264
- className={classNames(styles.chatTitleCard, {
265
- [styles.chatTitleCardSelected]: x.id === conversationId,
266
- })}
267
- >
268
- <Flex justify="space-between" align="center">
269
- <div>{x.name}</div>
270
- {conversationActivated === x.id && x.id !== '' && (
271
- <section>
272
- <Dropdown menu={{ items: buildConversationItems(x.id) }}>
273
- <ChatAppCube className={styles.cubeIcon}></ChatAppCube>
274
- </Dropdown>
275
- </section>
276
- )}
277
- </Flex>
278
- </Card>
279
- ))}
 
 
 
 
 
 
 
 
 
280
  </Flex>
281
  </Flex>
282
  </Flex>
 
1
  import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
 
2
  import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
3
  import {
4
  Avatar,
 
9
  Flex,
10
  MenuProps,
11
  Space,
12
+ Spin,
13
  Tag,
14
  } from 'antd';
15
  import { MenuItemProps } from 'antd/lib/menu/MenuItem';
 
29
  useRemoveDialog,
30
  useRenameConversation,
31
  useSelectConversationList,
32
+ useSelectConversationListLoading,
33
+ useSelectDialogListLoading,
34
  useSelectFirstDialogOnMount,
 
35
  } from './hooks';
36
 
37
  import RenameModal from '@/components/rename-modal';
 
39
 
40
  const Chat = () => {
41
  const dialogList = useSelectFirstDialogOnMount();
 
 
42
  const { onRemoveDialog } = useRemoveDialog();
43
  const { onRemoveConversation } = useRemoveConversation();
44
  const { handleClickDialog } = useClickDialogCard();
 
69
  hideDialogEditModal,
70
  showDialogEditModal,
71
  } = useEditDialog();
72
+ const dialogLoading = useSelectDialogListLoading();
73
+ const conversationLoading = useSelectConversationListLoading();
74
 
75
  useFetchDialogOnMount(dialogId, true);
76
 
 
205
  </Button>
206
  <Divider></Divider>
207
  <Flex className={styles.chatAppContent} vertical gap={10}>
208
+ <Spin spinning={dialogLoading} wrapperClassName={styles.chatSpin}>
209
+ {dialogList.map((x) => (
210
+ <Card
211
+ key={x.id}
212
+ hoverable
213
+ className={classNames(styles.chatAppCard, {
214
+ [styles.chatAppCardSelected]: dialogId === x.id,
215
+ })}
216
+ onMouseEnter={handleAppCardEnter(x.id)}
217
+ onMouseLeave={handleItemLeave}
218
+ onClick={handleDialogCardClick(x.id)}
219
+ >
220
+ <Flex justify="space-between" align="center">
221
+ <Space size={15}>
222
+ <Avatar src={x.icon} shape={'square'} />
223
+ <section>
224
+ <b>{x.name}</b>
225
+ <div>{x.description}</div>
226
+ </section>
227
+ </Space>
228
+ {activated === x.id && (
229
+ <section>
230
+ <Dropdown menu={{ items: buildAppItems(x.id) }}>
231
+ <ChatAppCube
232
+ className={styles.cubeIcon}
233
+ ></ChatAppCube>
234
+ </Dropdown>
235
+ </section>
236
+ )}
237
+ </Flex>
238
+ </Card>
239
+ ))}
240
+ </Spin>
241
  </Flex>
242
  </Flex>
243
  </Flex>
 
259
  </Flex>
260
  <Divider></Divider>
261
  <Flex vertical gap={10} className={styles.chatTitleContent}>
262
+ <Spin
263
+ spinning={conversationLoading}
264
+ wrapperClassName={styles.chatSpin}
265
+ >
266
+ {conversationList.map((x) => (
267
+ <Card
268
+ key={x.id}
269
+ hoverable
270
+ onClick={handleConversationCardClick(x.id)}
271
+ onMouseEnter={handleConversationCardEnter(x.id)}
272
+ onMouseLeave={handleConversationItemLeave}
273
+ className={classNames(styles.chatTitleCard, {
274
+ [styles.chatTitleCardSelected]: x.id === conversationId,
275
+ })}
276
+ >
277
+ <Flex justify="space-between" align="center">
278
+ <div>{x.name}</div>
279
+ {conversationActivated === x.id && x.id !== '' && (
280
+ <section>
281
+ <Dropdown
282
+ menu={{ items: buildConversationItems(x.id) }}
283
+ >
284
+ <ChatAppCube
285
+ className={styles.cubeIcon}
286
+ ></ChatAppCube>
287
+ </Dropdown>
288
+ </section>
289
+ )}
290
+ </Flex>
291
+ </Card>
292
+ ))}
293
+ </Spin>
294
  </Flex>
295
  </Flex>
296
  </Flex>
web/src/pages/knowledge/index.tsx CHANGED
@@ -1,16 +1,16 @@
1
  import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
2
  import ModalManager from '@/components/modal-manager';
 
 
3
  import { PlusOutlined } from '@ant-design/icons';
4
- import { Button, Empty, Flex, Space } from 'antd';
5
  import KnowledgeCard from './knowledge-card';
6
  import KnowledgeCreatingModal from './knowledge-creating-modal';
7
 
8
- import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
9
- import { useSelectUserInfo } from '@/hooks/userSettingHook';
10
  import styles from './index.less';
11
 
12
  const Knowledge = () => {
13
- const list = useFetchKnowledgeList();
14
  const userInfo = useSelectUserInfo();
15
 
16
  return (
@@ -50,15 +50,23 @@ const Knowledge = () => {
50
  </ModalManager>
51
  </Space>
52
  </div>
53
- <Flex gap={'large'} wrap="wrap" className={styles.knowledgeCardContainer}>
54
- {list.length > 0 ? (
55
- list.map((item: any) => {
56
- return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
57
- })
58
- ) : (
59
- <Empty></Empty>
60
- )}
61
- </Flex>
 
 
 
 
 
 
 
 
62
  </Flex>
63
  );
64
  };
 
1
  import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
2
  import ModalManager from '@/components/modal-manager';
3
+ import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
4
+ import { useSelectUserInfo } from '@/hooks/userSettingHook';
5
  import { PlusOutlined } from '@ant-design/icons';
6
+ import { Button, Empty, Flex, Space, Spin } from 'antd';
7
  import KnowledgeCard from './knowledge-card';
8
  import KnowledgeCreatingModal from './knowledge-creating-modal';
9
 
 
 
10
  import styles from './index.less';
11
 
12
  const Knowledge = () => {
13
+ const { list, loading } = useFetchKnowledgeList();
14
  const userInfo = useSelectUserInfo();
15
 
16
  return (
 
50
  </ModalManager>
51
  </Space>
52
  </div>
53
+ <Spin spinning={loading}>
54
+ <Flex
55
+ gap={'large'}
56
+ wrap="wrap"
57
+ className={styles.knowledgeCardContainer}
58
+ >
59
+ {list.length > 0 ? (
60
+ list.map((item: any) => {
61
+ return (
62
+ <KnowledgeCard item={item} key={item.name}></KnowledgeCard>
63
+ );
64
+ })
65
+ ) : (
66
+ <Empty></Empty>
67
+ )}
68
+ </Flex>
69
+ </Spin>
70
  </Flex>
71
  );
72
  };
web/src/pages/user-setting/hooks.ts CHANGED
@@ -19,5 +19,8 @@ export const useValidateSubmittable = () => {
19
  return { submittable, form };
20
  };
21
 
22
- export const useGetUserInfoLoading = () =>
23
  useOneNamespaceEffectsLoading('settingModel', ['setting']);
 
 
 
 
19
  return { submittable, form };
20
  };
21
 
22
+ export const useSelectSubmitUserInfoLoading = () =>
23
  useOneNamespaceEffectsLoading('settingModel', ['setting']);
24
+
25
+ export const useSelectUserInfoLoading = () =>
26
+ useOneNamespaceEffectsLoading('settingModel', ['getUserInfo']);
web/src/pages/user-setting/setting-model/hooks.ts CHANGED
@@ -113,3 +113,12 @@ export const useFetchSystemModelSettingOnMount = (visible: boolean) => {
113
 
114
  return { systemSetting, allOptions };
115
  };
 
 
 
 
 
 
 
 
 
 
113
 
114
  return { systemSetting, allOptions };
115
  };
116
+
117
+ export const useSelectModelProvidersLoading = () => {
118
+ const loading = useOneNamespaceEffectsLoading('settingModel', [
119
+ 'my_llm',
120
+ 'factories_list',
121
+ ]);
122
+
123
+ return loading;
124
+ };
web/src/pages/user-setting/setting-model/index.tsx CHANGED
@@ -23,12 +23,17 @@ import {
23
  List,
24
  Row,
25
  Space,
 
26
  Typography,
27
  } from 'antd';
28
  import { useCallback } from 'react';
29
  import SettingTitle from '../components/setting-title';
30
  import ApiKeyModal from './api-key-modal';
31
- import { useSubmitApiKey, useSubmitSystemModelSetting } from './hooks';
 
 
 
 
32
  import SystemModelSettingModal from './system-model-setting-modal';
33
 
34
  import styles from './index.less';
@@ -111,6 +116,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
111
  const UserSettingModel = () => {
112
  const factoryList = useFetchLlmFactoryListOnMount();
113
  const llmList = useFetchMyLlmListOnMount();
 
114
  const {
115
  saveApiKeyLoading,
116
  initialApiKey,
@@ -191,16 +197,18 @@ const UserSettingModel = () => {
191
 
192
  return (
193
  <>
194
- <section className={styles.modelWrapper}>
195
- <SettingTitle
196
- title="Model Setting"
197
- description="Manage your account settings and preferences here."
198
- showRightButton
199
- clickButton={showSystemSettingModal}
200
- ></SettingTitle>
201
- <Divider></Divider>
202
- <Collapse defaultActiveKey={['1']} ghost items={items} />
203
- </section>
 
 
204
  <ApiKeyModal
205
  visible={apiKeyVisible}
206
  hideModal={hideApiKeyModal}
 
23
  List,
24
  Row,
25
  Space,
26
+ Spin,
27
  Typography,
28
  } from 'antd';
29
  import { useCallback } from 'react';
30
  import SettingTitle from '../components/setting-title';
31
  import ApiKeyModal from './api-key-modal';
32
+ import {
33
+ useSelectModelProvidersLoading,
34
+ useSubmitApiKey,
35
+ useSubmitSystemModelSetting,
36
+ } from './hooks';
37
  import SystemModelSettingModal from './system-model-setting-modal';
38
 
39
  import styles from './index.less';
 
116
  const UserSettingModel = () => {
117
  const factoryList = useFetchLlmFactoryListOnMount();
118
  const llmList = useFetchMyLlmListOnMount();
119
+ const loading = useSelectModelProvidersLoading();
120
  const {
121
  saveApiKeyLoading,
122
  initialApiKey,
 
197
 
198
  return (
199
  <>
200
+ <Spin spinning={loading}>
201
+ <section className={styles.modelWrapper}>
202
+ <SettingTitle
203
+ title="Model Setting"
204
+ description="Manage your account settings and preferences here."
205
+ showRightButton
206
+ clickButton={showSystemSettingModal}
207
+ ></SettingTitle>
208
+ <Divider></Divider>
209
+ <Collapse defaultActiveKey={['1']} ghost items={items} />
210
+ </section>
211
+ </Spin>
212
  <ApiKeyModal
213
  visible={apiKeyVisible}
214
  hideModal={hideApiKeyModal}
web/src/pages/user-setting/setting-profile/index.tsx CHANGED
@@ -1,4 +1,8 @@
1
- import { useSaveSetting, useSelectUserInfo } from '@/hooks/userSettingHook';
 
 
 
 
2
  import {
3
  getBase64FromUploadFileList,
4
  getUploadFileListFromBase64,
@@ -12,6 +16,7 @@ import {
12
  Input,
13
  Select,
14
  Space,
 
15
  Tooltip,
16
  Upload,
17
  UploadFile,
@@ -19,7 +24,11 @@ import {
19
  import { useEffect } from 'react';
20
  import SettingTitle from '../components/setting-title';
21
  import { TimezoneList } from '../constants';
22
- import { useGetUserInfoLoading, useValidateSubmittable } from '../hooks';
 
 
 
 
23
 
24
  import parentStyles from '../index.less';
25
  import styles from './index.less';
@@ -42,8 +51,10 @@ const tailLayout = {
42
  const UserSettingProfile = () => {
43
  const userInfo = useSelectUserInfo();
44
  const saveSetting = useSaveSetting();
45
- const loading = useGetUserInfoLoading();
46
  const { form, submittable } = useValidateSubmittable();
 
 
47
 
48
  const onFinish = async (values: any) => {
49
  const avatar = await getBase64FromUploadFileList(values.avatar);
@@ -66,131 +77,133 @@ const UserSettingProfile = () => {
66
  description="Update your photo and personal details here."
67
  ></SettingTitle>
68
  <Divider />
69
- <Form
70
- colon={false}
71
- name="basic"
72
- labelAlign={'left'}
73
- labelCol={{ span: 8 }}
74
- wrapperCol={{ span: 16 }}
75
- style={{ width: '100%' }}
76
- initialValues={{ remember: true }}
77
- onFinish={onFinish}
78
- onFinishFailed={onFinishFailed}
79
- form={form}
80
- autoComplete="off"
81
- >
82
- <Form.Item<FieldType>
83
- label="Username"
84
- name="nickname"
85
- rules={[
86
- {
87
- required: true,
88
- message: 'Please input your username!',
89
- whitespace: true,
90
- },
91
- ]}
92
- >
93
- <Input />
94
- </Form.Item>
95
- <Divider />
96
- <Form.Item<FieldType>
97
- label={
98
- <div>
99
- <Space>
100
- Your photo
101
- <Tooltip title="prompt text">
102
- <QuestionCircleOutlined />
103
- </Tooltip>
104
- </Space>
105
- <div>This will be displayed on your profile.</div>
106
- </div>
107
- }
108
- name="avatar"
109
- valuePropName="fileList"
110
- getValueFromEvent={normFile}
111
  >
112
- <Upload
113
- listType="picture-card"
114
- maxCount={1}
115
- accept="image/*"
116
- beforeUpload={() => {
117
- return false;
118
- }}
119
- showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
 
 
120
  >
121
- <button style={{ border: 0, background: 'none' }} type="button">
122
- <PlusOutlined />
123
- <div style={{ marginTop: 8 }}>Upload</div>
124
- </button>
125
- </Upload>
126
- </Form.Item>
127
- <Divider />
128
- <Form.Item<FieldType>
129
- label="Color schema"
130
- name="color_schema"
131
- rules={[
132
- { required: true, message: 'Please select your color schema!' },
133
- ]}
134
- >
135
- <Select placeholder="select your color schema">
136
- <Option value="Bright">Bright</Option>
137
- <Option value="Dark">Dark</Option>
138
- </Select>
139
- </Form.Item>
140
- <Divider />
141
- <Form.Item<FieldType>
142
- label="Language"
143
- name="language"
144
- rules={[{ required: true, message: 'Please input your language!' }]}
145
- >
146
- <Select placeholder="select your language">
147
- <Option value="English">English</Option>
148
- <Option value="Chinese">Chinese</Option>
149
- </Select>
150
- </Form.Item>
151
- <Divider />
152
- <Form.Item<FieldType>
153
- label="Timezone"
154
- name="timezone"
155
- rules={[{ required: true, message: 'Please input your timezone!' }]}
156
- >
157
- <Select placeholder="select your timezone" showSearch>
158
- {TimezoneList.map((x) => (
159
- <Option value={x} key={x}>
160
- {x}
161
- </Option>
162
- ))}
163
- </Select>
164
- </Form.Item>
165
- <Divider />
166
- <Form.Item label="Email address">
167
- <Form.Item<FieldType> name="email" noStyle>
168
- <Input disabled />
169
  </Form.Item>
170
- <p className={parentStyles.itemDescription}>
171
- Once registered, an account cannot be changed and can only be
172
- cancelled.
173
- </p>
174
- </Form.Item>
175
- <Form.Item
176
- {...tailLayout}
177
- shouldUpdate={(prevValues, curValues) =>
178
- prevValues.additional !== curValues.additional
179
- }
180
- >
181
- <Space>
182
- <Button htmlType="button">Cancel</Button>
183
- <Button
184
- type="primary"
185
- htmlType="submit"
186
- disabled={!submittable}
187
- loading={loading}
 
 
 
 
 
 
 
188
  >
189
- Save
190
- </Button>
191
- </Space>
192
- </Form.Item>
193
- </Form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  </section>
195
  );
196
  };
 
1
+ import {
2
+ useFetchUserInfo,
3
+ useSaveSetting,
4
+ useSelectUserInfo,
5
+ } from '@/hooks/userSettingHook';
6
  import {
7
  getBase64FromUploadFileList,
8
  getUploadFileListFromBase64,
 
16
  Input,
17
  Select,
18
  Space,
19
+ Spin,
20
  Tooltip,
21
  Upload,
22
  UploadFile,
 
24
  import { useEffect } from 'react';
25
  import SettingTitle from '../components/setting-title';
26
  import { TimezoneList } from '../constants';
27
+ import {
28
+ useSelectSubmitUserInfoLoading,
29
+ useSelectUserInfoLoading,
30
+ useValidateSubmittable,
31
+ } from '../hooks';
32
 
33
  import parentStyles from '../index.less';
34
  import styles from './index.less';
 
51
  const UserSettingProfile = () => {
52
  const userInfo = useSelectUserInfo();
53
  const saveSetting = useSaveSetting();
54
+ const submitLoading = useSelectSubmitUserInfoLoading();
55
  const { form, submittable } = useValidateSubmittable();
56
+ const loading = useSelectUserInfoLoading();
57
+ useFetchUserInfo();
58
 
59
  const onFinish = async (values: any) => {
60
  const avatar = await getBase64FromUploadFileList(values.avatar);
 
77
  description="Update your photo and personal details here."
78
  ></SettingTitle>
79
  <Divider />
80
+ <Spin spinning={loading}>
81
+ <Form
82
+ colon={false}
83
+ name="basic"
84
+ labelAlign={'left'}
85
+ labelCol={{ span: 8 }}
86
+ wrapperCol={{ span: 16 }}
87
+ style={{ width: '100%' }}
88
+ initialValues={{ remember: true }}
89
+ onFinish={onFinish}
90
+ onFinishFailed={onFinishFailed}
91
+ form={form}
92
+ autoComplete="off"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  >
94
+ <Form.Item<FieldType>
95
+ label="Username"
96
+ name="nickname"
97
+ rules={[
98
+ {
99
+ required: true,
100
+ message: 'Please input your username!',
101
+ whitespace: true,
102
+ },
103
+ ]}
104
  >
105
+ <Input />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  </Form.Item>
107
+ <Divider />
108
+ <Form.Item<FieldType>
109
+ label={
110
+ <div>
111
+ <Space>
112
+ Your photo
113
+ <Tooltip title="prompt text">
114
+ <QuestionCircleOutlined />
115
+ </Tooltip>
116
+ </Space>
117
+ <div>This will be displayed on your profile.</div>
118
+ </div>
119
+ }
120
+ name="avatar"
121
+ valuePropName="fileList"
122
+ getValueFromEvent={normFile}
123
+ >
124
+ <Upload
125
+ listType="picture-card"
126
+ maxCount={1}
127
+ accept="image/*"
128
+ beforeUpload={() => {
129
+ return false;
130
+ }}
131
+ showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
132
  >
133
+ <button style={{ border: 0, background: 'none' }} type="button">
134
+ <PlusOutlined />
135
+ <div style={{ marginTop: 8 }}>Upload</div>
136
+ </button>
137
+ </Upload>
138
+ </Form.Item>
139
+ <Divider />
140
+ <Form.Item<FieldType>
141
+ label="Color schema"
142
+ name="color_schema"
143
+ rules={[
144
+ { required: true, message: 'Please select your color schema!' },
145
+ ]}
146
+ >
147
+ <Select placeholder="select your color schema">
148
+ <Option value="Bright">Bright</Option>
149
+ <Option value="Dark">Dark</Option>
150
+ </Select>
151
+ </Form.Item>
152
+ <Divider />
153
+ <Form.Item<FieldType>
154
+ label="Language"
155
+ name="language"
156
+ rules={[{ required: true, message: 'Please input your language!' }]}
157
+ >
158
+ <Select placeholder="select your language">
159
+ <Option value="English">English</Option>
160
+ <Option value="Chinese">Chinese</Option>
161
+ </Select>
162
+ </Form.Item>
163
+ <Divider />
164
+ <Form.Item<FieldType>
165
+ label="Timezone"
166
+ name="timezone"
167
+ rules={[{ required: true, message: 'Please input your timezone!' }]}
168
+ >
169
+ <Select placeholder="select your timezone" showSearch>
170
+ {TimezoneList.map((x) => (
171
+ <Option value={x} key={x}>
172
+ {x}
173
+ </Option>
174
+ ))}
175
+ </Select>
176
+ </Form.Item>
177
+ <Divider />
178
+ <Form.Item label="Email address">
179
+ <Form.Item<FieldType> name="email" noStyle>
180
+ <Input disabled />
181
+ </Form.Item>
182
+ <p className={parentStyles.itemDescription}>
183
+ Once registered, an account cannot be changed and can only be
184
+ cancelled.
185
+ </p>
186
+ </Form.Item>
187
+ <Form.Item
188
+ {...tailLayout}
189
+ shouldUpdate={(prevValues, curValues) =>
190
+ prevValues.additional !== curValues.additional
191
+ }
192
+ >
193
+ <Space>
194
+ <Button htmlType="button">Cancel</Button>
195
+ <Button
196
+ type="primary"
197
+ htmlType="submit"
198
+ disabled={!submittable}
199
+ loading={submitLoading}
200
+ >
201
+ Save
202
+ </Button>
203
+ </Space>
204
+ </Form.Item>
205
+ </Form>
206
+ </Spin>
207
  </section>
208
  );
209
  };
web/src/utils/fileUtil.ts CHANGED
@@ -48,8 +48,14 @@ export const getUploadFileListFromBase64 = (avatar: string) => {
48
 
49
  export const getBase64FromUploadFileList = async (fileList?: UploadFile[]) => {
50
  if (Array.isArray(fileList) && fileList.length > 0) {
51
- const base64 = await transformFile2Base64(fileList[0].originFileObj);
52
- return base64;
 
 
 
 
 
 
53
  // return fileList[0].thumbUrl; TODO: Even JPG files will be converted to base64 parameters in png format
54
  }
55
 
 
48
 
49
  export const getBase64FromUploadFileList = async (fileList?: UploadFile[]) => {
50
  if (Array.isArray(fileList) && fileList.length > 0) {
51
+ const file = fileList[0];
52
+ const originFileObj = file.originFileObj;
53
+ if (originFileObj) {
54
+ const base64 = await transformFile2Base64(originFileObj);
55
+ return base64;
56
+ } else {
57
+ return file.thumbUrl;
58
+ }
59
  // return fileList[0].thumbUrl; TODO: Even JPG files will be converted to base64 parameters in png format
60
  }
61