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 +4 -2
- web/src/hooks/chunkHooks.ts +24 -0
- web/src/hooks/knowledgeHook.ts +31 -14
- web/src/hooks/llmHooks.ts +1 -1
- web/src/hooks/routeHook.ts +13 -1
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts +1 -1
- web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts +9 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +13 -30
- web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx +4 -2
- web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +144 -199
- web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts +73 -0
- web/src/pages/add-knowledge/components/knowledge-setting/model.ts +2 -2
- web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx +4 -6
- web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx +3 -4
- web/src/pages/chat/chat-container/index.tsx +15 -4
- web/src/pages/chat/hooks.ts +12 -0
- web/src/pages/chat/index.less +8 -0
- web/src/pages/chat/index.tsx +70 -56
- web/src/pages/knowledge/index.tsx +21 -13
- web/src/pages/user-setting/hooks.ts +4 -1
- web/src/pages/user-setting/setting-model/hooks.ts +9 -0
- web/src/pages/user-setting/setting-model/index.tsx +19 -11
- web/src/pages/user-setting/setting-profile/index.tsx +137 -124
- web/src/utils/fileUtil.ts +8 -2
web/src/components/rename-modal/index.tsx
CHANGED
@@ -41,8 +41,10 @@ const RenameModal = ({
|
|
41 |
};
|
42 |
|
43 |
useEffect(() => {
|
44 |
-
|
45 |
-
|
|
|
|
|
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 {
|
|
|
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/
|
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 {
|
|
|
|
|
|
|
|
|
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
|
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 =
|
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 |
-
<
|
173 |
-
<
|
174 |
<Space
|
175 |
direction="vertical"
|
176 |
size={'middle'}
|
@@ -193,8 +176,8 @@ const Chunk = () => {
|
|
193 |
></ChunkCard>
|
194 |
))}
|
195 |
</Space>
|
196 |
-
</
|
197 |
-
</
|
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 |
-
|
56 |
-
|
|
|
|
|
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
|
34 |
-
|
35 |
-
|
36 |
-
|
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
|
45 |
-
|
46 |
-
const
|
47 |
-
|
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 |
-
<
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
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 |
-
<
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
128 |
>
|
129 |
-
<
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
<Form.Item
|
194 |
name={['parser_config', 'chunk_token_num']}
|
195 |
noStyle
|
196 |
-
initialValue={128}
|
197 |
rules={[
|
198 |
-
{ required: true, message: '
|
199 |
]}
|
200 |
>
|
201 |
-
<
|
|
|
|
|
|
|
|
|
202 |
</Form.Item>
|
203 |
</Flex>
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
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
|
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
|
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 |
-
<
|
41 |
<b>Retrieval testing</b>
|
42 |
-
</
|
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 |
-
<
|
85 |
<Space size={'middle'}>
|
86 |
<HistoryOutlined className={styles.historyIcon} />
|
87 |
<b>Test history</b>
|
88 |
</Space>
|
89 |
-
</
|
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 {
|
252 |
-
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
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 |
-
{
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
<
|
220 |
-
<
|
221 |
-
|
222 |
-
<
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
<
|
229 |
-
<
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
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 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
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 |
-
<
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
}
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 {
|
|
|
|
|
|
|
|
|
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 |
-
<
|
195 |
-
<
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
|
|
|
|
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 {
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
|
|
|
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
|
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 |
-
<
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
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 |
-
<
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
120 |
>
|
121 |
-
<
|
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 |
-
<
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
>
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|