From 2b9ed935f3d6884fcc772bfcc0f00668bf055064 Mon Sep 17 00:00:00 2001
From: chanx <1243304602@qq.com>
Date: Tue, 19 Aug 2025 09:39:48 +0800
Subject: [PATCH] feat(search): Optimized search functionality and user
interface #3221 (#9535)
### What problem does this PR solve?
feat(search): Optimized search functionality and user interface #3221
### Type of change
- Added similarity threshold adjustment function
- Optimized mind map display logic
- Adjusted search settings interface layout
- Fixed related search and document viewing functions
- Optimized time display and node selection logic
- [x] Bug Fix (non-breaking change which fixes an issue)
---
web/src/interfaces/request/chat.ts | 1 +
web/src/layouts/next-header.tsx | 6 +-
web/src/locales/en.ts | 2 +
web/src/locales/zh.ts | 2 +
.../node/dropdown/next-step-dropdown.tsx | 2 +-
.../agent/log-sheet/tool-timeline-item.tsx | 5 +-
.../agent/log-sheet/workflow-timeline.tsx | 21 +++++-
web/src/pages/next-search/embed-app-modal.tsx | 13 ++--
web/src/pages/next-search/hooks.ts | 39 +++++++---
web/src/pages/next-search/index.tsx | 19 ++++-
.../next-search/markdown-content/index.tsx | 40 +++++------
web/src/pages/next-search/mindmap-drawer.tsx | 2 +-
web/src/pages/next-search/search-home.tsx | 12 +++-
web/src/pages/next-search/search-setting.tsx | 53 +++++++++++---
web/src/pages/next-search/search-view.tsx | 2 +-
web/src/pages/next-search/share/index.tsx | 35 +++++++--
web/src/pages/next-searches/hooks.ts | 71 ++++++++++---------
web/src/pages/search/hooks.ts | 10 ++-
18 files changed, 231 insertions(+), 104 deletions(-)
diff --git a/web/src/interfaces/request/chat.ts b/web/src/interfaces/request/chat.ts
index a61af9fb6..6ae1e1c0f 100644
--- a/web/src/interfaces/request/chat.ts
+++ b/web/src/interfaces/request/chat.ts
@@ -7,4 +7,5 @@ export interface IFeedbackRequestBody {
export interface IAskRequestBody {
question: string;
kb_ids: string[];
+ search_id?: string;
}
diff --git a/web/src/layouts/next-header.tsx b/web/src/layouts/next-header.tsx
index c798d6e99..73fbf41ae 100644
--- a/web/src/layouts/next-header.tsx
+++ b/web/src/layouts/next-header.tsx
@@ -1,6 +1,5 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { useTheme } from '@/components/theme-provider';
-import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
@@ -163,9 +162,10 @@ export function Header() {
className="size-8 cursor-pointer"
onClick={navigateToProfile}
>
-
+ {/* Temporarily hidden */}
+ {/*
Pro
-
+ */}
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index fc90c4f95..3e855bdae 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -1437,6 +1437,8 @@ This delimiter is used to split the input text into several text pieces echo of
showQueryMindmap: 'Show Query Mindmap',
embedApp: 'Embed App',
relatedSearch: 'Related Search',
+ okText: 'Save',
+ cancelText: 'Cancel',
},
},
};
diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts
index aa2251534..0aa38a7e6 100644
--- a/web/src/locales/zh.ts
+++ b/web/src/locales/zh.ts
@@ -1341,6 +1341,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
showQueryMindmap: '显示查询思维导图',
embedApp: '嵌入网站',
relatedSearch: '相关搜索',
+ okText: '保存',
+ cancelText: '返回',
},
},
};
diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
index 9d84cedc8..f1ae8df1b 100644
--- a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
+++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
@@ -242,7 +242,7 @@ export function InnerNextStepDropdown({
}}
onClick={(e) => e.stopPropagation()}
>
-
+
diff --git a/web/src/pages/agent/log-sheet/tool-timeline-item.tsx b/web/src/pages/agent/log-sheet/tool-timeline-item.tsx
index 0ee59cf5b..c79ba3a6d 100644
--- a/web/src/pages/agent/log-sheet/tool-timeline-item.tsx
+++ b/web/src/pages/agent/log-sheet/tool-timeline-item.tsx
@@ -158,8 +158,9 @@ const ToolTimelineItem = ({
)}
- {/* 0:00
- {x.data.elapsed_time?.toString().slice(0, 6)} */}
+ {/* 0:00*/}
+ {tool.elapsed_time?.toString().slice(0, 6) || ''}
+ {tool.elapsed_time ? 's' : ''}
{
+ if (nodeId === 'begin') {
+ return '';
+ }
+ const data = currentEventListWithoutMessage?.find((x) => {
+ return (
+ x.data.component_id === nodeId &&
+ x.event === MessageEventType.NodeFinished
+ );
+ });
+ if (!data || data?.data.elapsed_time < 0.000001) {
+ return '';
+ }
+ return data?.data.elapsed_time || '';
+ };
+
const hasTrace = useCallback(
(componentId: string) => {
if (Array.isArray(traceData)) {
@@ -272,7 +288,10 @@ export const WorkFlowTimeline = ({
nodeLabel)}
- {x.data.elapsed_time?.toString().slice(0, 6)}
+ {getElapsedTime(x.data.component_id)
+ .toString()
+ .slice(0, 6)}
+ {getElapsedTime(x.data.component_id) ? 's' : ''}
void;
tenantId: string;
+ beta?: string;
};
const EmbedAppModal = (props: IEmbedAppModalProps) => {
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 { open, setOpen, token = '', from, url, tenantId, beta = '' } = props;
+
const [hideAvatar, setHideAvatar] = useState(false);
const [locale, setLocale] = useState('');
diff --git a/web/src/pages/next-search/hooks.ts b/web/src/pages/next-search/hooks.ts
index 42b9c43f4..3d483a92f 100644
--- a/web/src/pages/next-search/hooks.ts
+++ b/web/src/pages/next-search/hooks.ts
@@ -234,7 +234,10 @@ export const useTestRetrieval = (
setSelectedDocumentIds,
};
};
-export const useFetchRelatedQuestions = (tenantId?: string) => {
+export const useFetchRelatedQuestions = (
+ tenantId?: string,
+ searchId?: string,
+) => {
const [searchParams] = useSearchParams();
const shared_id = searchParams.get('shared_id');
const retrievalTestFunc = shared_id
@@ -251,6 +254,7 @@ export const useFetchRelatedQuestions = (tenantId?: string) => {
const { data } = await retrievalTestFunc({
question,
tenant_id: tenantId,
+ search_id: searchId,
});
return data?.data ?? [];
@@ -260,7 +264,12 @@ export const useFetchRelatedQuestions = (tenantId?: string) => {
return { data, loading, fetchRelatedQuestions: mutateAsync };
};
-export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
+export const useSendQuestion = (
+ kbIds: string[],
+ tenantId?: string,
+ searchId: string = '',
+ related_search: boolean = false,
+) => {
const { sharedId } = useGetSharedSearchParams();
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
sharedId ? api.askShare : api.ask,
@@ -271,7 +280,7 @@ export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
const [sendingLoading, setSendingLoading] = useState(false);
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
const { fetchRelatedQuestions, data: relatedQuestions } =
- useFetchRelatedQuestions(tenantId);
+ useFetchRelatedQuestions(tenantId, searchId);
const [searchStr, setSearchStr] = useState('');
const [isFirstRender, setIsFirstRender] = useState(true);
const [selectedDocumentIds, setSelectedDocumentIds] = useState([]);
@@ -286,7 +295,7 @@ export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
setIsFirstRender(false);
setCurrentAnswer({} as IAnswer);
setSendingLoading(true);
- send({ kb_ids: kbIds, question: q, tenantId });
+ send({ kb_ids: kbIds, question: q, tenantId, search_id: searchId });
testChunk({
kb_id: kbIds,
highlight: true,
@@ -295,7 +304,9 @@ export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
size: pagination.pageSize,
});
- fetchRelatedQuestions(q);
+ if (related_search) {
+ fetchRelatedQuestions(q);
+ }
},
[
send,
@@ -305,6 +316,8 @@ export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
setPagination,
pagination.pageSize,
tenantId,
+ searchId,
+ related_search,
],
);
@@ -408,7 +421,12 @@ export const useSearching = ({
isSearchStrEmpty,
setSearchStr,
stopOutputMessage,
- } = useSendQuestion(searchData.search_config.kb_ids, tenantId as string);
+ } = useSendQuestion(
+ searchData.search_config.kb_ids,
+ tenantId as string,
+ searchData.id,
+ searchData.search_config.related_search,
+ );
const handleSearchStrChange = useCallback(
(value: string) => {
@@ -435,15 +453,20 @@ export const useSearching = ({
showMindMapModal,
mindMapLoading,
mindMap,
- } = useShowMindMapDrawer(searchData.search_config.kb_ids, searchStr);
+ } = useShowMindMapDrawer(
+ searchData.search_config.kb_ids,
+ searchStr,
+ searchData.id,
+ );
const { chunks, total } = useSelectTestingResult();
const handleSearch = useCallback(
(value: string) => {
sendQuestion(value);
setSearchStr?.(value);
+ hideMindMapModal();
},
- [setSearchStr, sendQuestion],
+ [setSearchStr, sendQuestion, hideMindMapModal],
);
const { pagination, setPagination } = useGetPaginationWithRouter();
diff --git a/web/src/pages/next-search/index.tsx b/web/src/pages/next-search/index.tsx
index a4b2f7f6d..201c60baa 100644
--- a/web/src/pages/next-search/index.tsx
+++ b/web/src/pages/next-search/index.tsx
@@ -1,3 +1,4 @@
+import { useFetchTokenListBeforeOtherStep } from '@/components/embed-dialog/use-show-embed-dialog';
import { PageHeader } from '@/components/page-header';
import {
Breadcrumb,
@@ -10,7 +11,10 @@ import {
import { Button } from '@/components/ui/button';
import { SharedFrom } from '@/constants/chat';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
-import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
+import {
+ useFetchTenantInfo,
+ useFetchUserInfo,
+} from '@/hooks/user-setting-hooks';
import { Send, Settings } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,11 +33,13 @@ 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 { data: userInfo } = useFetchUserInfo();
const tenantId = tenantInfo.tenant_id;
const { t } = useTranslation();
const { openSetting: checkOpenSetting } = useCheckSettings(
@@ -75,6 +81,7 @@ export default function SearchPage() {
isSearching={isSearching}
searchText={searchText}
setSearchText={setSearchText}
+ userInfo={userInfo}
/>
)}
@@ -105,6 +112,7 @@ export default function SearchPage() {
token={SearchData?.id as string}
from={SharedFrom.Search}
tenantId={tenantId}
+ beta={beta}
/>
}
{
@@ -121,7 +129,14 @@ export default function SearchPage() {
{loading && (
-
+
)}
diff --git a/web/src/pages/next-search/search-home.tsx b/web/src/pages/next-search/search-home.tsx
index 37f9ceeec..742120a3a 100644
--- a/web/src/pages/next-search/search-home.tsx
+++ b/web/src/pages/next-search/search-home.tsx
@@ -1,5 +1,5 @@
import { Input } from '@/components/originui/input';
-import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
+import { IUserInfo } from '@/interfaces/database/user-setting';
import { cn } from '@/lib/utils';
import { Search } from 'lucide-react';
import { Dispatch, SetStateAction } from 'react';
@@ -12,13 +12,15 @@ export default function SearchPage({
setIsSearching,
searchText,
setSearchText,
+ userInfo,
}: {
isSearching: boolean;
setIsSearching: Dispatch
>;
searchText: string;
setSearchText: Dispatch>;
+ userInfo?: IUserInfo;
}) {
- const { data: userInfo } = useFetchUserInfo();
+ // const { data: userInfo } = useFetchUserInfo();
const { t } = useTranslation();
return (
@@ -38,7 +40,11 @@ export default function SearchPage({
<>
👋 Hi there
- {t('search.welcomeBack')}, {userInfo?.nickname}
+ {userInfo && (
+ <>
+ {t('search.welcomeBack')}, {userInfo.nickname}
+ >
+ )}
>
)}
diff --git a/web/src/pages/next-search/search-setting.tsx b/web/src/pages/next-search/search-setting.tsx
index 84efe8990..5e335e438 100644
--- a/web/src/pages/next-search/search-setting.tsx
+++ b/web/src/pages/next-search/search-setting.tsx
@@ -18,6 +18,7 @@ import {
} from '@/components/ui/multi-select';
import { RAGFlowSelect } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
+import { Textarea } from '@/components/ui/textarea';
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import {
useComposeLlmOptionsByModelTypes,
@@ -64,7 +65,7 @@ const SearchSettingFormSchema = z
description: z.string().optional(),
search_config: z.object({
kb_ids: z.array(z.string()).min(1, 'At least one dataset is required'),
- vector_similarity_weight: z.number().min(0).max(100),
+ vector_similarity_weight: z.number().min(0).max(1),
web_search: z.boolean(),
similarity_threshold: z.number(),
use_kg: z.boolean(),
@@ -128,7 +129,7 @@ const SearchSetting: React.FC = ({
: 0.3) || 0.3,
web_search: search_config?.web_search || false,
doc_ids: [],
- similarity_threshold: 0.0,
+ similarity_threshold: search_config?.similarity_threshold || 0.2,
use_kg: false,
rerank_id: search_config?.rerank_id || '',
use_rerank: search_config?.rerank_id ? true : false,
@@ -417,7 +418,7 @@ const SearchSetting: React.FC = ({
{t('search.description')}
- {
@@ -466,7 +467,41 @@ const SearchSetting: React.FC = ({
)}
/>
-
+ (
+
+ Similarity Threshold
+
+
+
+
+
+
+
+
+
+
+ )}
+ />
{/* Keyword Similarity Weight */}
= ({
render={({ field }) => (
- *Keyword
+ *Vector
Similarity Weight
= ({
)}
{/* Feature Controls */}
- (
@@ -622,7 +657,7 @@ const SearchSetting: React.FC = ({
{t('search.enableWebSearch')}
)}
- />
+ /> */}
= ({
setOpen(false);
}}
>
- {t('modal.cancelText')}
+ {t('search.cancelText')}
-
+
diff --git a/web/src/pages/next-search/search-view.tsx b/web/src/pages/next-search/search-view.tsx
index bcc80283a..c0846b8c8 100644
--- a/web/src/pages/next-search/search-view.tsx
+++ b/web/src/pages/next-search/search-view.tsx
@@ -276,7 +276,7 @@ export default function SearchingView({
{mindMapVisible && (
-
+
{
- console.log('locale', locale, i18n.language);
if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale);
}
@@ -28,15 +30,36 @@ export default function SearchingPage() {
return (
<>
{visibleAvatar && (
-
+
)}
-
;
+ {/*
; */}
+ {!isSearching && (
+
+
+
+ )}
+ {isSearching && (
+
+
+
+ )}
>
);
}
diff --git a/web/src/pages/next-searches/hooks.ts b/web/src/pages/next-searches/hooks.ts
index ac20d6541..01dac13f2 100644
--- a/web/src/pages/next-searches/hooks.ts
+++ b/web/src/pages/next-searches/hooks.ts
@@ -6,7 +6,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'umi';
-
interface CreateSearchProps {
name: string;
description?: string;
@@ -122,40 +121,6 @@ interface DeleteSearchResponse {
message: string;
}
-export const useDeleteSearch = () => {
- const { t } = useTranslation();
-
- const {
- data,
- isError,
- mutateAsync: deleteSearchMutation,
- } = useMutation
({
- mutationKey: ['deleteSearch'],
- mutationFn: async (props) => {
- const response = await searchService.deleteSearch(props);
- if (response.code !== 0) {
- throw new Error(response.message || 'Failed to delete search');
- }
- return response;
- },
- onSuccess: () => {
- message.success(t('message.deleted'));
- },
- onError: (error) => {
- message.error(t('message.error', { error: error.message }));
- },
- });
-
- const deleteSearch = useCallback(
- (props: DeleteSearchProps) => {
- return deleteSearchMutation(props);
- },
- [deleteSearchMutation],
- );
-
- return { data, isError, deleteSearch };
-};
-
export interface IllmSettingProps {
llm_id: string;
parameter: string;
@@ -237,6 +202,42 @@ export const useFetchSearchDetail = (tenantId?: string) => {
return { data: data?.data, isLoading, isError };
};
+export const useDeleteSearch = () => {
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+ const {
+ data,
+ isError,
+ mutateAsync: deleteSearchMutation,
+ } = useMutation({
+ mutationKey: ['deleteSearch'],
+ mutationFn: async (props) => {
+ const { data: response } = await searchService.deleteSearch(props);
+ if (response.code !== 0) {
+ throw new Error(response.message || 'Failed to delete search');
+ }
+
+ queryClient.invalidateQueries({ queryKey: ['searchList'] });
+ return response;
+ },
+ onSuccess: () => {
+ message.success(t('message.deleted'));
+ },
+ onError: (error) => {
+ message.error(t('message.error', { error: error.message }));
+ },
+ });
+
+ const deleteSearch = useCallback(
+ (props: DeleteSearchProps) => {
+ return deleteSearchMutation(props);
+ },
+ [deleteSearchMutation],
+ );
+
+ return { data, isError, deleteSearch };
+};
+
export type IUpdateSearchProps = Omit & {
search_id: string;
};
diff --git a/web/src/pages/search/hooks.ts b/web/src/pages/search/hooks.ts
index 8c7607972..abfecd698 100644
--- a/web/src/pages/search/hooks.ts
+++ b/web/src/pages/search/hooks.ts
@@ -217,7 +217,11 @@ export const useTestRetrieval = (
};
};
-export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
+export const useShowMindMapDrawer = (
+ kbIds: string[],
+ question: string,
+ searchId = '',
+) => {
const { visible, showModal, hideModal } = useSetModalState();
const ref = useRef();
@@ -228,7 +232,7 @@ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
} = useSearchFetchMindMap();
const handleShowModal = useCallback(() => {
- const searchParams = { question: trim(question), kb_ids: kbIds };
+ const searchParams = { question: trim(question), kb_ids: kbIds, searchId };
if (
!isEmpty(searchParams.question) &&
!isEqual(searchParams, ref.current)
@@ -237,7 +241,7 @@ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
fetchMindMap(searchParams);
}
showModal();
- }, [fetchMindMap, showModal, question, kbIds]);
+ }, [fetchMindMap, showModal, question, kbIds, searchId]);
return {
mindMap,