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()} > -
+
Next Step
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 && ( -
+
{searchData.name}
)} - ; + {/* ; */} + {!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,