mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Fix (search): Optimize the search page functionality and UI #3221 - Add a search list component - Implement search settings - Optimize search result display - Add related search functionality - Adjust the search input box style - Unify internationalized text ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -49,7 +49,7 @@ export const RAGFlowAvatar = memo(
|
|||||||
const initials = getInitials(name);
|
const initials = getInitials(name);
|
||||||
const { from, to } = name
|
const { from, to } = name
|
||||||
? getColorForName(name)
|
? getColorForName(name)
|
||||||
: { from: 'hsl(0, 0%, 80%)', to: 'hsl(0, 0%, 30%)' };
|
: { from: 'hsl(0, 0%, 30%)', to: 'hsl(0, 0%, 80%)' };
|
||||||
|
|
||||||
const fallbackRef = useRef<HTMLElement>(null);
|
const fallbackRef = useRef<HTMLElement>(null);
|
||||||
const [fontSize, setFontSize] = useState('0.875rem');
|
const [fontSize, setFontSize] = useState('0.875rem');
|
||||||
|
|||||||
@ -1419,6 +1419,24 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
search: {
|
search: {
|
||||||
createSearch: 'Create Search',
|
createSearch: 'Create Search',
|
||||||
searchGreeting: 'How can I help you today ?',
|
searchGreeting: 'How can I help you today ?',
|
||||||
|
profile: 'Hide Profile',
|
||||||
|
locale: 'Locale',
|
||||||
|
embedCode: 'Embed code',
|
||||||
|
id: 'ID',
|
||||||
|
copySuccess: 'Copy Success',
|
||||||
|
welcomeBack: 'Welcome back',
|
||||||
|
searchSettings: 'Search Settings',
|
||||||
|
name: 'Name',
|
||||||
|
avatar: 'Avatar',
|
||||||
|
description: 'Description',
|
||||||
|
datasets: 'Datasets',
|
||||||
|
rerankModel: 'Rerank Model',
|
||||||
|
AISummary: 'AI Summary',
|
||||||
|
enableWebSearch: 'Enable Web Search',
|
||||||
|
enableRelatedSearch: 'Enable Related Search',
|
||||||
|
showQueryMindmap: 'Show Query Mindmap',
|
||||||
|
embedApp: 'Embed App',
|
||||||
|
relatedSearch: 'Related Search',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1192,6 +1192,12 @@ export default {
|
|||||||
search: {
|
search: {
|
||||||
createSearch: '新建查詢',
|
createSearch: '新建查詢',
|
||||||
searchGreeting: '今天我能為你做些什麽?',
|
searchGreeting: '今天我能為你做些什麽?',
|
||||||
|
profile: '隱藏個人資料',
|
||||||
|
locale: '語言',
|
||||||
|
embedCode: '嵌入代碼',
|
||||||
|
id: 'ID',
|
||||||
|
copySuccess: '複製成功',
|
||||||
|
welcomeBack: '歡迎回來',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1323,6 +1323,24 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
search: {
|
search: {
|
||||||
createSearch: '新建查询',
|
createSearch: '新建查询',
|
||||||
searchGreeting: '今天我能为你做些什么?',
|
searchGreeting: '今天我能为你做些什么?',
|
||||||
|
profile: '隐藏个人资料',
|
||||||
|
locale: '语言',
|
||||||
|
embedCode: '嵌入代码',
|
||||||
|
id: 'ID',
|
||||||
|
copySuccess: '复制成功',
|
||||||
|
welcomeBack: '欢迎回来',
|
||||||
|
searchSettings: '搜索设置',
|
||||||
|
name: '姓名',
|
||||||
|
avatar: '头像',
|
||||||
|
description: '描述',
|
||||||
|
datasets: '数据集',
|
||||||
|
rerankModel: 'rerank 模型',
|
||||||
|
AISummary: 'AI 总结',
|
||||||
|
enableWebSearch: '启用网页搜索',
|
||||||
|
enableRelatedSearch: '启用相关搜索',
|
||||||
|
showQueryMindmap: '显示查询思维导图',
|
||||||
|
embedApp: '嵌入网站',
|
||||||
|
relatedSearch: '相关搜索',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useNavigate } from 'umi';
|
|||||||
import { Agents } from './agent-list';
|
import { Agents } from './agent-list';
|
||||||
import { SeeAllAppCard } from './application-card';
|
import { SeeAllAppCard } from './application-card';
|
||||||
import { ChatList } from './chat-list';
|
import { ChatList } from './chat-list';
|
||||||
|
import { SearchList } from './search-list';
|
||||||
|
|
||||||
const IconMap = {
|
const IconMap = {
|
||||||
[Routes.Chats]: 'chat',
|
[Routes.Chats]: 'chat',
|
||||||
@ -56,6 +57,7 @@ export function Applications() {
|
|||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{val === Routes.Agents && <Agents></Agents>}
|
{val === Routes.Agents && <Agents></Agents>}
|
||||||
{val === Routes.Chats && <ChatList></ChatList>}
|
{val === Routes.Chats && <ChatList></ChatList>}
|
||||||
|
{val === Routes.Searches && <SearchList></SearchList>}
|
||||||
{<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>}
|
{<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
15
web/src/pages/home/search-list.tsx
Normal file
15
web/src/pages/home/search-list.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useFetchSearchList } from '../next-searches/hooks';
|
||||||
|
import { ApplicationCard } from './application-card';
|
||||||
|
|
||||||
|
export function SearchList() {
|
||||||
|
const { data } = useFetchSearchList();
|
||||||
|
|
||||||
|
return data?.data.search_apps
|
||||||
|
.slice(0, 10)
|
||||||
|
.map((x) => (
|
||||||
|
<ApplicationCard
|
||||||
|
key={x.id}
|
||||||
|
app={{ avatar: x.avatar, title: x.name, update_time: x.update_time }}
|
||||||
|
></ApplicationCard>
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -7,21 +7,28 @@ import {
|
|||||||
LanguageAbbreviationMap,
|
LanguageAbbreviationMap,
|
||||||
} from '@/constants/common';
|
} from '@/constants/common';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { message } from 'antd';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
||||||
|
|
||||||
type IEmbedAppModalProps = {
|
type IEmbedAppModalProps = {
|
||||||
open: any;
|
open: any;
|
||||||
url: string;
|
url: string;
|
||||||
token: string;
|
token: string;
|
||||||
from: string;
|
from: string;
|
||||||
beta: string;
|
|
||||||
setOpen: (e: any) => void;
|
setOpen: (e: any) => void;
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||||
const { t } = useTranslate('chat');
|
const { t } = useTranslate('search');
|
||||||
const { open, setOpen, token = '', from, beta = '', url, tenantId } = props;
|
const { open, setOpen, token = '', from, url, tenantId } = props;
|
||||||
|
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && !beta) {
|
||||||
|
handleOperate();
|
||||||
|
}
|
||||||
|
}, [handleOperate, open, beta]);
|
||||||
const [hideAvatar, setHideAvatar] = useState(false);
|
const [hideAvatar, setHideAvatar] = useState(false);
|
||||||
const [locale, setLocale] = useState('');
|
const [locale, setLocale] = useState('');
|
||||||
|
|
||||||
@ -69,7 +76,7 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
|||||||
{/* Hide Avatar Toggle */}
|
{/* Hide Avatar Toggle */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium mb-2">
|
<label className="block text-sm font-medium mb-2">
|
||||||
{t('avatarHidden')}
|
{t('profile')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Switch
|
<Switch
|
||||||
@ -83,7 +90,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
|||||||
|
|
||||||
{/* Locale Select */}
|
{/* Locale Select */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium mb-2">Locale</label>
|
<label className="block text-sm font-medium mb-2">
|
||||||
|
{t('locale')}
|
||||||
|
</label>
|
||||||
<RAGFlowSelect
|
<RAGFlowSelect
|
||||||
placeholder="Select a locale"
|
placeholder="Select a locale"
|
||||||
value={locale}
|
value={locale}
|
||||||
@ -93,7 +102,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Embed Code */}
|
{/* Embed Code */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium mb-2">Embed code</label>
|
<label className="block text-sm font-medium mb-2">
|
||||||
|
{t('embedCode')}
|
||||||
|
</label>
|
||||||
{/* <div className=" border rounded-lg"> */}
|
{/* <div className=" border rounded-lg"> */}
|
||||||
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
|
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
|
||||||
<HightLightMarkdown>{text}</HightLightMarkdown>
|
<HightLightMarkdown>{text}</HightLightMarkdown>
|
||||||
@ -102,18 +113,21 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
|||||||
|
|
||||||
{/* ID Field */}
|
{/* ID Field */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium mb-2">ID</label>
|
<label className="block text-sm font-medium mb-2">{t('id')}</label>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center border border-border rounded-lg bg-bg-base">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={token}
|
value={token}
|
||||||
readOnly
|
readOnly
|
||||||
className="flex-1 px-4 py-2 border border-gray-700 rounded-lg bg-bg-base focus:outline-none"
|
className="flex-1 px-4 py-2 focus:outline-none bg-bg-base rounded-lg"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigator.clipboard.writeText(token)}
|
onClick={() => {
|
||||||
className="ml-2 p-2 text-gray-400 hover:text-white transition-colors"
|
navigator.clipboard.writeText(token);
|
||||||
|
message.success(t('copySuccess'));
|
||||||
|
}}
|
||||||
|
className="ml-2 p-2 hover:text-white transition-colors"
|
||||||
title="Copy ID"
|
title="Copy ID"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@ -486,3 +486,16 @@ export const useSearching = ({
|
|||||||
onChange,
|
onChange,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCheckSettings = (data: ISearchAppDetailProps) => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
openSetting: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { search_config, name } = data;
|
||||||
|
const { kb_ids } = search_config;
|
||||||
|
return {
|
||||||
|
openSetting: kb_ids && kb_ids.length && name ? false : true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -13,12 +13,13 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|||||||
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { Send, Settings } from 'lucide-react';
|
import { Send, Settings } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
useFetchSearchDetail,
|
useFetchSearchDetail,
|
||||||
} from '../next-searches/hooks';
|
} from '../next-searches/hooks';
|
||||||
import EmbedAppModal from './embed-app-modal';
|
import EmbedAppModal from './embed-app-modal';
|
||||||
|
import { useCheckSettings } from './hooks';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import SearchHome from './search-home';
|
import SearchHome from './search-home';
|
||||||
import { SearchSetting } from './search-setting';
|
import { SearchSetting } from './search-setting';
|
||||||
@ -28,15 +29,20 @@ export default function SearchPage() {
|
|||||||
const { navigateToSearchList } = useNavigatePage();
|
const { navigateToSearchList } = useNavigatePage();
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const { data: SearchData } = useFetchSearchDetail();
|
const { data: SearchData } = useFetchSearchDetail();
|
||||||
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
|
||||||
const [openSetting, setOpenSetting] = useState(false);
|
const [openSetting, setOpenSetting] = useState(false);
|
||||||
const [openEmbed, setOpenEmbed] = useState(false);
|
const [openEmbed, setOpenEmbed] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const { data: tenantInfo } = useFetchTenantInfo();
|
const { data: tenantInfo } = useFetchTenantInfo();
|
||||||
const tenantId = tenantInfo.tenant_id;
|
const tenantId = tenantInfo.tenant_id;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { openSetting: checkOpenSetting } = useCheckSettings(
|
||||||
|
SearchData as ISearchAppDetailProps,
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleOperate();
|
setOpenSetting(checkOpenSetting);
|
||||||
}, [handleOperate]);
|
}, [checkOpenSetting]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
setOpenSetting(false);
|
setOpenSetting(false);
|
||||||
@ -60,7 +66,7 @@ export default function SearchPage() {
|
|||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div className="flex gap-3 w-full">
|
<div className="flex gap-3 w-full bg-bg-base">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{!isSearching && (
|
{!isSearching && (
|
||||||
<div className="animate-fade-in-down">
|
<div className="animate-fade-in-down">
|
||||||
@ -98,7 +104,6 @@ export default function SearchPage() {
|
|||||||
url="/next-search/share"
|
url="/next-search/share"
|
||||||
token={SearchData?.id as string}
|
token={SearchData?.id as string}
|
||||||
from={SharedFrom.Search}
|
from={SharedFrom.Search}
|
||||||
beta={beta}
|
|
||||||
tenantId={tenantId}
|
tenantId={tenantId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -119,7 +124,7 @@ export default function SearchPage() {
|
|||||||
onClick={() => setOpenEmbed(!openEmbed)}
|
onClick={() => setOpenEmbed(!openEmbed)}
|
||||||
>
|
>
|
||||||
<Send />
|
<Send />
|
||||||
<div>Embed App</div>
|
<div>{t('search.embedApp')}</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{!isSearching && (
|
{!isSearching && (
|
||||||
@ -130,7 +135,9 @@ export default function SearchPage() {
|
|||||||
onClick={() => setOpenSetting(!openSetting)}
|
onClick={() => setOpenSetting(!openSetting)}
|
||||||
>
|
>
|
||||||
<Settings className="text-text-secondary" />
|
<Settings className="text-text-secondary" />
|
||||||
<div className="text-text-secondary">Search Settings</div>
|
<div className="text-text-secondary">
|
||||||
|
{t('search.searchSettings')}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import Spotlight from './spotlight';
|
import Spotlight from './spotlight';
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ export default function SearchPage({
|
|||||||
setSearchText: Dispatch<SetStateAction<string>>;
|
setSearchText: Dispatch<SetStateAction<string>>;
|
||||||
}) {
|
}) {
|
||||||
const { data: userInfo } = useFetchUserInfo();
|
const { data: userInfo } = useFetchUserInfo();
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<section className="relative w-full flex transition-all justify-center items-center mt-32">
|
<section className="relative w-full flex transition-all justify-center items-center mt-32">
|
||||||
<div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]">
|
<div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]">
|
||||||
@ -36,14 +38,14 @@ export default function SearchPage({
|
|||||||
<>
|
<>
|
||||||
<p className="mb-4 transition-opacity">👋 Hi there</p>
|
<p className="mb-4 transition-opacity">👋 Hi there</p>
|
||||||
<p className="mb-10 transition-opacity">
|
<p className="mb-10 transition-opacity">
|
||||||
Welcome back, {userInfo?.nickname}
|
{t('search.welcomeBack')}, {userInfo?.nickname}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="relative w-full ">
|
<div className="relative w-full ">
|
||||||
<Input
|
<Input
|
||||||
placeholder="How can I help you today?"
|
placeholder={t('search.searchGreeting')}
|
||||||
className="w-full rounded-full py-6 px-4 pr-10 text-text-primary text-lg bg-bg-base delay-700"
|
className="w-full rounded-full py-6 px-4 pr-10 text-text-primary text-lg bg-bg-base delay-700"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onKeyUp={(e) => {
|
onKeyUp={(e) => {
|
||||||
@ -57,7 +59,7 @@ export default function SearchPage({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 transform rounded-full bg-white p-2 text-gray-800 shadow w-12"
|
className="absolute right-2 top-1/2 -translate-y-1/2 transform rounded-full bg-text-primary p-2 text-bg-base shadow w-12"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsSearching(!isSearching);
|
setIsSearching(!isSearching);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -28,10 +28,10 @@ import { IKnowledge } from '@/interfaces/database/knowledge';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { transformFile2Base64 } from '@/utils/file-util';
|
import { transformFile2Base64 } from '@/utils/file-util';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { t } from 'i18next';
|
|
||||||
import { Pencil, Upload, X } from 'lucide-react';
|
import { Pencil, Upload, X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
LlmModelType,
|
LlmModelType,
|
||||||
@ -113,6 +113,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||||
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
||||||
|
const { t } = useTranslation();
|
||||||
const resetForm = useCallback(() => {
|
const resetForm = useCallback(() => {
|
||||||
formMethods.reset({
|
formMethods.reset({
|
||||||
search_id: data?.id,
|
search_id: data?.id,
|
||||||
@ -305,7 +306,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
style={{ maxHeight: 'calc(100dvh - 170px)' }}
|
style={{ maxHeight: 'calc(100dvh - 170px)' }}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center text-base mb-8">
|
<div className="flex justify-between items-center text-base mb-8">
|
||||||
<div className="text-text-primary">Search Settings</div>
|
<div className="text-text-primary">{t('search.searchSettings')}</div>
|
||||||
<div onClick={() => setOpen(false)}>
|
<div onClick={() => setOpen(false)}>
|
||||||
<X size={16} className="text-text-primary cursor-pointer" />
|
<X size={16} className="text-text-primary cursor-pointer" />
|
||||||
</div>
|
</div>
|
||||||
@ -334,10 +335,11 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
<span className="text-destructive mr-1"> *</span>Name
|
<span className="text-destructive mr-1"> *</span>
|
||||||
|
{t('search.name')}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Name" {...field} />
|
<Input placeholder={t('search.name')} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -350,7 +352,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
name="avatar"
|
name="avatar"
|
||||||
render={() => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Avatar</FormLabel>
|
<FormLabel>{t('search.avatar')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative group flex items-end gap-2">
|
<div className="relative group flex items-end gap-2">
|
||||||
<div>
|
<div>
|
||||||
@ -413,7 +415,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
name="description"
|
name="description"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>{t('search.description')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="You are an intelligent assistant."
|
placeholder="You are an intelligent assistant."
|
||||||
@ -443,7 +445,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
<span className="text-destructive mr-1"> *</span>Datasets
|
<span className="text-destructive mr-1"> *</span>
|
||||||
|
{t('search.datasets')}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -501,26 +504,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
// <FormItem className="flex flex-col">
|
|
||||||
// <FormLabel>
|
|
||||||
// <span className="text-destructive mr-1"> *</span>Keyword
|
|
||||||
// Similarity Weight
|
|
||||||
// </FormLabel>
|
|
||||||
// <FormControl>
|
|
||||||
// {/* <div className="flex justify-between items-center">
|
|
||||||
// <SingleFormSlider
|
|
||||||
// max={100}
|
|
||||||
// step={1}
|
|
||||||
// value={field.value as number}
|
|
||||||
// onChange={(values) => field.onChange(values)}
|
|
||||||
// ></SingleFormSlider>
|
|
||||||
// <Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
|
||||||
// {field.value}
|
|
||||||
// </Label>
|
|
||||||
// </div> */}
|
|
||||||
// </FormControl>
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -536,7 +519,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>Rerank Model</FormLabel>
|
<FormLabel>{t('search.rerankModel')}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -612,7 +595,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>AI Summary</FormLabel>
|
<FormLabel>{t('search.AISummary')}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -636,7 +619,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>Enable Web Search</FormLabel>
|
<FormLabel>{t('search.enableWebSearch')}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -652,7 +635,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>Enable Related Search</FormLabel>
|
<FormLabel>{t('search.enableRelatedSearch')}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -668,7 +651,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>Show Query Mindmap</FormLabel>
|
<FormLabel>{t('search.showQueryMindmap')}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -683,9 +666,9 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('modal.cancelText')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">Confirm</Button>
|
<Button type="submit">{t('modal.okText')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@ -9,13 +9,11 @@ import {
|
|||||||
} from '@/components/ui/popover';
|
} from '@/components/ui/popover';
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Spin } from '@/components/ui/spin';
|
|
||||||
import { IReference } from '@/interfaces/database/chat';
|
import { IReference } from '@/interfaces/database/chat';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { TFunction } from 'i18next';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { BrainCircuit, Search, Square, X } from 'lucide-react';
|
import { BrainCircuit, Search, X } from 'lucide-react';
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
@ -35,7 +33,6 @@ export default function SearchingView({
|
|||||||
answer,
|
answer,
|
||||||
sendingLoading,
|
sendingLoading,
|
||||||
relatedQuestions,
|
relatedQuestions,
|
||||||
loading,
|
|
||||||
isFirstRender,
|
isFirstRender,
|
||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
isSearchStrEmpty,
|
isSearchStrEmpty,
|
||||||
@ -56,19 +53,17 @@ export default function SearchingView({
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
pagination,
|
pagination,
|
||||||
onChange,
|
onChange,
|
||||||
t,
|
|
||||||
}: ISearchReturnProps & {
|
}: ISearchReturnProps & {
|
||||||
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||||
searchData: ISearchAppDetailProps;
|
searchData: ISearchAppDetailProps;
|
||||||
t: TFunction<'translation', undefined>;
|
|
||||||
}) {
|
}) {
|
||||||
const { t: tt, i18n } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const changeLanguage = async () => {
|
// const changeLanguage = async () => {
|
||||||
await i18n.changeLanguage('zh');
|
// await i18n.changeLanguage('zh');
|
||||||
};
|
// };
|
||||||
changeLanguage();
|
// changeLanguage();
|
||||||
}, [i18n]);
|
// }, [i18n]);
|
||||||
const [searchtext, setSearchtext] = useState<string>('');
|
const [searchtext, setSearchtext] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -105,9 +100,9 @@ export default function SearchingView({
|
|||||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||||
<div className="relative w-full text-primary">
|
<div className="relative w-full text-primary">
|
||||||
<Input
|
<Input
|
||||||
placeholder={tt('search.searchGreeting')}
|
placeholder={t('search.searchGreeting')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-bg-base',
|
||||||
)}
|
)}
|
||||||
value={searchtext}
|
value={searchtext}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -122,16 +117,17 @@ export default function SearchingView({
|
|||||||
/>
|
/>
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
||||||
<X
|
<X
|
||||||
className="text-text-secondary"
|
className="text-text-secondary cursor-pointer"
|
||||||
size={14}
|
size={14}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setSearchtext('');
|
||||||
handleClickRelatedQuestion('');
|
handleClickRelatedQuestion('');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-text-secondary">|</span>
|
<span className="text-text-secondary ml-4">|</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
className="rounded-full bg-text-primary p-1 text-bg-base shadow w-12 h-8 ml-4"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (sendingLoading) {
|
if (sendingLoading) {
|
||||||
stopOutputMessage();
|
stopOutputMessage();
|
||||||
@ -141,7 +137,8 @@ export default function SearchingView({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sendingLoading ? (
|
{sendingLoading ? (
|
||||||
<Square size={22} className="m-auto" />
|
// <Square size={22} className="m-auto" />
|
||||||
|
<div className="w-2 h-2 bg-bg-base m-auto"></div>
|
||||||
) : (
|
) : (
|
||||||
<Search size={22} className="m-auto" />
|
<Search size={22} className="m-auto" />
|
||||||
)}
|
)}
|
||||||
@ -157,10 +154,10 @@ export default function SearchingView({
|
|||||||
{searchData.search_config.summary && !isSearchStrEmpty && (
|
{searchData.search_config.summary && !isSearchStrEmpty && (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-start items-start text-text-primary text-2xl">
|
<div className="flex justify-start items-start text-text-primary text-2xl">
|
||||||
AI Summary
|
{t('search.AISummary')}
|
||||||
</div>
|
</div>
|
||||||
{isEmpty(answer) && sendingLoading ? (
|
{isEmpty(answer) && sendingLoading ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 mt-2">
|
||||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||||
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
||||||
@ -194,76 +191,75 @@ export default function SearchingView({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 ">
|
<div className="mt-3 ">
|
||||||
<Spin spinning={loading}>
|
{chunks?.length > 0 && (
|
||||||
{chunks?.length > 0 && (
|
<>
|
||||||
<>
|
{chunks.map((chunk, index) => {
|
||||||
{chunks.map((chunk, index) => {
|
return (
|
||||||
return (
|
<>
|
||||||
<>
|
<div
|
||||||
<div
|
key={chunk.chunk_id}
|
||||||
key={chunk.chunk_id}
|
className="w-full flex flex-col"
|
||||||
className="w-full flex flex-col"
|
>
|
||||||
>
|
<div className="w-full">
|
||||||
<div className="w-full">
|
<ImageWithPopover
|
||||||
<ImageWithPopover
|
id={chunk.img_id}
|
||||||
id={chunk.img_id}
|
></ImageWithPopover>
|
||||||
></ImageWithPopover>
|
<Popover>
|
||||||
<Popover>
|
<PopoverTrigger asChild>
|
||||||
<PopoverTrigger asChild>
|
<div
|
||||||
<div
|
dangerouslySetInnerHTML={{
|
||||||
dangerouslySetInnerHTML={{
|
__html: DOMPurify.sanitize(
|
||||||
__html: DOMPurify.sanitize(
|
`${chunk.highlight}...`,
|
||||||
`${chunk.highlight}...`,
|
),
|
||||||
),
|
}}
|
||||||
}}
|
className="text-sm text-text-primary mb-1"
|
||||||
className="text-sm text-text-primary mb-1"
|
></div>
|
||||||
></div>
|
</PopoverTrigger>
|
||||||
</PopoverTrigger>
|
<PopoverContent className="text-text-primary">
|
||||||
<PopoverContent className="text-text-primary">
|
<HightLightMarkdown>
|
||||||
<HightLightMarkdown>
|
{chunk.content_with_weight}
|
||||||
{chunk.content_with_weight}
|
</HightLightMarkdown>
|
||||||
</HightLightMarkdown>
|
</PopoverContent>
|
||||||
</PopoverContent>
|
</Popover>
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
|
||||||
onClick={() =>
|
|
||||||
clickDocumentButton(chunk.doc_id, chunk as any)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
|
||||||
{chunk.docnm_kwd}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{index < chunks.length - 1 && (
|
<div
|
||||||
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
||||||
)}
|
onClick={() =>
|
||||||
</>
|
clickDocumentButton(chunk.doc_id, chunk as any)
|
||||||
);
|
}
|
||||||
})}
|
>
|
||||||
</>
|
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
||||||
)}
|
{chunk.docnm_kwd}
|
||||||
</Spin>
|
</div>
|
||||||
{relatedQuestions?.length > 0 && (
|
</div>
|
||||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
{index < chunks.length - 1 && (
|
||||||
<p className="text-text-primary mb-2 text-xl">
|
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
||||||
Related Search
|
)}
|
||||||
</p>
|
</>
|
||||||
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
);
|
||||||
{relatedQuestions?.map((x, idx) => (
|
})}
|
||||||
<Button
|
</>
|
||||||
key={idx}
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
onClick={handleClickRelatedQuestion(x)}
|
|
||||||
>
|
|
||||||
Related Search{x}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
{relatedQuestions?.length > 0 &&
|
||||||
|
searchData.search_config.related_search && (
|
||||||
|
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||||
|
<p className="text-text-primary mb-2 text-xl">
|
||||||
|
{t('relatedSearch')}
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
||||||
|
{relatedQuestions?.map((x, idx) => (
|
||||||
|
<Button
|
||||||
|
key={idx}
|
||||||
|
variant="transparent"
|
||||||
|
className="bg-bg-card text-text-secondary"
|
||||||
|
onClick={handleClickRelatedQuestion(x)}
|
||||||
|
>
|
||||||
|
{x}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
import { useSearching } from './hooks';
|
import { useSearching } from './hooks';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
@ -21,13 +20,11 @@ export default function SearchingPage({
|
|||||||
setIsSearching,
|
setIsSearching,
|
||||||
setSearchText,
|
setSearchText,
|
||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<SearchingView
|
<SearchingView
|
||||||
{...searchingParam}
|
{...searchingParam}
|
||||||
searchData={searchData}
|
searchData={searchData}
|
||||||
setIsSearching={setIsSearching}
|
setIsSearching={setIsSearching}
|
||||||
t={t}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
useFetchSearchDetail,
|
useFetchSearchDetail,
|
||||||
@ -9,7 +9,7 @@ import { useGetSharedSearchParams, useSearching } from '../hooks';
|
|||||||
import '../index.less';
|
import '../index.less';
|
||||||
import SearchingView from '../search-view';
|
import SearchingView from '../search-view';
|
||||||
export default function SearchingPage() {
|
export default function SearchingPage() {
|
||||||
const { tenantId, locale } = useGetSharedSearchParams();
|
const { tenantId, locale, visibleAvatar } = useGetSharedSearchParams();
|
||||||
const {
|
const {
|
||||||
data: searchData = {
|
data: searchData = {
|
||||||
search_config: { kb_ids: [] },
|
search_config: { kb_ids: [] },
|
||||||
@ -18,18 +18,25 @@ export default function SearchingPage() {
|
|||||||
const searchingParam = useSearching({
|
const searchingParam = useSearching({
|
||||||
data: searchData,
|
data: searchData,
|
||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (locale) {
|
|
||||||
// i18n.changeLanguage(locale);
|
|
||||||
// }
|
|
||||||
// }, [locale, i18n]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('locale', locale, i18n.language);
|
console.log('locale', locale, i18n.language);
|
||||||
if (locale && i18n.language !== locale) {
|
if (locale && i18n.language !== locale) {
|
||||||
i18n.changeLanguage(locale);
|
i18n.changeLanguage(locale);
|
||||||
}
|
}
|
||||||
}, [locale]);
|
}, [locale]);
|
||||||
return <SearchingView {...searchingParam} searchData={searchData} t={t} />;
|
return (
|
||||||
|
<>
|
||||||
|
{visibleAvatar && (
|
||||||
|
<div className="flex justify-start items-center gap-1 mx-6 mt-6 text-text-primary">
|
||||||
|
<RAGFlowAvatar
|
||||||
|
avatar={searchData.avatar}
|
||||||
|
name={searchData.name}
|
||||||
|
></RAGFlowAvatar>
|
||||||
|
<div>{searchData.name}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<SearchingView {...searchingParam} searchData={searchData} />;
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user