From 78727c880901bd2d03f575938d6f04e79baba382 Mon Sep 17 00:00:00 2001 From: balibabu <cike8899@users.noreply.github.com> 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 ba902c5..9802ff8 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 ( <Modal diff --git a/web/src/hooks/chunkHooks.ts b/web/src/hooks/chunkHooks.ts new file mode 100644 index 0000000..377192f --- /dev/null +++ b/web/src/hooks/chunkHooks.ts @@ -0,0 +1,24 @@ +import { useCallback } from 'react'; +import { useDispatch } from 'umi'; +import { useGetKnowledgeSearchParams } from './routeHook'; + +interface PayloadType { + doc_id: string; + keywords?: string; +} + +export const useFetchChunkList = () => { + 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 bb874e6..1ef5167 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<number>; } => { @@ -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<string>) => { 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 c48974c..7c3e243 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 e51983c..f03e4a0 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 1d0917a..3183a1d 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 edd256b..59bf2fd 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 ab9bb20..d63e70e 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<string[]>([]); 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<string | undefined>(); 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} > - <div className={styles.pageContent}> - <Spin spinning={loading} className={styles.spin} size="large"> + <Spin spinning={loading} className={styles.spin} size="large"> + <div className={styles.pageContent}> <Space direction="vertical" size={'middle'} @@ -193,8 +176,8 @@ const Chunk = () => { ></ChunkCard> ))} </Space> - </Spin> - </div> + </div> + </Spin> <div className={styles.pageFooter}> <Pagination responsive diff --git a/web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx index 13109e6..a80d7f4 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/rename-modal/index.tsx @@ -52,8 +52,10 @@ const RenameModal = () => { }; useEffect(() => { - form.setFieldValue('name', initialName); - }, [initialName, documentId, form]); + if (isModalOpen) { + form.setFieldValue('name', initialName); + } + }, [initialName, documentId, form, isModalOpen]); return ( <Modal diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index f0e7e70..7d50cfb 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -1,19 +1,4 @@ -import { - useFetchKnowledgeBaseConfiguration, - useKnowledgeBaseId, -} from '@/hooks/knowledgeHook'; -import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks'; -import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; -import { - useFetchTenantInfo, - useSelectParserList, -} from '@/hooks/userSettingHook'; -import { IKnowledge } from '@/interfaces/database/knowledge'; -import { - getBase64FromUploadFileList, - getUploadFileListFromBase64, - normFile, -} from '@/utils/fileUtil'; +import { normFile } from '@/utils/fileUtil'; import { PlusOutlined } from '@ant-design/icons'; import { Button, @@ -26,14 +11,14 @@ import { Select, Slider, Space, + Spin, Typography, Upload, - UploadFile, } from 'antd'; -import pick from 'lodash/pick'; -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'umi'; -import { LlmModelType } from '../../constant'; +import { + useFetchKnowledgeConfigurationOnMount, + useSubmitKnowledgeConfiguration, +} from './hooks'; import styles from './index.less'; @@ -41,205 +26,165 @@ const { Title } = Typography; const { Option } = Select; const Configuration = () => { - 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 ( <div className={styles.configurationWrapper}> <Title level={5}>Configuration</Title> <p>Update your knowledge base details especially parsing method here.</p> <Divider></Divider> - <Form - form={form} - name="validateOnly" - layout="vertical" - autoComplete="off" - onFinish={onFinish} - onFinishFailed={onFinishFailed} - > - <Form.Item - name="name" - label="Knowledge base name" - rules={[{ required: true }]} - > - <Input /> - </Form.Item> - <Form.Item - name="avatar" - label="Knowledge base photo" - valuePropName="fileList" - getValueFromEvent={normFile} + <Spin spinning={loading}> + <Form + form={form} + name="validateOnly" + layout="vertical" + autoComplete="off" + onFinish={submitKnowledgeConfiguration} + onFinishFailed={onFinishFailed} > - <Upload - listType="picture-card" - maxCount={1} - beforeUpload={() => false} - showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + <Form.Item + name="name" + label="Knowledge base name" + rules={[{ required: true }]} > - <button style={{ border: 0, background: 'none' }} type="button"> - <PlusOutlined /> - <div style={{ marginTop: 8 }}>Upload</div> - </button> - </Upload> - </Form.Item> - <Form.Item name="description" label="Description"> - <Input /> - </Form.Item> - <Form.Item - label="Language" - name="language" - initialValue={'Chinese'} - rules={[{ required: true, message: 'Please input your language!' }]} - > - <Select placeholder="select your language"> - <Option value="English">English</Option> - <Option value="Chinese">Chinese</Option> - </Select> - </Form.Item> - <Form.Item - name="permission" - label="Permissions" - rules={[{ required: true }]} - > - <Radio.Group> - <Radio value="me">Only me</Radio> - <Radio value="team">Team</Radio> - </Radio.Group> - </Form.Item> - <Form.Item - name="embd_id" - label="Embedding Model" - rules={[{ required: true }]} - tooltip="xx" - > - <Select - placeholder="Please select a country" - options={embeddingModelOptions} - ></Select> - </Form.Item> - <Form.Item - name="parser_id" - label="Knowledge base category" - tooltip="xx" - rules={[{ required: true }]} - > - <Select placeholder="Please select a country"> - {parserList.map((x) => ( - <Option value={x.value} key={x.value}> - {x.label} - </Option> - ))} - </Select> - </Form.Item> - <Form.Item noStyle dependencies={['parser_id']}> - {({ getFieldValue }) => { - const parserId = getFieldValue('parser_id'); + <Input /> + </Form.Item> + <Form.Item + name="avatar" + label="Knowledge base photo" + valuePropName="fileList" + getValueFromEvent={normFile} + > + <Upload + listType="picture-card" + maxCount={1} + beforeUpload={() => false} + showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + > + <button style={{ border: 0, background: 'none' }} type="button"> + <PlusOutlined /> + <div style={{ marginTop: 8 }}>Upload</div> + </button> + </Upload> + </Form.Item> + <Form.Item name="description" label="Description"> + <Input /> + </Form.Item> + <Form.Item + label="Language" + name="language" + initialValue={'Chinese'} + rules={[{ required: true, message: 'Please input your language!' }]} + > + <Select placeholder="select your language"> + <Option value="English">English</Option> + <Option value="Chinese">Chinese</Option> + </Select> + </Form.Item> + <Form.Item + name="permission" + label="Permissions" + rules={[{ required: true }]} + > + <Radio.Group> + <Radio value="me">Only me</Radio> + <Radio value="team">Team</Radio> + </Radio.Group> + </Form.Item> + <Form.Item + name="embd_id" + label="Embedding Model" + rules={[{ required: true }]} + tooltip="xx" + > + <Select + placeholder="Please select a country" + options={embeddingModelOptions} + ></Select> + </Form.Item> + <Form.Item + name="parser_id" + label="Knowledge base category" + tooltip="xx" + rules={[{ required: true }]} + > + <Select placeholder="Please select a country"> + {parserList.map((x) => ( + <Option value={x.value} key={x.value}> + {x.label} + </Option> + ))} + </Select> + </Form.Item> + <Form.Item noStyle dependencies={['parser_id']}> + {({ getFieldValue }) => { + const parserId = getFieldValue('parser_id'); - if (parserId === 'naive') { - return ( - <Form.Item label="Chunk token number" tooltip="xxx"> - <Flex gap={20} align="center"> - <Flex flex={1}> + if (parserId === 'naive') { + return ( + <Form.Item label="Chunk token number" tooltip="xxx"> + <Flex gap={20} align="center"> + <Flex flex={1}> + <Form.Item + name={['parser_config', 'chunk_token_num']} + noStyle + initialValue={128} + rules={[ + { required: true, message: 'Province is required' }, + ]} + > + <Slider + className={styles.variableSlider} + max={2048} + /> + </Form.Item> + </Flex> <Form.Item name={['parser_config', 'chunk_token_num']} noStyle - initialValue={128} rules={[ - { required: true, message: 'Province is required' }, + { required: true, message: 'Street is required' }, ]} > - <Slider className={styles.variableSlider} max={2048} /> + <InputNumber + className={styles.sliderInputNumber} + max={2048} + min={0} + /> </Form.Item> </Flex> - <Form.Item - name={['parser_config', 'chunk_token_num']} - noStyle - initialValue={128} - rules={[ - { required: true, message: 'Street is required' }, - ]} - > - <InputNumber - className={styles.sliderInputNumber} - max={2048} - min={0} - /> - </Form.Item> - </Flex> - </Form.Item> - ); - } - return null; - }} - </Form.Item> - <Form.Item> - <div className={styles.buttonWrapper}> - <Space> - <Button htmlType="reset" size={'middle'}> - Cancel - </Button> - <Button - htmlType="submit" - type="primary" - size={'middle'} - loading={loading} - > - Save - </Button> - </Space> - </div> - </Form.Item> - </Form> + </Form.Item> + ); + } + return null; + }} + </Form.Item> + <Form.Item> + <div className={styles.buttonWrapper}> + <Space> + <Button htmlType="reset" size={'middle'}> + Cancel + </Button> + <Button + htmlType="submit" + type="primary" + size={'middle'} + loading={submitLoading} + > + Save + </Button> + </Space> + </div> + </Form.Item> + </Form> + </Spin> </div> ); }; 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 0000000..008979d --- /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 1f44996..29126cc 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<KSModelState> = { 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<KSModelState> = { 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 791f1db..637fd89 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 ( <section className={styles.testingControlWrapper}> - <p> + <div> <b>Retrieval testing</b> - </p> + </div> <p>Final step! After success, leave the rest to Infiniflow AI.</p> <Divider></Divider> <section> @@ -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) => { </Form> </section> <section> - <p className={styles.historyTitle}> + <div className={styles.historyTitle}> <Space size={'middle'}> <HistoryOutlined className={styles.historyIcon} /> <b>Test history</b> </Space> - </p> + </div> <Space direction="vertical" size={'middle'} diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx index 65c7dae..329c121 100644 --- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx @@ -1,14 +1,13 @@ +import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; +import { PlusOutlined } from '@ant-design/icons'; import { Form, Input, Select, Upload } from 'antd'; - import classNames from 'classnames'; import { ISegmentedContentProps } from '../interface'; -import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; -import { PlusOutlined } from '@ant-design/icons'; import styles from './index.less'; const AssistantSetting = ({ show }: ISegmentedContentProps) => { - 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 e7cd63e..17a9469 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={ - <Button type="primary" onClick={handlePressEnter} loading={loading}> + <Button + type="primary" + onClick={handlePressEnter} + loading={sendLoading} + disabled={disabled} + > Send </Button> } diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index 877c91a..6da74c4 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 57eb15a..7372f5e 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 b6ee17a..88c3bca 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 = () => { </Button> <Divider></Divider> <Flex className={styles.chatAppContent} vertical gap={10}> - {dialogList.map((x) => ( - <Card - key={x.id} - hoverable - className={classNames(styles.chatAppCard, { - [styles.chatAppCardSelected]: dialogId === x.id, - })} - onMouseEnter={handleAppCardEnter(x.id)} - onMouseLeave={handleItemLeave} - onClick={handleDialogCardClick(x.id)} - > - <Flex justify="space-between" align="center"> - <Space size={15}> - <Avatar src={x.icon} shape={'square'} /> - <section> - <b>{x.name}</b> - <div>{x.description}</div> - </section> - </Space> - {activated === x.id && ( - <section> - <Dropdown menu={{ items: buildAppItems(x.id) }}> - <ChatAppCube className={styles.cubeIcon}></ChatAppCube> - </Dropdown> - </section> - )} - </Flex> - </Card> - ))} + <Spin spinning={dialogLoading} wrapperClassName={styles.chatSpin}> + {dialogList.map((x) => ( + <Card + key={x.id} + hoverable + className={classNames(styles.chatAppCard, { + [styles.chatAppCardSelected]: dialogId === x.id, + })} + onMouseEnter={handleAppCardEnter(x.id)} + onMouseLeave={handleItemLeave} + onClick={handleDialogCardClick(x.id)} + > + <Flex justify="space-between" align="center"> + <Space size={15}> + <Avatar src={x.icon} shape={'square'} /> + <section> + <b>{x.name}</b> + <div>{x.description}</div> + </section> + </Space> + {activated === x.id && ( + <section> + <Dropdown menu={{ items: buildAppItems(x.id) }}> + <ChatAppCube + className={styles.cubeIcon} + ></ChatAppCube> + </Dropdown> + </section> + )} + </Flex> + </Card> + ))} + </Spin> </Flex> </Flex> </Flex> @@ -254,29 +259,38 @@ const Chat = () => { </Flex> <Divider></Divider> <Flex vertical gap={10} className={styles.chatTitleContent}> - {conversationList.map((x) => ( - <Card - key={x.id} - hoverable - onClick={handleConversationCardClick(x.id)} - onMouseEnter={handleConversationCardEnter(x.id)} - onMouseLeave={handleConversationItemLeave} - className={classNames(styles.chatTitleCard, { - [styles.chatTitleCardSelected]: x.id === conversationId, - })} - > - <Flex justify="space-between" align="center"> - <div>{x.name}</div> - {conversationActivated === x.id && x.id !== '' && ( - <section> - <Dropdown menu={{ items: buildConversationItems(x.id) }}> - <ChatAppCube className={styles.cubeIcon}></ChatAppCube> - </Dropdown> - </section> - )} - </Flex> - </Card> - ))} + <Spin + spinning={conversationLoading} + wrapperClassName={styles.chatSpin} + > + {conversationList.map((x) => ( + <Card + key={x.id} + hoverable + onClick={handleConversationCardClick(x.id)} + onMouseEnter={handleConversationCardEnter(x.id)} + onMouseLeave={handleConversationItemLeave} + className={classNames(styles.chatTitleCard, { + [styles.chatTitleCardSelected]: x.id === conversationId, + })} + > + <Flex justify="space-between" align="center"> + <div>{x.name}</div> + {conversationActivated === x.id && x.id !== '' && ( + <section> + <Dropdown + menu={{ items: buildConversationItems(x.id) }} + > + <ChatAppCube + className={styles.cubeIcon} + ></ChatAppCube> + </Dropdown> + </section> + )} + </Flex> + </Card> + ))} + </Spin> </Flex> </Flex> </Flex> diff --git a/web/src/pages/knowledge/index.tsx b/web/src/pages/knowledge/index.tsx index 78f2af5..8b61e0c 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 = () => { </ModalManager> </Space> </div> - <Flex gap={'large'} wrap="wrap" className={styles.knowledgeCardContainer}> - {list.length > 0 ? ( - list.map((item: any) => { - return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>; - }) - ) : ( - <Empty></Empty> - )} - </Flex> + <Spin spinning={loading}> + <Flex + gap={'large'} + wrap="wrap" + className={styles.knowledgeCardContainer} + > + {list.length > 0 ? ( + list.map((item: any) => { + return ( + <KnowledgeCard item={item} key={item.name}></KnowledgeCard> + ); + }) + ) : ( + <Empty></Empty> + )} + </Flex> + </Spin> </Flex> ); }; diff --git a/web/src/pages/user-setting/hooks.ts b/web/src/pages/user-setting/hooks.ts index ee670c6..bb770a4 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 0049cc6..af400bb 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 57cf93e..67b39a8 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 ( <> - <section className={styles.modelWrapper}> - <SettingTitle - title="Model Setting" - description="Manage your account settings and preferences here." - showRightButton - clickButton={showSystemSettingModal} - ></SettingTitle> - <Divider></Divider> - <Collapse defaultActiveKey={['1']} ghost items={items} /> - </section> + <Spin spinning={loading}> + <section className={styles.modelWrapper}> + <SettingTitle + title="Model Setting" + description="Manage your account settings and preferences here." + showRightButton + clickButton={showSystemSettingModal} + ></SettingTitle> + <Divider></Divider> + <Collapse defaultActiveKey={['1']} ghost items={items} /> + </section> + </Spin> <ApiKeyModal visible={apiKeyVisible} hideModal={hideApiKeyModal} diff --git a/web/src/pages/user-setting/setting-profile/index.tsx b/web/src/pages/user-setting/setting-profile/index.tsx index daf9212..64f48a2 100644 --- a/web/src/pages/user-setting/setting-profile/index.tsx +++ b/web/src/pages/user-setting/setting-profile/index.tsx @@ -1,4 +1,8 @@ -import { useSaveSetting, useSelectUserInfo } from '@/hooks/userSettingHook'; +import { + useFetchUserInfo, + useSaveSetting, + useSelectUserInfo, +} from '@/hooks/userSettingHook'; import { getBase64FromUploadFileList, getUploadFileListFromBase64, @@ -12,6 +16,7 @@ import { Input, Select, Space, + Spin, Tooltip, Upload, UploadFile, @@ -19,7 +24,11 @@ import { import { useEffect } from 'react'; import SettingTitle from '../components/setting-title'; import { TimezoneList } from '../constants'; -import { useGetUserInfoLoading, useValidateSubmittable } from '../hooks'; +import { + useSelectSubmitUserInfoLoading, + useSelectUserInfoLoading, + useValidateSubmittable, +} from '../hooks'; import parentStyles from '../index.less'; import styles from './index.less'; @@ -42,8 +51,10 @@ const tailLayout = { const UserSettingProfile = () => { 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." ></SettingTitle> <Divider /> - <Form - colon={false} - name="basic" - labelAlign={'left'} - labelCol={{ span: 8 }} - wrapperCol={{ span: 16 }} - style={{ width: '100%' }} - initialValues={{ remember: true }} - onFinish={onFinish} - onFinishFailed={onFinishFailed} - form={form} - autoComplete="off" - > - <Form.Item<FieldType> - label="Username" - name="nickname" - rules={[ - { - required: true, - message: 'Please input your username!', - whitespace: true, - }, - ]} - > - <Input /> - </Form.Item> - <Divider /> - <Form.Item<FieldType> - label={ - <div> - <Space> - Your photo - <Tooltip title="prompt text"> - <QuestionCircleOutlined /> - </Tooltip> - </Space> - <div>This will be displayed on your profile.</div> - </div> - } - name="avatar" - valuePropName="fileList" - getValueFromEvent={normFile} + <Spin spinning={loading}> + <Form + colon={false} + name="basic" + labelAlign={'left'} + labelCol={{ span: 8 }} + wrapperCol={{ span: 16 }} + style={{ width: '100%' }} + initialValues={{ remember: true }} + onFinish={onFinish} + onFinishFailed={onFinishFailed} + form={form} + autoComplete="off" > - <Upload - listType="picture-card" - maxCount={1} - accept="image/*" - beforeUpload={() => { - return false; - }} - showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + <Form.Item<FieldType> + label="Username" + name="nickname" + rules={[ + { + required: true, + message: 'Please input your username!', + whitespace: true, + }, + ]} > - <button style={{ border: 0, background: 'none' }} type="button"> - <PlusOutlined /> - <div style={{ marginTop: 8 }}>Upload</div> - </button> - </Upload> - </Form.Item> - <Divider /> - <Form.Item<FieldType> - label="Color schema" - name="color_schema" - rules={[ - { required: true, message: 'Please select your color schema!' }, - ]} - > - <Select placeholder="select your color schema"> - <Option value="Bright">Bright</Option> - <Option value="Dark">Dark</Option> - </Select> - </Form.Item> - <Divider /> - <Form.Item<FieldType> - label="Language" - name="language" - rules={[{ required: true, message: 'Please input your language!' }]} - > - <Select placeholder="select your language"> - <Option value="English">English</Option> - <Option value="Chinese">Chinese</Option> - </Select> - </Form.Item> - <Divider /> - <Form.Item<FieldType> - label="Timezone" - name="timezone" - rules={[{ required: true, message: 'Please input your timezone!' }]} - > - <Select placeholder="select your timezone" showSearch> - {TimezoneList.map((x) => ( - <Option value={x} key={x}> - {x} - </Option> - ))} - </Select> - </Form.Item> - <Divider /> - <Form.Item label="Email address"> - <Form.Item<FieldType> name="email" noStyle> - <Input disabled /> + <Input /> </Form.Item> - <p className={parentStyles.itemDescription}> - Once registered, an account cannot be changed and can only be - cancelled. - </p> - </Form.Item> - <Form.Item - {...tailLayout} - shouldUpdate={(prevValues, curValues) => - prevValues.additional !== curValues.additional - } - > - <Space> - <Button htmlType="button">Cancel</Button> - <Button - type="primary" - htmlType="submit" - disabled={!submittable} - loading={loading} + <Divider /> + <Form.Item<FieldType> + label={ + <div> + <Space> + Your photo + <Tooltip title="prompt text"> + <QuestionCircleOutlined /> + </Tooltip> + </Space> + <div>This will be displayed on your profile.</div> + </div> + } + name="avatar" + valuePropName="fileList" + getValueFromEvent={normFile} + > + <Upload + listType="picture-card" + maxCount={1} + accept="image/*" + beforeUpload={() => { + return false; + }} + showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} > - Save - </Button> - </Space> - </Form.Item> - </Form> + <button style={{ border: 0, background: 'none' }} type="button"> + <PlusOutlined /> + <div style={{ marginTop: 8 }}>Upload</div> + </button> + </Upload> + </Form.Item> + <Divider /> + <Form.Item<FieldType> + label="Color schema" + name="color_schema" + rules={[ + { required: true, message: 'Please select your color schema!' }, + ]} + > + <Select placeholder="select your color schema"> + <Option value="Bright">Bright</Option> + <Option value="Dark">Dark</Option> + </Select> + </Form.Item> + <Divider /> + <Form.Item<FieldType> + label="Language" + name="language" + rules={[{ required: true, message: 'Please input your language!' }]} + > + <Select placeholder="select your language"> + <Option value="English">English</Option> + <Option value="Chinese">Chinese</Option> + </Select> + </Form.Item> + <Divider /> + <Form.Item<FieldType> + label="Timezone" + name="timezone" + rules={[{ required: true, message: 'Please input your timezone!' }]} + > + <Select placeholder="select your timezone" showSearch> + {TimezoneList.map((x) => ( + <Option value={x} key={x}> + {x} + </Option> + ))} + </Select> + </Form.Item> + <Divider /> + <Form.Item label="Email address"> + <Form.Item<FieldType> name="email" noStyle> + <Input disabled /> + </Form.Item> + <p className={parentStyles.itemDescription}> + Once registered, an account cannot be changed and can only be + cancelled. + </p> + </Form.Item> + <Form.Item + {...tailLayout} + shouldUpdate={(prevValues, curValues) => + prevValues.additional !== curValues.additional + } + > + <Space> + <Button htmlType="button">Cancel</Button> + <Button + type="primary" + htmlType="submit" + disabled={!submittable} + loading={submitLoading} + > + Save + </Button> + </Space> + </Form.Item> + </Form> + </Spin> </section> ); }; diff --git a/web/src/utils/fileUtil.ts b/web/src/utils/fileUtil.ts index 44e43c6..40b6bd9 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 } -- GitLab