mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32: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 { from, to } = 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 [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: {
|
||||
createSearch: 'Create Search',
|
||||
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: {
|
||||
createSearch: '新建查詢',
|
||||
searchGreeting: '今天我能為你做些什麽?',
|
||||
profile: '隱藏個人資料',
|
||||
locale: '語言',
|
||||
embedCode: '嵌入代碼',
|
||||
id: 'ID',
|
||||
copySuccess: '複製成功',
|
||||
welcomeBack: '歡迎回來',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1323,6 +1323,24 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
search: {
|
||||
createSearch: '新建查询',
|
||||
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 { SeeAllAppCard } from './application-card';
|
||||
import { ChatList } from './chat-list';
|
||||
import { SearchList } from './search-list';
|
||||
|
||||
const IconMap = {
|
||||
[Routes.Chats]: 'chat',
|
||||
@ -56,6 +57,7 @@ export function Applications() {
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{val === Routes.Agents && <Agents></Agents>}
|
||||
{val === Routes.Chats && <ChatList></ChatList>}
|
||||
{val === Routes.Searches && <SearchList></SearchList>}
|
||||
{<SeeAllAppCard click={handleNavigate}></SeeAllAppCard>}
|
||||
</div>
|
||||
</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,
|
||||
} from '@/constants/common';
|
||||
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 = {
|
||||
open: any;
|
||||
url: string;
|
||||
token: string;
|
||||
from: string;
|
||||
beta: string;
|
||||
setOpen: (e: any) => void;
|
||||
tenantId: string;
|
||||
};
|
||||
|
||||
const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
const { t } = useTranslate('chat');
|
||||
const { open, setOpen, token = '', from, beta = '', url, tenantId } = props;
|
||||
const { t } = useTranslate('search');
|
||||
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 [locale, setLocale] = useState('');
|
||||
|
||||
@ -69,7 +76,7 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
{/* Hide Avatar Toggle */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">
|
||||
{t('avatarHidden')}
|
||||
{t('profile')}
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<Switch
|
||||
@ -83,7 +90,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
|
||||
{/* Locale Select */}
|
||||
<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
|
||||
placeholder="Select a locale"
|
||||
value={locale}
|
||||
@ -93,7 +102,9 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
</div>
|
||||
{/* Embed Code */}
|
||||
<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"> */}
|
||||
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
|
||||
<HightLightMarkdown>{text}</HightLightMarkdown>
|
||||
@ -102,18 +113,21 @@ const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
|
||||
{/* ID Field */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium mb-2">ID</label>
|
||||
<div className="flex items-center">
|
||||
<label className="block text-sm font-medium mb-2">{t('id')}</label>
|
||||
<div className="flex items-center border border-border rounded-lg bg-bg-base">
|
||||
<input
|
||||
type="text"
|
||||
value={token}
|
||||
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
|
||||
type="button"
|
||||
onClick={() => navigator.clipboard.writeText(token)}
|
||||
className="ml-2 p-2 text-gray-400 hover:text-white transition-colors"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(token);
|
||||
message.success(t('copySuccess'));
|
||||
}}
|
||||
className="ml-2 p-2 hover:text-white transition-colors"
|
||||
title="Copy ID"
|
||||
>
|
||||
<svg
|
||||
|
||||
@ -486,3 +486,16 @@ export const useSearching = ({
|
||||
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 { Send, Settings } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
useFetchSearchDetail,
|
||||
} from '../next-searches/hooks';
|
||||
import EmbedAppModal from './embed-app-modal';
|
||||
import { useCheckSettings } from './hooks';
|
||||
import './index.less';
|
||||
import SearchHome from './search-home';
|
||||
import { SearchSetting } from './search-setting';
|
||||
@ -28,15 +29,20 @@ export default function SearchPage() {
|
||||
const { navigateToSearchList } = useNavigatePage();
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const { data: SearchData } = useFetchSearchDetail();
|
||||
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||
|
||||
const [openSetting, setOpenSetting] = useState(false);
|
||||
const [openEmbed, setOpenEmbed] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
const { t } = useTranslation();
|
||||
const { openSetting: checkOpenSetting } = useCheckSettings(
|
||||
SearchData as ISearchAppDetailProps,
|
||||
);
|
||||
useEffect(() => {
|
||||
handleOperate();
|
||||
}, [handleOperate]);
|
||||
setOpenSetting(checkOpenSetting);
|
||||
}, [checkOpenSetting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSearching) {
|
||||
setOpenSetting(false);
|
||||
@ -60,7 +66,7 @@ export default function SearchPage() {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</PageHeader>
|
||||
<div className="flex gap-3 w-full">
|
||||
<div className="flex gap-3 w-full bg-bg-base">
|
||||
<div className="flex-1">
|
||||
{!isSearching && (
|
||||
<div className="animate-fade-in-down">
|
||||
@ -98,7 +104,6 @@ export default function SearchPage() {
|
||||
url="/next-search/share"
|
||||
token={SearchData?.id as string}
|
||||
from={SharedFrom.Search}
|
||||
beta={beta}
|
||||
tenantId={tenantId}
|
||||
/>
|
||||
}
|
||||
@ -119,7 +124,7 @@ export default function SearchPage() {
|
||||
onClick={() => setOpenEmbed(!openEmbed)}
|
||||
>
|
||||
<Send />
|
||||
<div>Embed App</div>
|
||||
<div>{t('search.embedApp')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
{!isSearching && (
|
||||
@ -130,7 +135,9 @@ export default function SearchPage() {
|
||||
onClick={() => setOpenSetting(!openSetting)}
|
||||
>
|
||||
<Settings className="text-text-secondary" />
|
||||
<div className="text-text-secondary">Search Settings</div>
|
||||
<div className="text-text-secondary">
|
||||
{t('search.searchSettings')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -3,6 +3,7 @@ import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import './index.less';
|
||||
import Spotlight from './spotlight';
|
||||
|
||||
@ -18,6 +19,7 @@ export default function SearchPage({
|
||||
setSearchText: Dispatch<SetStateAction<string>>;
|
||||
}) {
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<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]">
|
||||
@ -36,14 +38,14 @@ export default function SearchPage({
|
||||
<>
|
||||
<p className="mb-4 transition-opacity">👋 Hi there</p>
|
||||
<p className="mb-10 transition-opacity">
|
||||
Welcome back, {userInfo?.nickname}
|
||||
{t('search.welcomeBack')}, {userInfo?.nickname}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="relative w-full ">
|
||||
<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"
|
||||
value={searchText}
|
||||
onKeyUp={(e) => {
|
||||
@ -57,7 +59,7 @@ export default function SearchPage({
|
||||
/>
|
||||
<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={() => {
|
||||
setIsSearching(!isSearching);
|
||||
}}
|
||||
|
||||
@ -28,10 +28,10 @@ import { IKnowledge } from '@/interfaces/database/knowledge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { transformFile2Base64 } from '@/utils/file-util';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from 'i18next';
|
||||
import { Pencil, Upload, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
LlmModelType,
|
||||
@ -113,6 +113,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
||||
const { t } = useTranslation();
|
||||
const resetForm = useCallback(() => {
|
||||
formMethods.reset({
|
||||
search_id: data?.id,
|
||||
@ -305,7 +306,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
style={{ maxHeight: 'calc(100dvh - 170px)' }}
|
||||
>
|
||||
<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)}>
|
||||
<X size={16} className="text-text-primary cursor-pointer" />
|
||||
</div>
|
||||
@ -334,10 +335,11 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Name
|
||||
<span className="text-destructive mr-1"> *</span>
|
||||
{t('search.name')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Name" {...field} />
|
||||
<Input placeholder={t('search.name')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -350,7 +352,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
name="avatar"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<FormLabel>Avatar</FormLabel>
|
||||
<FormLabel>{t('search.avatar')}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative group flex items-end gap-2">
|
||||
<div>
|
||||
@ -413,7 +415,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormLabel>{t('search.description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="You are an intelligent assistant."
|
||||
@ -443,7 +445,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Datasets
|
||||
<span className="text-destructive mr-1"> *</span>
|
||||
{t('search.datasets')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
@ -501,26 +504,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
</div>
|
||||
<FormMessage />
|
||||
</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}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Rerank Model</FormLabel>
|
||||
<FormLabel>{t('search.rerankModel')}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@ -612,7 +595,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>AI Summary</FormLabel>
|
||||
<FormLabel>{t('search.AISummary')}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@ -636,7 +619,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Enable Web Search</FormLabel>
|
||||
<FormLabel>{t('search.enableWebSearch')}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@ -652,7 +635,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Enable Related Search</FormLabel>
|
||||
<FormLabel>{t('search.enableRelatedSearch')}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@ -668,7 +651,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Show Query Mindmap</FormLabel>
|
||||
<FormLabel>{t('search.showQueryMindmap')}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@ -683,9 +666,9 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t('modal.cancelText')}
|
||||
</Button>
|
||||
<Button type="submit">Confirm</Button>
|
||||
<Button type="submit">{t('modal.okText')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@ -9,13 +9,11 @@ import {
|
||||
} from '@/components/ui/popover';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { IReference } from '@/interfaces/database/chat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { TFunction } from 'i18next';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
@ -35,7 +33,6 @@ export default function SearchingView({
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
@ -56,19 +53,17 @@ export default function SearchingView({
|
||||
handleSearch,
|
||||
pagination,
|
||||
onChange,
|
||||
t,
|
||||
}: ISearchReturnProps & {
|
||||
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||
searchData: ISearchAppDetailProps;
|
||||
t: TFunction<'translation', undefined>;
|
||||
}) {
|
||||
const { t: tt, i18n } = useTranslation();
|
||||
useEffect(() => {
|
||||
const changeLanguage = async () => {
|
||||
await i18n.changeLanguage('zh');
|
||||
};
|
||||
changeLanguage();
|
||||
}, [i18n]);
|
||||
const { t } = useTranslation();
|
||||
// useEffect(() => {
|
||||
// const changeLanguage = async () => {
|
||||
// await i18n.changeLanguage('zh');
|
||||
// };
|
||||
// changeLanguage();
|
||||
// }, [i18n]);
|
||||
const [searchtext, setSearchtext] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
@ -105,9 +100,9 @@ export default function SearchingView({
|
||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||
<div className="relative w-full text-primary">
|
||||
<Input
|
||||
placeholder={tt('search.searchGreeting')}
|
||||
placeholder={t('search.searchGreeting')}
|
||||
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}
|
||||
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">
|
||||
<X
|
||||
className="text-text-secondary"
|
||||
className="text-text-secondary cursor-pointer"
|
||||
size={14}
|
||||
onClick={() => {
|
||||
setSearchtext('');
|
||||
handleClickRelatedQuestion('');
|
||||
}}
|
||||
/>
|
||||
<span className="text-text-secondary">|</span>
|
||||
<span className="text-text-secondary ml-4">|</span>
|
||||
<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={() => {
|
||||
if (sendingLoading) {
|
||||
stopOutputMessage();
|
||||
@ -141,7 +137,8 @@ export default function SearchingView({
|
||||
}}
|
||||
>
|
||||
{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" />
|
||||
)}
|
||||
@ -157,10 +154,10 @@ export default function SearchingView({
|
||||
{searchData.search_config.summary && !isSearchStrEmpty && (
|
||||
<>
|
||||
<div className="flex justify-start items-start text-text-primary text-2xl">
|
||||
AI Summary
|
||||
{t('search.AISummary')}
|
||||
</div>
|
||||
{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-2/3 bg-bg-card" />
|
||||
@ -194,76 +191,75 @@ export default function SearchingView({
|
||||
</>
|
||||
)}
|
||||
<div className="mt-3 ">
|
||||
<Spin spinning={loading}>
|
||||
{chunks?.length > 0 && (
|
||||
<>
|
||||
{chunks.map((chunk, index) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={chunk.chunk_id}
|
||||
className="w-full flex flex-col"
|
||||
>
|
||||
<div className="w-full">
|
||||
<ImageWithPopover
|
||||
id={chunk.img_id}
|
||||
></ImageWithPopover>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
`${chunk.highlight}...`,
|
||||
),
|
||||
}}
|
||||
className="text-sm text-text-primary mb-1"
|
||||
></div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="text-text-primary">
|
||||
<HightLightMarkdown>
|
||||
{chunk.content_with_weight}
|
||||
</HightLightMarkdown>
|
||||
</PopoverContent>
|
||||
</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>
|
||||
{chunks?.length > 0 && (
|
||||
<>
|
||||
{chunks.map((chunk, index) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={chunk.chunk_id}
|
||||
className="w-full flex flex-col"
|
||||
>
|
||||
<div className="w-full">
|
||||
<ImageWithPopover
|
||||
id={chunk.img_id}
|
||||
></ImageWithPopover>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
`${chunk.highlight}...`,
|
||||
),
|
||||
}}
|
||||
className="text-sm text-text-primary mb-1"
|
||||
></div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="text-text-primary">
|
||||
<HightLightMarkdown>
|
||||
{chunk.content_with_weight}
|
||||
</HightLightMarkdown>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
{index < chunks.length - 1 && (
|
||||
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
{relatedQuestions?.length > 0 && (
|
||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||
<p className="text-text-primary mb-2 text-xl">
|
||||
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>
|
||||
<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>
|
||||
{index < chunks.length - 1 && (
|
||||
<div className="w-full border-b border-border-default/80 mt-6"></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>
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
import { useSearching } from './hooks';
|
||||
import './index.less';
|
||||
@ -21,13 +20,11 @@ export default function SearchingPage({
|
||||
setIsSearching,
|
||||
setSearchText,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SearchingView
|
||||
{...searchingParam}
|
||||
searchData={searchData}
|
||||
setIsSearching={setIsSearching}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import i18n from '@/locales/config';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
useFetchSearchDetail,
|
||||
@ -9,7 +9,7 @@ import { useGetSharedSearchParams, useSearching } from '../hooks';
|
||||
import '../index.less';
|
||||
import SearchingView from '../search-view';
|
||||
export default function SearchingPage() {
|
||||
const { tenantId, locale } = useGetSharedSearchParams();
|
||||
const { tenantId, locale, visibleAvatar } = useGetSharedSearchParams();
|
||||
const {
|
||||
data: searchData = {
|
||||
search_config: { kb_ids: [] },
|
||||
@ -18,18 +18,25 @@ export default function SearchingPage() {
|
||||
const searchingParam = useSearching({
|
||||
data: searchData,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (locale) {
|
||||
// i18n.changeLanguage(locale);
|
||||
// }
|
||||
// }, [locale, i18n]);
|
||||
useEffect(() => {
|
||||
console.log('locale', locale, i18n.language);
|
||||
if (locale && i18n.language !== locale) {
|
||||
i18n.changeLanguage(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