mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
♻️ refactor: Logout UX, Improved State Teardown, & Remove Unused Code (#5292)
* refactor: SearchBar and Nav components to streamline search functionality and improve state management * refactor: remove refresh conversations * chore: update useNewConvo calls to remove hardcoded default index * refactor: null check for submission in useSSE hook * refactor: remove useConversation hook and update useSearch to utilize useNewConvo * refactor: remove conversation and banner store files; consolidate state management into misc; improve typing of families and add messagesSiblingIdxFamily * refactor: more effectively clear all user/convo state without side effects on logout/delete user * refactor: replace useParams with useLocation in SearchBar to correctly load conversation * refactor: update SearchButtons to use button element and improve conversation ID handling * refactor: use named function for `newConversation` for better call stack tracing * refactor: enhance TermsAndConditionsModal to support array content and improve type definitions for terms of service * refactor: add SetConvoProvider and message invalidation when navigating from search results to prevent initial route rendering edge cases * refactor: rename getLocalStorageItems to localStorage and update imports for consistency * refactor: move clearLocalStorage function to utils and simplify localStorage clearing logic * refactor: migrate authentication mutations to a dedicated Auth data provider and update related tests
This commit is contained in:
parent
24beda3d69
commit
aa80e4594e
45 changed files with 378 additions and 434 deletions
|
|
@ -10,14 +10,10 @@ import {
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { setTokenHeader, SystemRoles } from 'librechat-data-provider';
|
||||
import {
|
||||
useGetUserQuery,
|
||||
useLoginUserMutation,
|
||||
useRefreshTokenMutation,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import { useGetUserQuery, useRefreshTokenMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TLoginResponse, TLoginUser } from 'librechat-data-provider';
|
||||
import { useLoginUserMutation, useLogoutUserMutation, useGetRole } from '~/data-provider';
|
||||
import { TAuthConfig, TUserContext, TAuthContext, TResError } from '~/common';
|
||||
import { useLogoutUserMutation, useGetRole } from '~/data-provider';
|
||||
import useTimeout from './useTimeout';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { default as useAppStartup } from './useAppStartup';
|
||||
export { default as useClearStates } from './useClearStates';
|
||||
|
|
|
|||
52
client/src/hooks/Config/useClearStates.ts
Normal file
52
client/src/hooks/Config/useClearStates.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { useRecoilCallback } from 'recoil';
|
||||
import { clearLocalStorage } from '~/utils/localStorage';
|
||||
import store from '~/store';
|
||||
|
||||
export default function useClearStates() {
|
||||
const clearConversations = store.useClearConvoState();
|
||||
const clearSubmissions = store.useClearSubmissionState();
|
||||
const clearLatestMessages = store.useClearLatestMessages();
|
||||
|
||||
const clearStates = useRecoilCallback(
|
||||
({ reset, snapshot }) =>
|
||||
async (skipFirst?: boolean) => {
|
||||
await clearSubmissions(skipFirst);
|
||||
await clearConversations(skipFirst);
|
||||
await clearLatestMessages(skipFirst);
|
||||
|
||||
const keys = await snapshot.getPromise(store.conversationKeysAtom);
|
||||
|
||||
for (const key of keys) {
|
||||
if (skipFirst === true && key === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reset(store.filesByIndex(key));
|
||||
reset(store.presetByIndex(key));
|
||||
reset(store.textByIndex(key));
|
||||
reset(store.showStopButtonByIndex(key));
|
||||
reset(store.abortScrollFamily(key));
|
||||
reset(store.isSubmittingFamily(key));
|
||||
reset(store.optionSettingsFamily(key));
|
||||
reset(store.showAgentSettingsFamily(key));
|
||||
reset(store.showBingToneSettingFamily(key));
|
||||
reset(store.showPopoverFamily(key));
|
||||
reset(store.showMentionPopoverFamily(key));
|
||||
reset(store.showPlusPopoverFamily(key));
|
||||
reset(store.showPromptsPopoverFamily(key));
|
||||
reset(store.activePromptByIndex(key));
|
||||
reset(store.globalAudioURLFamily(key));
|
||||
reset(store.globalAudioFetchingFamily(key));
|
||||
reset(store.globalAudioPlayingFamily(key));
|
||||
reset(store.activeRunFamily(key));
|
||||
reset(store.audioRunFamily(key));
|
||||
reset(store.messagesSiblingIdxFamily(key.toString()));
|
||||
}
|
||||
|
||||
clearLocalStorage(skipFirst);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return clearStates;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import store from '~/store';
|
|||
|
||||
type TempOverrideType = Record<string, unknown> & {
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
modelsConfig: TModelsConfig;
|
||||
modelsConfig?: TModelsConfig;
|
||||
combinedOptions: unknown[];
|
||||
combined: boolean;
|
||||
};
|
||||
|
|
@ -38,7 +38,7 @@ export default function useConfigOverride() {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (overrideQuery.data) {
|
||||
if (overrideQuery.data != null) {
|
||||
handleOverride(overrideQuery.data);
|
||||
}
|
||||
}, [overrideQuery.data, handleOverride]);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,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 useConversation } from './useConversation';
|
||||
export { default as useGenerateConvo } from './useGenerateConvo';
|
||||
export { default as useConversations } from './useConversations';
|
||||
export { default as useArchiveHandler } from './useArchiveHandler';
|
||||
export { default as useDebouncedInput } from './useDebouncedInput';
|
||||
export { default as useBookmarkSuccess } from './useBookmarkSuccess';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||
import { useArchiveConversationMutation } from '~/data-provider';
|
||||
import useConversations from './useConversations';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import useLocalize from '../useLocalize';
|
||||
|
|
@ -16,7 +15,6 @@ export default function useArchiveHandler(
|
|||
const navigate = useNavigate();
|
||||
const { showToast } = useToastContext();
|
||||
const { newConversation } = useNewConvo();
|
||||
const { refreshConversations } = useConversations();
|
||||
const { conversationId: currentConvoId } = useParams();
|
||||
|
||||
const archiveConvoMutation = useArchiveConversationMutation(conversationId ?? '');
|
||||
|
|
@ -38,7 +36,6 @@ export default function useArchiveHandler(
|
|||
newConversation();
|
||||
navigate('/c/new', { replace: true });
|
||||
}
|
||||
refreshConversations();
|
||||
retainView();
|
||||
},
|
||||
onError: () => {
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
|
||||
import { useGetEndpointsQuery, useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import type {
|
||||
TConversation,
|
||||
TMessagesAtom,
|
||||
TSubmission,
|
||||
TPreset,
|
||||
TModelsConfig,
|
||||
TEndpointsConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const useConversation = () => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const setConversation = useSetRecoilState(store.conversation);
|
||||
const resetLatestMessage = useResetRecoilState(store.latestMessage);
|
||||
const setMessages = useSetRecoilState<TMessagesAtom>(store.messages);
|
||||
const setSubmission = useSetRecoilState<TSubmission | null>(store.submission);
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
|
||||
const switchToConversation = useRecoilCallback(
|
||||
() =>
|
||||
async (
|
||||
conversation: TConversation,
|
||||
messages: TMessagesAtom = null,
|
||||
preset: TPreset | null = null,
|
||||
modelsData?: TModelsConfig,
|
||||
) => {
|
||||
const modelsConfig = modelsData ?? modelsQuery.data;
|
||||
const { endpoint = null } = conversation;
|
||||
|
||||
if (endpoint === null) {
|
||||
const defaultEndpoint = getDefaultEndpoint({
|
||||
convoSetup: preset ?? conversation,
|
||||
endpointsConfig,
|
||||
});
|
||||
|
||||
const endpointType = getEndpointField(endpointsConfig, defaultEndpoint, 'type');
|
||||
if (!conversation.endpointType && endpointType) {
|
||||
conversation.endpointType = endpointType;
|
||||
}
|
||||
|
||||
const models = modelsConfig?.[defaultEndpoint] ?? [];
|
||||
conversation = buildDefaultConvo({
|
||||
conversation,
|
||||
lastConversationSetup: preset as TConversation,
|
||||
endpoint: defaultEndpoint,
|
||||
models,
|
||||
});
|
||||
}
|
||||
|
||||
setConversation(conversation);
|
||||
setMessages(messages);
|
||||
setSubmission({} as TSubmission);
|
||||
resetLatestMessage();
|
||||
logger.log(
|
||||
'[useConversation] Switched to conversation and reset Latest Message',
|
||||
conversation,
|
||||
);
|
||||
|
||||
if (conversation.conversationId === 'new' && !modelsData) {
|
||||
queryClient.invalidateQueries([QueryKeys.messages, 'new']);
|
||||
navigate('/c/new');
|
||||
}
|
||||
},
|
||||
[endpointsConfig, modelsQuery.data],
|
||||
);
|
||||
|
||||
const newConversation = useCallback(
|
||||
(template = {}, preset?: TPreset, modelsData?: TModelsConfig) => {
|
||||
switchToConversation(
|
||||
{
|
||||
conversationId: 'new',
|
||||
title: 'New Chat',
|
||||
...template,
|
||||
endpoint: null,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
[],
|
||||
preset,
|
||||
modelsData,
|
||||
);
|
||||
},
|
||||
[switchToConversation],
|
||||
);
|
||||
|
||||
const searchPlaceholderConversation = useCallback(() => {
|
||||
switchToConversation(
|
||||
{
|
||||
conversationId: 'search',
|
||||
title: 'Search',
|
||||
endpoint: null,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
[],
|
||||
);
|
||||
}, [switchToConversation]);
|
||||
|
||||
return {
|
||||
switchToConversation,
|
||||
newConversation,
|
||||
searchPlaceholderConversation,
|
||||
};
|
||||
};
|
||||
|
||||
export default useConversation;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { useSetRecoilState } from 'recoil';
|
||||
import { useCallback } from 'react';
|
||||
import store from '~/store';
|
||||
|
||||
const useConversations = () => {
|
||||
const setRefreshConversationsHint = useSetRecoilState(store.refreshConversationsHint);
|
||||
|
||||
const refreshConversations = useCallback(() => {
|
||||
setRefreshConversationsHint((prevState) => prevState + 1);
|
||||
}, [setRefreshConversationsHint]);
|
||||
|
||||
return { refreshConversations };
|
||||
};
|
||||
|
||||
export default useConversations;
|
||||
|
|
@ -12,20 +12,29 @@ const useNavigateToConvo = (index = 0) => {
|
|||
const clearAllConversations = store.useClearConvoState();
|
||||
const clearAllLatestMessages = store.useClearLatestMessages(`useNavigateToConvo ${index}`);
|
||||
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
const { hasSetConversation, setConversation } = store.useCreateConversationAtom(index);
|
||||
|
||||
const navigateToConvo = (conversation: TConversation, _resetLatestMessage = true) => {
|
||||
const navigateToConvo = (
|
||||
conversation?: TConversation | null,
|
||||
_resetLatestMessage = true,
|
||||
invalidateMessages = false,
|
||||
) => {
|
||||
if (!conversation) {
|
||||
console.log('Conversation not provided');
|
||||
return;
|
||||
}
|
||||
hasSetConversation.current = true;
|
||||
setSubmission(null);
|
||||
if (_resetLatestMessage) {
|
||||
clearAllLatestMessages();
|
||||
}
|
||||
if (invalidateMessages && conversation.conversationId != null && conversation.conversationId) {
|
||||
queryClient.setQueryData([QueryKeys.messages, Constants.NEW_CONVO], []);
|
||||
queryClient.invalidateQueries([QueryKeys.messages, conversation.conversationId]);
|
||||
}
|
||||
|
||||
let convo = { ...conversation };
|
||||
if (!convo?.endpoint) {
|
||||
if (!convo.endpoint) {
|
||||
/* undefined endpoint edge case */
|
||||
const modelsConfig = queryClient.getQueryData<TModelsConfig>([QueryKeys.models]);
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
|
|
@ -53,9 +62,17 @@ const useNavigateToConvo = (index = 0) => {
|
|||
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`);
|
||||
};
|
||||
|
||||
const navigateWithLastTools = (conversation: TConversation, _resetLatestMessage?: boolean) => {
|
||||
const navigateWithLastTools = (
|
||||
conversation?: TConversation | null,
|
||||
_resetLatestMessage?: boolean,
|
||||
invalidateMessages?: boolean,
|
||||
) => {
|
||||
if (!conversation) {
|
||||
console.log('Conversation not provided');
|
||||
return;
|
||||
}
|
||||
// set conversation to the new conversation
|
||||
if (conversation?.endpoint === EModelEndpoint.gptPlugins) {
|
||||
if (conversation.endpoint === EModelEndpoint.gptPlugins) {
|
||||
let lastSelectedTools = [];
|
||||
try {
|
||||
lastSelectedTools =
|
||||
|
|
@ -63,15 +80,17 @@ const useNavigateToConvo = (index = 0) => {
|
|||
} catch (e) {
|
||||
// console.error(e);
|
||||
}
|
||||
const hasTools = (conversation.tools?.length ?? 0) > 0;
|
||||
navigateToConvo(
|
||||
{
|
||||
...conversation,
|
||||
tools: conversation?.tools?.length ? conversation?.tools : lastSelectedTools,
|
||||
tools: hasTools ? conversation.tools : lastSelectedTools,
|
||||
},
|
||||
_resetLatestMessage,
|
||||
invalidateMessages,
|
||||
);
|
||||
} else {
|
||||
navigateToConvo(conversation, _resetLatestMessage);
|
||||
navigateToConvo(conversation, _resetLatestMessage, invalidateMessages);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,23 @@ import { useGetSearchEnabledQuery } from 'librechat-data-provider/react-query';
|
|||
import type { UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||
import type { ConversationListResponse } from 'librechat-data-provider';
|
||||
import { useSearchInfiniteQuery } from '~/data-provider';
|
||||
import useConversation from './useConversation';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import store from '~/store';
|
||||
|
||||
export default function useSearchMessages({ isAuthenticated }: { isAuthenticated: boolean }) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const { searchPlaceholderConversation } = useConversation();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default function useSSE(
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (submission === null || Object.keys(submission).length === 0) {
|
||||
if (submission == null || Object.keys(submission).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,11 @@ const useNewConvo = (index = 0) => {
|
|||
) ?? assistants[0]?.id;
|
||||
}
|
||||
|
||||
if (currentAssistantId && isAssistantEndpoint && conversation.conversationId === Constants.NEW_CONVO) {
|
||||
if (
|
||||
currentAssistantId &&
|
||||
isAssistantEndpoint &&
|
||||
conversation.conversationId === Constants.NEW_CONVO
|
||||
) {
|
||||
const assistant = assistants.find((asst) => asst.id === currentAssistantId);
|
||||
conversation.model = assistant?.model;
|
||||
updateLastSelectedModel({
|
||||
|
|
@ -168,7 +172,7 @@ const useNewConvo = (index = 0) => {
|
|||
);
|
||||
|
||||
const newConversation = useCallback(
|
||||
({
|
||||
function createNewConvo({
|
||||
template: _template = {},
|
||||
preset: _preset,
|
||||
modelsData,
|
||||
|
|
@ -182,7 +186,7 @@ const useNewConvo = (index = 0) => {
|
|||
buildDefault?: boolean;
|
||||
keepLatestMessage?: boolean;
|
||||
keepAddedConvos?: boolean;
|
||||
} = {}) => {
|
||||
} = {}) {
|
||||
pauseGlobalAudio();
|
||||
|
||||
const templateConvoId = _template.conversationId ?? '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue