🔍 refactor: Search & Message Retrieval (#6903)

* refactor: conversation search fetch

* refactor: Message and Convo fetch with paramters and search

* refactor: update search states and cleanup old store states

* refactor: re-enable search API; fix: search conversation

* fix: message's convo fetch

* fix: redirect when searching

* chore: use logger instead of console

* fix: search message loading

* feat: small optimizations

* feat(Message): remove cache for search path

* fix: handle delete of all archivedConversation and sharedLinks

* chore: cleanup

* fix: search messages

* style: update ConvoOptions styles

* refactor(SearchButtons): streamline conversation fetching and remove unused state

* fix: ensure messages are invalidated after fetching conversation data

* fix: add iconURL to conversation query selection

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-04-17 03:07:43 +02:00 committed by GitHub
parent 851938e7a6
commit 88f4ad7c47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 489 additions and 576 deletions

View file

@ -1,7 +1,7 @@
export { default as useSearch } from './useSearch';
export { default as usePresets } from './usePresets';
export { default as useGetSender } from './useGetSender';
export { default as useDefaultConvo } from './useDefaultConvo';
export { default as useSearchEnabled } from './useSearchEnabled';
export { default as useGenerateConvo } from './useGenerateConvo';
export { default as useDebouncedInput } from './useDebouncedInput';
export { default as useBookmarkSuccess } from './useBookmarkSuccess';

View file

@ -29,6 +29,7 @@ const useNavigateToConvo = (index = 0) => {
dataService.getConversationById(conversationId),
);
logger.log('conversation', 'Fetched fresh conversation data', data);
await queryClient.invalidateQueries([QueryKeys.messages, conversationId]);
setConversation(data);
} catch (error) {
console.error('Error fetching conversation data on navigation', error);
@ -38,6 +39,7 @@ const useNavigateToConvo = (index = 0) => {
const navigateToConvo = (
conversation?: TConversation | null,
_resetLatestMessage = true,
/** Likely need to remove this since it happens after fetching conversation data */
invalidateMessages = false,
) => {
if (!conversation) {

View file

@ -1,102 +0,0 @@
import { useEffect, useCallback, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useNavigate, useLocation } from 'react-router-dom';
import type { UseInfiniteQueryResult } from '@tanstack/react-query';
import type { SearchConversationListResponse } from 'librechat-data-provider';
import { useSearchInfiniteQuery, useGetSearchEnabledQuery } from '~/data-provider';
import useNewConvo from '~/hooks/useNewConvo';
import store from '~/store';
export interface UseSearchMessagesResult {
searchQuery: string;
searchQueryRes: UseInfiniteQueryResult<SearchConversationListResponse, unknown> | undefined;
}
export default function useSearchMessages({
isAuthenticated,
}: {
isAuthenticated: boolean;
}): UseSearchMessagesResult {
const navigate = useNavigate();
const location = useLocation();
const { switchToConversation } = useNewConvo();
const searchPlaceholderConversation = useCallback(() => {
switchToConversation({
conversationId: 'search',
title: 'Search',
endpoint: null,
createdAt: '',
updatedAt: '',
});
}, [switchToConversation]);
const searchQuery = useRecoilValue(store.searchQuery);
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchQuery(searchQuery);
}, 350); // 350ms debounce
return () => clearTimeout(handler);
}, [searchQuery]);
const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated });
const searchQueryRes = useSearchInfiniteQuery(
{ nextCursor: null, search: debouncedSearchQuery, pageSize: 20 },
{ enabled: isAuthenticated && !!debouncedSearchQuery },
) as UseInfiniteQueryResult<SearchConversationListResponse, unknown> | undefined;
useEffect(() => {
if (searchQuery && searchQuery.length > 0) {
navigate('/search', { replace: true });
return;
}
if (location.pathname && location.pathname.includes('/c/')) {
return;
}
navigate('/c/new', { replace: true });
/* Disabled eslint rule because we don't want to run this effect when location changes */
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navigate, searchQuery]);
useEffect(() => {
if (searchEnabledQuery.data === true) {
setIsSearchEnabled(searchEnabledQuery.data);
} else if (searchEnabledQuery.isError) {
console.error('Failed to get search enabled', searchEnabledQuery.error);
}
}, [
searchEnabledQuery.data,
searchEnabledQuery.error,
searchEnabledQuery.isError,
setIsSearchEnabled,
]);
const onSearchSuccess = useCallback(
() => searchPlaceholderConversation(),
[searchPlaceholderConversation],
);
useEffect(() => {
// we use isInitialLoading here instead of isLoading because query is disabled by default
if (searchQueryRes?.data) {
onSearchSuccess();
}
}, [searchQueryRes?.data, searchQueryRes?.isInitialLoading, onSearchSuccess]);
const setIsSearchTyping = useSetRecoilState(store.isSearchTyping);
useEffect(() => {
if (!searchQueryRes?.isLoading && !searchQueryRes?.isFetching) {
setIsSearchTyping(false);
}
}, [searchQueryRes?.isLoading, searchQueryRes?.isFetching, setIsSearchTyping]);
return {
searchQuery,
searchQueryRes,
};
}

View file

@ -0,0 +1,20 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useGetSearchEnabledQuery } from '~/data-provider';
import { logger } from '~/utils';
import store from '~/store';
export default function useSearchEnabled(isAuthenticated: boolean) {
const setSearch = useSetRecoilState(store.search);
const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated });
useEffect(() => {
if (searchEnabledQuery.data === true) {
setSearch((prev) => ({ ...prev, enabled: searchEnabledQuery.data }));
} else if (searchEnabledQuery.isError) {
logger.error('Failed to get search enabled: ', searchEnabledQuery.error);
}
}, [searchEnabledQuery.data, searchEnabledQuery.error, searchEnabledQuery.isError, setSearch]);
return searchEnabledQuery;
}