From 78727c880901bd2d03f575938d6f04e79baba382 Mon Sep 17 00:00:00 2001 From: balibabu Date: Wed, 20 Mar 2024 11:13:51 +0800 Subject: [PATCH] fix: disable sending messages if both application and conversation are empty and add loading to all pages (#134) * 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 | 6 +- web/src/hooks/chunkHooks.ts | 24 ++ web/src/hooks/knowledgeHook.ts | 45 ++- web/src/hooks/llmHooks.ts | 2 +- web/src/hooks/routeHook.ts | 14 +- .../components/document-preview/hooks.ts | 2 +- .../components/knowledge-chunk/hooks.ts | 9 + .../components/knowledge-chunk/index.tsx | 43 +-- .../knowledge-file/rename-modal/index.tsx | 6 +- .../knowledge-setting/configuration.tsx | 343 ++++++++---------- .../components/knowledge-setting/hooks.ts | 73 ++++ .../components/knowledge-setting/model.ts | 4 +- .../testing-control/index.tsx | 10 +- .../assistant-setting.tsx | 7 +- web/src/pages/chat/chat-container/index.tsx | 19 +- web/src/pages/chat/hooks.ts | 12 + web/src/pages/chat/index.less | 8 + web/src/pages/chat/index.tsx | 126 ++++--- web/src/pages/knowledge/index.tsx | 34 +- web/src/pages/user-setting/hooks.ts | 5 +- .../pages/user-setting/setting-model/hooks.ts | 9 + .../user-setting/setting-model/index.tsx | 30 +- .../user-setting/setting-profile/index.tsx | 261 ++++++------- web/src/utils/fileUtil.ts | 10 +- 24 files changed, 629 insertions(+), 473 deletions(-) create mode 100644 web/src/hooks/chunkHooks.ts create mode 100644 web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts diff --git a/web/src/components/rename-modal/index.tsx b/web/src/components/rename-modal/index.tsx index ba902c57828..9802ff88f9c 100644 --- a/web/src/components/rename-modal/index.tsx +++ b/web/src/components/rename-modal/index.tsx @@ -41,8 +41,10 @@ const RenameModal = ({ }; useEffect(() => { - form.setFieldValue('name', initialName); - }, [initialName, form]); + if (visible) { + form.setFieldValue('name', initialName); + } + }, [initialName, form, visible]); return ( { + const dispatch = useDispatch(); + const { documentId } = useGetKnowledgeSearchParams(); + + const fetchChunkList = useCallback(() => { + dispatch({ + type: 'chunkModel/chunk_list', + payload: { + doc_id: documentId, + }, + }); + }, [dispatch, documentId]); + + return fetchChunkList; +}; diff --git a/web/src/hooks/knowledgeHook.ts b/web/src/hooks/knowledgeHook.ts index bb874e64bb0..1ef516771f8 100644 --- a/web/src/hooks/knowledgeHook.ts +++ b/web/src/hooks/knowledgeHook.ts @@ -1,8 +1,9 @@ import showDeleteConfirm from '@/components/deleting-confirm'; -import { KnowledgeSearchParams } from '@/constants/knowledge'; import { IKnowledge } from '@/interfaces/database/knowledge'; import { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSearchParams, useSelector } from 'umi'; +import { useGetKnowledgeSearchParams } from './routeHook'; +import { useOneNamespaceEffectsLoading } from './storeHooks'; export const useKnowledgeBaseId = (): string => { const [searchParams] = useSearchParams(); @@ -11,17 +12,6 @@ export const useKnowledgeBaseId = (): string => { return knowledgeBaseId || ''; }; -export const useGetKnowledgeSearchParams = () => { - const [currentQueryParameters] = useSearchParams(); - - return { - documentId: - currentQueryParameters.get(KnowledgeSearchParams.DocumentId) || '', - knowledgeId: - currentQueryParameters.get(KnowledgeSearchParams.KnowledgeId) || '', - }; -}; - export const useDeleteDocumentById = (): { removeDocument: (documentId: string) => Promise; } => { @@ -135,8 +125,9 @@ export const useFetchKnowledgeBaseConfiguration = () => { export const useFetchKnowledgeList = ( shouldFilterListWithoutDocument: boolean = false, -): IKnowledge[] => { +): { list: IKnowledge[]; loading: boolean } => { const dispatch = useDispatch(); + const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']); const knowledgeModel = useSelector((state: any) => state.knowledgeModel); const { data = [] } = knowledgeModel; @@ -156,7 +147,7 @@ export const useFetchKnowledgeList = ( fetchList(); }, [fetchList]); - return list; + return { list, loading }; }; export const useSelectFileThumbnails = () => { @@ -189,3 +180,29 @@ export const useFetchFileThumbnails = (docIds?: Array) => { return { fileThumbnails, fetchFileThumbnails }; }; + +//#region knowledge configuration + +export const useUpdateKnowledge = () => { + const dispatch = useDispatch(); + + const saveKnowledgeConfiguration = useCallback( + (payload: any) => { + dispatch({ + type: 'kSModel/updateKb', + payload, + }); + }, + [dispatch], + ); + + return saveKnowledgeConfiguration; +}; + +export const useSelectKnowledgeDetails = () => { + const knowledgeDetails: IKnowledge = useSelector( + (state: any) => state.kSModel.knowledgeDetails, + ); + return knowledgeDetails; +}; +//#endregion diff --git a/web/src/hooks/llmHooks.ts b/web/src/hooks/llmHooks.ts index c48974c1a25..7c3e2437da5 100644 --- a/web/src/hooks/llmHooks.ts +++ b/web/src/hooks/llmHooks.ts @@ -8,8 +8,8 @@ import { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'umi'; export const useFetchLlmList = ( - isOnMountFetching: boolean = true, modelType?: LlmModelType, + isOnMountFetching: boolean = true, ) => { const dispatch = useDispatch(); diff --git a/web/src/hooks/routeHook.ts b/web/src/hooks/routeHook.ts index e51983c7737..f03e4a074a0 100644 --- a/web/src/hooks/routeHook.ts +++ b/web/src/hooks/routeHook.ts @@ -1,4 +1,5 @@ -import { useLocation } from 'umi'; +import { KnowledgeSearchParams } from '@/constants/knowledge'; +import { useLocation, useSearchParams } from 'umi'; export enum SegmentIndex { Second = '2', @@ -19,3 +20,14 @@ export const useSecondPathName = () => { export const useThirdPathName = () => { return useSegmentedPathName(SegmentIndex.Third); }; + +export const useGetKnowledgeSearchParams = () => { + const [currentQueryParameters] = useSearchParams(); + + return { + documentId: + currentQueryParameters.get(KnowledgeSearchParams.DocumentId) || '', + knowledgeId: + currentQueryParameters.get(KnowledgeSearchParams.KnowledgeId) || '', + }; +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts index 1d0917aedee..3183a1d5c43 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts @@ -1,4 +1,4 @@ -import { useGetKnowledgeSearchParams } from '@/hooks/knowledgeHook'; +import { useGetKnowledgeSearchParams } from '@/hooks/routeHook'; import { api_host } from '@/utils/api'; import { useSize } from 'ahooks'; import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types'; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts index edd256b24b1..59bf2fd2487 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts @@ -1,3 +1,4 @@ +import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge'; import { buildChunkHighlights } from '@/utils/documentUtils'; import { useCallback, useMemo, useState } from 'react'; @@ -46,3 +47,11 @@ export const useGetChunkHighlights = ( return highlights; }; + +export const useSelectChunkListLoading = () => { + return useOneNamespaceEffectsLoading('chunkModel', [ + 'create_hunk', + 'chunk_list', + 'switch_chunk', + ]); +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx index ab9bb20a0da..d63e70e39b6 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx @@ -1,23 +1,22 @@ +import { useFetchChunkList } from '@/hooks/chunkHooks'; import { useDeleteChunkByIds } from '@/hooks/knowledgeHook'; -import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; import type { PaginationProps } from 'antd'; import { Divider, Flex, Pagination, Space, Spin, message } from 'antd'; +import classNames from 'classnames'; import { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSearchParams, useSelector } from 'umi'; import ChunkCard from './components/chunk-card'; import CreatingModal from './components/chunk-creating-modal'; import ChunkToolBar from './components/chunk-toolbar'; -// import DocumentPreview from './components/document-preview'; -import classNames from 'classnames'; import DocumentPreview from './components/document-preview/preview'; -import { useHandleChunkCardClick, useSelectDocumentInfo } from './hooks'; +import { + useHandleChunkCardClick, + useSelectChunkListLoading, + useSelectDocumentInfo, +} from './hooks'; import { ChunkModelState } from './model'; import styles from './index.less'; -interface PayloadType { - doc_id: string; - keywords?: string; -} const Chunk = () => { const dispatch = useDispatch(); @@ -27,12 +26,7 @@ const Chunk = () => { const [selectedChunkIds, setSelectedChunkIds] = useState([]); const [searchParams] = useSearchParams(); const { data = [], total, pagination } = chunkModel; - const effects = useSelector((state: any) => state.loading.effects); - const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [ - 'create_hunk', - 'chunk_list', - 'switch_chunk', - ]); + const loading = useSelectChunkListLoading(); const documentId: string = searchParams.get('doc_id') || ''; const [chunkId, setChunkId] = useState(); const { removeChunk } = useDeleteChunkByIds(); @@ -40,18 +34,7 @@ const Chunk = () => { const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); const isPdf = documentInfo.type === 'pdf'; - const getChunkList = useCallback(() => { - const payload: PayloadType = { - doc_id: documentId, - }; - - dispatch({ - type: 'chunkModel/chunk_list', - payload: { - ...payload, - }, - }); - }, [dispatch, documentId]); + const getChunkList = useFetchChunkList(); const handleEditChunk = useCallback( (chunk_id?: string) => { @@ -169,8 +152,8 @@ const Chunk = () => { vertical className={isPdf ? styles.pagePdfWrapper : styles.pageWrapper} > -
- + +
{ > ))} - -
+
+
{ }; useEffect(() => { - form.setFieldValue('name', initialName); - }, [initialName, documentId, form]); + if (isModalOpen) { + form.setFieldValue('name', initialName); + } + }, [initialName, documentId, form, isModalOpen]); return ( { - const [form] = Form.useForm(); - const dispatch = useDispatch(); - const knowledgeBaseId = useKnowledgeBaseId(); - const loading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']); - - const knowledgeDetails: IKnowledge = useSelector( - (state: any) => state.kSModel.knowledgeDetails, - ); - - const parserList = useSelectParserList(); - - const embeddingModelOptions = useSelectLlmOptions(); - - const onFinish = async (values: any) => { - const avatar = await getBase64FromUploadFileList(values.avatar); - dispatch({ - type: 'kSModel/updateKb', - payload: { - ...values, - avatar, - kb_id: knowledgeBaseId, - }, - }); - }; + const { submitKnowledgeConfiguration, submitLoading } = + useSubmitKnowledgeConfiguration(); + const { form, parserList, embeddingModelOptions, loading } = + useFetchKnowledgeConfigurationOnMount(); const onFinishFailed = (errorInfo: any) => { console.log('Failed:', errorInfo); }; - useEffect(() => { - const fileList: UploadFile[] = getUploadFileListFromBase64( - knowledgeDetails.avatar, - ); - - form.setFieldsValue({ - ...pick(knowledgeDetails, [ - 'description', - 'name', - 'permission', - 'embd_id', - 'parser_id', - 'language', - 'parser_config.chunk_token_num', - ]), - avatar: fileList, - }); - }, [form, knowledgeDetails]); - - useFetchTenantInfo(); - useFetchKnowledgeBaseConfiguration(); - - useFetchLlmList(LlmModelType.Embedding); - return (
Configuration

Update your knowledge base details especially parsing method here.

-
- - - - + - false} - showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + - - - - - - - - - - - - Only me - Team - - - - - - - - - - {({ getFieldValue }) => { - const parserId = getFieldValue('parser_id'); + + + + false} + showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + > + + + + + + + + + + + + Only me + Team + + + + + + + + + + {({ getFieldValue }) => { + const parserId = getFieldValue('parser_id'); - if (parserId === 'naive') { - return ( - - - + if (parserId === 'naive') { + return ( + + + + + + + - + - - - - - - ); - } - return null; - }} - - -
- - - - -
-
-
+ + ); + } + return null; + }} + + +
+ + + + +
+
+ +
); }; diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts b/web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts new file mode 100644 index 00000000000..008979d65c2 --- /dev/null +++ b/web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts @@ -0,0 +1,73 @@ +import { + useFetchKnowledgeBaseConfiguration, + useKnowledgeBaseId, + useSelectKnowledgeDetails, + useUpdateKnowledge, +} from '@/hooks/knowledgeHook'; +import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks'; +import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; +import { + useFetchTenantInfo, + useSelectParserList, +} from '@/hooks/userSettingHook'; +import { + getBase64FromUploadFileList, + getUploadFileListFromBase64, +} from '@/utils/fileUtil'; +import { Form, UploadFile } from 'antd'; +import pick from 'lodash/pick'; +import { useCallback, useEffect } from 'react'; +import { LlmModelType } from '../../constant'; + +export const useSubmitKnowledgeConfiguration = () => { + const save = useUpdateKnowledge(); + const knowledgeBaseId = useKnowledgeBaseId(); + const submitLoading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']); + + const submitKnowledgeConfiguration = useCallback( + async (values: any) => { + const avatar = await getBase64FromUploadFileList(values.avatar); + save({ + ...values, + avatar, + kb_id: knowledgeBaseId, + }); + }, + [save, knowledgeBaseId], + ); + + return { submitKnowledgeConfiguration, submitLoading }; +}; + +export const useFetchKnowledgeConfigurationOnMount = () => { + const [form] = Form.useForm(); + const loading = useOneNamespaceEffectsLoading('kSModel', ['getKbDetail']); + + const knowledgeDetails = useSelectKnowledgeDetails(); + const parserList = useSelectParserList(); + const embeddingModelOptions = useSelectLlmOptions(); + + useFetchTenantInfo(); + useFetchKnowledgeBaseConfiguration(); + useFetchLlmList(LlmModelType.Embedding); + + useEffect(() => { + const fileList: UploadFile[] = getUploadFileListFromBase64( + knowledgeDetails.avatar, + ); + form.setFieldsValue({ + ...pick(knowledgeDetails, [ + 'description', + 'name', + 'permission', + 'embd_id', + 'parser_id', + 'language', + 'parser_config.chunk_token_num', + ]), + avatar: fileList, + }); + }, [form, knowledgeDetails]); + + return { form, parserList, embeddingModelOptions, loading }; +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts index 1f4499631ee..29126cc9b9c 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts @@ -34,7 +34,7 @@ const model: DvaModel = { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Created successfully!'); + message.success('Created!'); } return data; }, @@ -43,7 +43,7 @@ const model: DvaModel = { const { retcode } = data; if (retcode === 0) { yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } }); - message.success('Updated successfully!'); + message.success('Updated!'); } }, *getKbDetail({ payload = {} }, { call, put }) { diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx index 791f1db2120..637fd89fbac 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx @@ -37,9 +37,9 @@ const TestingControl = ({ form, handleTesting }: IProps) => { return (
-

+

Retrieval testing -

+

Final step! After success, leave the rest to Infiniflow AI.

@@ -48,8 +48,6 @@ const TestingControl = ({ form, handleTesting }: IProps) => { layout="vertical" form={form} initialValues={{ - similarity_threshold: 0.2, - vector_similarity_weight: 0.3, top_k: 1024, }} > @@ -81,12 +79,12 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
-

+

Test history -

+
{ - const knowledgeList = useFetchKnowledgeList(true); + const { list: knowledgeList } = useFetchKnowledgeList(true); const knowledgeOptions = knowledgeList.map((x) => ({ label: x.name, value: x.id, diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index e7cd63e19f3..17a94699be7 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -30,6 +30,7 @@ import { useClickDrawer, useFetchConversationOnMount, useGetFileIcon, + useGetSendButtonDisabled, useSendMessage, } from '../hooks'; @@ -248,11 +249,15 @@ const ChatContainer = () => { addNewestConversation, removeLatestMessage, } = useFetchConversationOnMount(); - const { handleInputChange, handlePressEnter, value, loading } = - useSendMessage(conversation, addNewestConversation, removeLatestMessage); + const { + handleInputChange, + handlePressEnter, + value, + loading: sendLoading, + } = useSendMessage(conversation, addNewestConversation, removeLatestMessage); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); - + const disabled = useGetSendButtonDisabled(); useGetFileIcon(); return ( @@ -284,8 +289,14 @@ const ChatContainer = () => { size="large" placeholder="Message Resume Assistant..." value={value} + disabled={disabled} suffix={ - } diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index 877c91a17b2..6da74c46ff9 100644 --- a/web/src/pages/chat/hooks.ts +++ b/web/src/pages/chat/hooks.ts @@ -767,4 +767,16 @@ export const useClickDrawer = () => { }; }; +export const useSelectDialogListLoading = () => { + return useOneNamespaceEffectsLoading('chatModel', ['listDialog']); +}; +export const useSelectConversationListLoading = () => { + return useOneNamespaceEffectsLoading('chatModel', ['listConversation']); +}; + +export const useGetSendButtonDisabled = () => { + const { dialogId, conversationId } = useGetChatSearchParams(); + + return dialogId === '' && conversationId === ''; +}; //#endregion diff --git a/web/src/pages/chat/index.less b/web/src/pages/chat/index.less index 57eb15a7dd9..7372f5e1190 100644 --- a/web/src/pages/chat/index.less +++ b/web/src/pages/chat/index.less @@ -41,6 +41,14 @@ overflow: auto; } + .chatSpin { + :global(.ant-spin-container) { + display: flex; + flex-direction: column; + gap: 10px; + } + } + .chatTitleCard { :global(.ant-card-body) { padding: 8px; diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index b6ee17a8ac8..88c3bca9614 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -1,5 +1,4 @@ import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg'; -import { useSetModalState } from '@/hooks/commonHooks'; import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons'; import { Avatar, @@ -10,6 +9,7 @@ import { Flex, MenuProps, Space, + Spin, Tag, } from 'antd'; import { MenuItemProps } from 'antd/lib/menu/MenuItem'; @@ -29,8 +29,9 @@ import { useRemoveDialog, useRenameConversation, useSelectConversationList, + useSelectConversationListLoading, + useSelectDialogListLoading, useSelectFirstDialogOnMount, - useSetCurrentDialog, } from './hooks'; import RenameModal from '@/components/rename-modal'; @@ -38,8 +39,6 @@ import styles from './index.less'; const Chat = () => { const dialogList = useSelectFirstDialogOnMount(); - const { visible, hideModal, showModal } = useSetModalState(); - const { setCurrentDialog, currentDialog } = useSetCurrentDialog(); const { onRemoveDialog } = useRemoveDialog(); const { onRemoveConversation } = useRemoveConversation(); const { handleClickDialog } = useClickDialogCard(); @@ -70,6 +69,8 @@ const Chat = () => { hideDialogEditModal, showDialogEditModal, } = useEditDialog(); + const dialogLoading = useSelectDialogListLoading(); + const conversationLoading = useSelectConversationListLoading(); useFetchDialogOnMount(dialogId, true); @@ -204,35 +205,39 @@ const Chat = () => { - {dialogList.map((x) => ( - - - - -
- {x.name} -
{x.description}
-
-
- {activated === x.id && ( -
- - - -
- )} -
-
- ))} + + {dialogList.map((x) => ( + + + + +
+ {x.name} +
{x.description}
+
+
+ {activated === x.id && ( +
+ + + +
+ )} +
+
+ ))} +
@@ -254,29 +259,38 @@ const Chat = () => { - {conversationList.map((x) => ( - - -
{x.name}
- {conversationActivated === x.id && x.id !== '' && ( -
- - - -
- )} -
-
- ))} + + {conversationList.map((x) => ( + + +
{x.name}
+ {conversationActivated === x.id && x.id !== '' && ( +
+ + + +
+ )} +
+
+ ))} +
diff --git a/web/src/pages/knowledge/index.tsx b/web/src/pages/knowledge/index.tsx index 78f2af55da8..8b61e0c157e 100644 --- a/web/src/pages/knowledge/index.tsx +++ b/web/src/pages/knowledge/index.tsx @@ -1,16 +1,16 @@ import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import ModalManager from '@/components/modal-manager'; +import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; +import { useSelectUserInfo } from '@/hooks/userSettingHook'; import { PlusOutlined } from '@ant-design/icons'; -import { Button, Empty, Flex, Space } from 'antd'; +import { Button, Empty, Flex, Space, Spin } from 'antd'; import KnowledgeCard from './knowledge-card'; import KnowledgeCreatingModal from './knowledge-creating-modal'; -import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; -import { useSelectUserInfo } from '@/hooks/userSettingHook'; import styles from './index.less'; const Knowledge = () => { - const list = useFetchKnowledgeList(); + const { list, loading } = useFetchKnowledgeList(); const userInfo = useSelectUserInfo(); return ( @@ -50,15 +50,23 @@ const Knowledge = () => {
- - {list.length > 0 ? ( - list.map((item: any) => { - return ; - }) - ) : ( - - )} - + + + {list.length > 0 ? ( + list.map((item: any) => { + return ( + + ); + }) + ) : ( + + )} + + ); }; diff --git a/web/src/pages/user-setting/hooks.ts b/web/src/pages/user-setting/hooks.ts index ee670c69611..bb770a4945a 100644 --- a/web/src/pages/user-setting/hooks.ts +++ b/web/src/pages/user-setting/hooks.ts @@ -19,5 +19,8 @@ export const useValidateSubmittable = () => { return { submittable, form }; }; -export const useGetUserInfoLoading = () => +export const useSelectSubmitUserInfoLoading = () => useOneNamespaceEffectsLoading('settingModel', ['setting']); + +export const useSelectUserInfoLoading = () => + useOneNamespaceEffectsLoading('settingModel', ['getUserInfo']); diff --git a/web/src/pages/user-setting/setting-model/hooks.ts b/web/src/pages/user-setting/setting-model/hooks.ts index 0049cc6ec6e..af400bb62e1 100644 --- a/web/src/pages/user-setting/setting-model/hooks.ts +++ b/web/src/pages/user-setting/setting-model/hooks.ts @@ -113,3 +113,12 @@ export const useFetchSystemModelSettingOnMount = (visible: boolean) => { return { systemSetting, allOptions }; }; + +export const useSelectModelProvidersLoading = () => { + const loading = useOneNamespaceEffectsLoading('settingModel', [ + 'my_llm', + 'factories_list', + ]); + + return loading; +}; diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 57cf93e81ce..67b39a8bd3e 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -23,12 +23,17 @@ import { List, Row, Space, + Spin, Typography, } from 'antd'; import { useCallback } from 'react'; import SettingTitle from '../components/setting-title'; import ApiKeyModal from './api-key-modal'; -import { useSubmitApiKey, useSubmitSystemModelSetting } from './hooks'; +import { + useSelectModelProvidersLoading, + useSubmitApiKey, + useSubmitSystemModelSetting, +} from './hooks'; import SystemModelSettingModal from './system-model-setting-modal'; import styles from './index.less'; @@ -111,6 +116,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { const UserSettingModel = () => { const factoryList = useFetchLlmFactoryListOnMount(); const llmList = useFetchMyLlmListOnMount(); + const loading = useSelectModelProvidersLoading(); const { saveApiKeyLoading, initialApiKey, @@ -191,16 +197,18 @@ const UserSettingModel = () => { return ( <> -
- - - -
+ +
+ + + +
+
{ const userInfo = useSelectUserInfo(); const saveSetting = useSaveSetting(); - const loading = useGetUserInfoLoading(); + const submitLoading = useSelectSubmitUserInfoLoading(); const { form, submittable } = useValidateSubmittable(); + const loading = useSelectUserInfoLoading(); + useFetchUserInfo(); const onFinish = async (values: any) => { const avatar = await getBase64FromUploadFileList(values.avatar); @@ -66,131 +77,133 @@ const UserSettingProfile = () => { description="Update your photo and personal details here." > -
- - label="Username" - name="nickname" - rules={[ - { - required: true, - message: 'Please input your username!', - whitespace: true, - }, - ]} - > - - - - - label={ -
- - Your photo - - - - -
This will be displayed on your profile.
-
- } - name="avatar" - valuePropName="fileList" - getValueFromEvent={normFile} + + - { - return false; - }} - showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + + label="Username" + name="nickname" + rules={[ + { + required: true, + message: 'Please input your username!', + whitespace: true, + }, + ]} > - - - - - - label="Color schema" - name="color_schema" - rules={[ - { required: true, message: 'Please select your color schema!' }, - ]} - > - - - - - label="Language" - name="language" - rules={[{ required: true, message: 'Please input your language!' }]} - > - - - - - label="Timezone" - name="timezone" - rules={[{ required: true, message: 'Please input your timezone!' }]} - > - - - - - name="email" noStyle> - + -

- Once registered, an account cannot be changed and can only be - cancelled. -

- - - prevValues.additional !== curValues.additional - } - > - - - - - - + + + + + + label="Color schema" + name="color_schema" + rules={[ + { required: true, message: 'Please select your color schema!' }, + ]} + > + + + + + label="Language" + name="language" + rules={[{ required: true, message: 'Please input your language!' }]} + > + + + + + label="Timezone" + name="timezone" + rules={[{ required: true, message: 'Please input your timezone!' }]} + > + + + + + name="email" noStyle> + + +

+ Once registered, an account cannot be changed and can only be + cancelled. +

+ + + prevValues.additional !== curValues.additional + } + > + + + + + + +
); }; diff --git a/web/src/utils/fileUtil.ts b/web/src/utils/fileUtil.ts index 44e43c6dfe9..40b6bd9f71f 100644 --- a/web/src/utils/fileUtil.ts +++ b/web/src/utils/fileUtil.ts @@ -48,8 +48,14 @@ export const getUploadFileListFromBase64 = (avatar: string) => { export const getBase64FromUploadFileList = async (fileList?: UploadFile[]) => { if (Array.isArray(fileList) && fileList.length > 0) { - const base64 = await transformFile2Base64(fileList[0].originFileObj); - return base64; + const file = fileList[0]; + const originFileObj = file.originFileObj; + if (originFileObj) { + const base64 = await transformFile2Base64(originFileObj); + return base64; + } else { + return file.thumbUrl; + } // return fileList[0].thumbUrl; TODO: Even JPG files will be converted to base64 parameters in png format }