From 5fe8cf60186413355651a21f76219415f5c4bd0f Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 28 Aug 2025 14:45:20 +0800 Subject: [PATCH] Feat: Use AvatarUpload to replace the avatar settings on the dataset and search pages #3221 (#9785) ### What problem does this PR solve? Feat: Use AvatarUpload to replace the avatar settings on the dataset and search pages #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/avatar-upload.tsx | 123 ++++++++++-------- web/src/hooks/use-knowledge-request.ts | 2 +- .../dataset/setting/chunk-method-form.tsx | 2 +- .../pages/dataset/setting/general-form.tsx | 86 +----------- web/src/pages/dataset/setting/hooks.ts | 55 +------- web/src/pages/next-search/search-setting.tsx | 74 +---------- 6 files changed, 83 insertions(+), 259 deletions(-) diff --git a/web/src/components/avatar-upload.tsx b/web/src/components/avatar-upload.tsx index 8df544068..af141b180 100644 --- a/web/src/components/avatar-upload.tsx +++ b/web/src/components/avatar-upload.tsx @@ -1,71 +1,80 @@ import { transformFile2Base64 } from '@/utils/file-util'; import { Pencil, Upload } from 'lucide-react'; -import { ChangeEventHandler, useCallback, useEffect, useState } from 'react'; +import { + ChangeEventHandler, + forwardRef, + useCallback, + useEffect, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; import { Input } from './ui/input'; type AvatarUploadProps = { value?: string; onChange?: (value: string) => void }; -export function AvatarUpload({ value, onChange }: AvatarUploadProps) { - const { t } = useTranslation(); - const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 +export const AvatarUpload = forwardRef( + function AvatarUpload({ value, onChange }, ref) { + const { t } = useTranslation(); + const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 - const handleChange: ChangeEventHandler = useCallback( - async (ev) => { - const file = ev.target?.files?.[0]; - if (/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')) { - const str = await transformFile2Base64(file!); - setAvatarBase64Str(str); - onChange?.(str); + const handleChange: ChangeEventHandler = useCallback( + async (ev) => { + const file = ev.target?.files?.[0]; + if (/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')) { + const str = await transformFile2Base64(file!); + setAvatarBase64Str(str); + onChange?.(str); + } + ev.target.value = ''; + }, + [onChange], + ); + + useEffect(() => { + if (value) { + setAvatarBase64Str(value); } - ev.target.value = ''; - }, - [onChange], - ); + }, [value]); - useEffect(() => { - if (value) { - setAvatarBase64Str(value); - } - }, [value]); - - return ( -
-
- {!avatarBase64Str ? ( -
-
- -

{t('common.upload')}

+ return ( +
+
+ {!avatarBase64Str ? ( +
+
+ +

{t('common.upload')}

+
-
- ) : ( -
- - - - -
- + ) : ( +
+ + + + +
+ +
-
- )} - + )} + +
+
+ {t('knowledgeConfiguration.photoTip')} +
-
- {t('knowledgeConfiguration.photoTip')} -
-
- ); -} + ); + }, +); diff --git a/web/src/hooks/use-knowledge-request.ts b/web/src/hooks/use-knowledge-request.ts index 5c8c60364..cda325ccb 100644 --- a/web/src/hooks/use-knowledge-request.ts +++ b/web/src/hooks/use-knowledge-request.ts @@ -1,4 +1,5 @@ import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; +import message from '@/components/ui/message'; import { IKnowledge, IKnowledgeGraph, @@ -13,7 +14,6 @@ import kbService, { } from '@/services/knowledge-service'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; -import { message } from 'antd'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParams, useSearchParams } from 'umi'; import { diff --git a/web/src/pages/dataset/setting/chunk-method-form.tsx b/web/src/pages/dataset/setting/chunk-method-form.tsx index 711f08368..b5a378993 100644 --- a/web/src/pages/dataset/setting/chunk-method-form.tsx +++ b/web/src/pages/dataset/setting/chunk-method-form.tsx @@ -4,7 +4,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { DocumentParserType } from '@/constants/knowledge'; -import { useUpdateKnowledge } from '@/hooks/knowledge-hooks'; +import { useUpdateKnowledge } from '@/hooks/use-knowledge-request'; import { useMemo } from 'react'; import { useParams } from 'umi'; import { AudioConfiguration } from './configuration/audio'; diff --git a/web/src/pages/dataset/setting/general-form.tsx b/web/src/pages/dataset/setting/general-form.tsx index 3c14534eb..03ea65710 100644 --- a/web/src/pages/dataset/setting/general-form.tsx +++ b/web/src/pages/dataset/setting/general-form.tsx @@ -1,7 +1,7 @@ +import { AvatarUpload } from '@/components/avatar-upload'; import { FormContainer } from '@/components/form-container'; import { SelectWithSearch } from '@/components/originui/select-with-search'; import { RAGFlowFormItem } from '@/components/ragflow-form'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button, ButtonLoading } from '@/components/ui/button'; import { FormControl, @@ -12,10 +12,8 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { PermissionRole } from '@/constants/permission'; -import { useUpdateKnowledge } from '@/hooks/knowledge-hooks'; -import { transformFile2Base64 } from '@/utils/file-util'; -import { Pencil, Upload } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; +import { useUpdateKnowledge } from '@/hooks/use-knowledge-request'; +import { useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useParams } from 'umi'; @@ -23,8 +21,6 @@ import { useParams } from 'umi'; export function GeneralForm() { const form = useFormContext(); const { t } = useTranslation(); - const [avatarFile, setAvatarFile] = useState(null); - const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 const { saveKnowledgeConfiguration, loading: submitLoading } = useUpdateKnowledge(); @@ -43,26 +39,6 @@ export function GeneralForm() { })); }, [t]); - // init avatar file if it exists in defaultValues - useEffect(() => { - if (!avatarFile) { - let avatarList = defaultValues['avatar']; - if (avatarList && avatarList.length > 0) { - setAvatarBase64Str(avatarList[0].thumbUrl); - } - } - }, [avatarFile, defaultValues]); - - // input[type=file] on change event, get img base64 - useEffect(() => { - if (avatarFile) { - (async () => { - // make use of img compression transformFile2Base64 - setAvatarBase64Str(await transformFile2Base64(avatarFile)); - })(); - } - }, [avatarFile]); - return ( <> @@ -90,62 +66,14 @@ export function GeneralForm() { ( + render={({ field }) => (
{t('setting.avatar')} -
-
- {!avatarBase64Str ? ( -
-
- -

{t('common.upload')}

-
-
- ) : ( -
- - - - -
- -
-
- )} - { - const file = ev.target?.files?.[0]; - if ( - /\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '') - ) { - setAvatarFile(file!); - } - ev.target.value = ''; - }} - /> -
-
- {t('knowledgeConfiguration.photoTip')} -
-
+
@@ -209,8 +137,8 @@ export function GeneralForm() { onClick={() => { (async () => { let isValidate = await form.trigger('name'); - const { name, description, permission } = form.getValues(); - const avatar = avatarBase64Str; + const { name, description, permission, avatar } = + form.getValues(); if (isValidate) { saveKnowledgeConfiguration({ diff --git a/web/src/pages/dataset/setting/hooks.ts b/web/src/pages/dataset/setting/hooks.ts index 9e010838f..3332ca3f5 100644 --- a/web/src/pages/dataset/setting/hooks.ts +++ b/web/src/pages/dataset/setting/hooks.ts @@ -2,46 +2,15 @@ import { LlmModelType } from '@/constants/knowledge'; import { useSetModalState } from '@/hooks/common-hooks'; import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; -import { useNavigateToDataset } from '@/hooks/route-hook'; -import { - useFetchKnowledgeBaseConfiguration, - useUpdateKnowledge, -} from '@/hooks/use-knowledge-request'; +import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request'; import { useSelectParserList } from '@/hooks/user-setting-hooks'; -import { - getBase64FromUploadFileList, - getUploadFileListFromBase64, -} from '@/utils/file-util'; import { useIsFetching } from '@tanstack/react-query'; -import { Form, UploadFile } from 'antd'; -import { FormInstance } from 'antd/lib'; import { pick } from 'lodash'; import { useCallback, useEffect, useState } from 'react'; import { UseFormReturn } from 'react-hook-form'; import { z } from 'zod'; import { formSchema } from './form-schema'; -export const useSubmitKnowledgeConfiguration = (form: FormInstance) => { - const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(); - const navigateToDataset = useNavigateToDataset(); - - const submitKnowledgeConfiguration = useCallback(async () => { - const values = await form.validateFields(); - const avatar = await getBase64FromUploadFileList(values.avatar); - saveKnowledgeConfiguration({ - ...values, - avatar, - }); - navigateToDataset(); - }, [saveKnowledgeConfiguration, form, navigateToDataset]); - - return { - submitKnowledgeConfiguration, - submitLoading: loading, - navigateToDataset, - }; -}; - // The value that does not need to be displayed in the analysis method Select const HiddenFields = ['email', 'picture', 'audio']; @@ -67,11 +36,6 @@ export const useFetchKnowledgeConfigurationOnMount = ( const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); useEffect(() => { - const fileList: UploadFile[] = getUploadFileListFromBase64( - knowledgeDetails.avatar, - ); - - console.log('🚀 ~ useEffect ~ fileList:', fileList, knowledgeDetails); const parser_config = { ...form.formState?.defaultValues?.parser_config, ...knowledgeDetails.parser_config, @@ -86,12 +50,10 @@ export const useFetchKnowledgeConfigurationOnMount = ( 'language', 'parser_config', 'pagerank', + 'avatar', ]), }; - form.reset({ - ...formValues, - avatar: fileList, - }); + form.reset(formValues); }, [form, knowledgeDetails]); return knowledgeDetails; @@ -100,17 +62,6 @@ export const useFetchKnowledgeConfigurationOnMount = ( export const useSelectKnowledgeDetailsLoading = () => useIsFetching({ queryKey: ['fetchKnowledgeDetail'] }) > 0; -export const useHandleChunkMethodChange = () => { - const [form] = Form.useForm(); - const chunkMethod = Form.useWatch('parser_id', form); - - useEffect(() => { - console.log('🚀 ~ useHandleChunkMethodChange ~ chunkMethod:', chunkMethod); - }, [chunkMethod]); - - return { form, chunkMethod }; -}; - export const useRenameKnowledgeTag = () => { const [tag, setTag] = useState(''); const { diff --git a/web/src/pages/next-search/search-setting.tsx b/web/src/pages/next-search/search-setting.tsx index 2e99aa61d..4702e5bd8 100644 --- a/web/src/pages/next-search/search-setting.tsx +++ b/web/src/pages/next-search/search-setting.tsx @@ -1,11 +1,11 @@ // src/pages/next-search/search-setting.tsx +import { AvatarUpload } from '@/components/avatar-upload'; import { MetadataFilter, MetadataFilterSchema, } from '@/components/metadata-filter'; import { Input } from '@/components/originui/input'; -import { RAGFlowAvatar } from '@/components/ragflow-avatar'; import { Button } from '@/components/ui/button'; import { SingleFormSlider } from '@/components/ui/dual-range-slider'; import { @@ -31,9 +31,8 @@ import { import { useFetchTenantInfo } from '@/hooks/user-setting-hooks'; import { IKnowledge } from '@/interfaces/database/knowledge'; import { cn } from '@/lib/utils'; -import { transformFile2Base64 } from '@/utils/file-util'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Pencil, Upload, X } from 'lucide-react'; +import { X } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -110,8 +109,6 @@ const SearchSetting: React.FC = ({ resolver: zodResolver(SearchSettingFormSchema), }); - const [avatarFile, setAvatarFile] = useState(null); - const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 const [datasetList, setDatasetList] = useState([]); const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState(''); const { t } = useTranslation(); @@ -174,19 +171,7 @@ const SearchSetting: React.FC = ({ setWidth0('w-[440px]'); } }, [open]); - useEffect(() => { - if (!avatarFile) { - setAvatarBase64Str(data?.avatar); - } - }, [avatarFile, data?.avatar]); - useEffect(() => { - if (avatarFile) { - (async () => { - // make use of img compression transformFile2Base64 - setAvatarBase64Str(await transformFile2Base64(avatarFile)); - })(); - } - }, [avatarFile]); + const { list: datasetListOrigin } = useFetchKnowledgeList(); useEffect(() => { @@ -273,7 +258,6 @@ const SearchSetting: React.FC = ({ llm_setting: { ...llmSetting }, }, tenant_id: systemSetting.tenant_id, - avatar: avatarBase64Str, }); setOpen(false); } catch (error) { @@ -337,59 +321,11 @@ const SearchSetting: React.FC = ({ ( + render={({ field }) => ( {t('search.avatar')} -
-
- {!avatarBase64Str ? ( -
-
- -

{t('common.upload')}

-
-
- ) : ( -
- -
- -
-
- )} - { - const file = ev.target?.files?.[0]; - if ( - /\.(jpg|jpeg|png|webp|bmp)$/i.test( - file?.name ?? '', - ) - ) { - setAvatarFile(file!); - } - ev.target.value = ''; - }} - /> -
- -
- {t('knowledgeConfiguration.photoTip')} -
-
+