📢 fix: Invalid engineTTS and Conversation State on Navigation (#6904)

* fix: handle invalid engineTTS values and prevent VoiceDropdown render errors

* refactor: add verbose developer logging for debugging conversation state issues

* refactor: remove unnecessary effect for conversationId changes

* chore: imports

* fix: include model and entity IDs in conversation query selection

* feat: add fetchFreshData function to retrieve conversation data on navigation

* fix: remove unnecessary comment in fetchFreshData function

* chore: reorder imports in useNavigateToConvo for consistency

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-04-16 03:00:06 +02:00 committed by GitHub
parent d32f34e5d7
commit 000f3a3733
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 75 additions and 18 deletions

View file

@ -192,7 +192,9 @@ module.exports = {
try {
const convos = await Conversation.find(query)
.select('conversationId endpoint title createdAt updatedAt user')
.select(
'conversationId endpoint title createdAt updatedAt user model agent_id assistant_id',
)
.sort({ updatedAt: order === 'asc' ? 1 : -1 })
.limit(limit + 1)
.lean();

View file

@ -136,6 +136,14 @@ function Speech() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
// Reset engineTTS if it is set to a removed/invalid value (e.g., 'edge')
useEffect(() => {
const validEngines = ['browser', 'external'];
if (!validEngines.includes(engineTTS)) {
setEngineTTS('browser');
}
}, [engineTTS, setEngineTTS]);
logger.log({ sttExternal, ttsExternal });
const contentRef = useRef(null);

View file

@ -12,5 +12,9 @@ export default function VoiceDropdown() {
const engineTTS = useRecoilValue<string>(store.engineTTS);
const VoiceDropdownComponent = voiceDropdownComponentsMap[engineTTS];
if (!VoiceDropdownComponent) {
return null;
}
return <VoiceDropdownComponent />;
}

View file

@ -4,8 +4,9 @@ import { EModelEndpoint, isAgentsEndpoint, Constants, QueryKeys } from 'librecha
import type { TConversation, TPreset, Agent } from 'librechat-data-provider';
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
import { useGetAgentByIdQuery } from '~/data-provider';
import { useChatContext } from '~/Providers/ChatContext';
import { useGetAgentByIdQuery } from '~/data-provider';
import { logger } from '~/utils';
export default function useSelectAgent() {
const queryClient = useQueryClient();
@ -22,6 +23,7 @@ export default function useSelectAgent() {
const updateConversation = useCallback(
(agent: Partial<Agent>, template: Partial<TPreset | TConversation>) => {
logger.log('conversation', 'Updating conversation with agent', agent);
if (isAgentsEndpoint(conversation?.endpoint)) {
const currentConvo = getDefaultConversation({
conversation: { ...(conversation ?? {}), agent_id: agent.id },

View file

@ -4,7 +4,7 @@ import type { AssistantsEndpoint, TConversation, TPreset } from 'librechat-data-
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
import { useChatContext } from '~/Providers/ChatContext';
import useAssistantListMap from './useAssistantListMap';
import { mapAssistants } from '~/utils';
import { mapAssistants, logger } from '~/utils';
export default function useSelectAssistant(endpoint: AssistantsEndpoint) {
const getDefaultConversation = useDefaultConvo();
@ -24,6 +24,7 @@ export default function useSelectAssistant(endpoint: AssistantsEndpoint) {
conversationId: 'new',
};
logger.log('conversation', 'Updating conversation with assistant', assistant);
if (isAssistantsEndpoint(conversation?.endpoint)) {
const currentConvo = getDefaultConversation({
conversation: { ...(conversation ?? {}) },

View file

@ -11,7 +11,7 @@ import type {
} from 'librechat-data-provider';
import type { SetterOrUpdater } from 'recoil';
import type { AssistantListItem } from '~/common';
import { getEndpointField, buildDefaultConvo, getDefaultEndpoint } from '~/utils';
import { getEndpointField, buildDefaultConvo, getDefaultEndpoint, logger } from '~/utils';
import useAssistantListMap from '~/hooks/Assistants/useAssistantListMap';
import { useGetEndpointsQuery } from '~/data-provider';
import { mainTextareaId } from '~/common';
@ -44,6 +44,7 @@ const useGenerateConvo = ({
conversationId: rootConvo.conversationId,
} as TConversation;
logger.log('conversation', 'Setting conversation from `useNewConvo`', update);
return update;
});
}

View file

@ -1,7 +1,13 @@
import { useSetRecoilState } from 'recoil';
import { useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import { QueryKeys, EModelEndpoint, LocalStorageKeys, Constants } from 'librechat-data-provider';
import {
QueryKeys,
Constants,
dataService,
EModelEndpoint,
LocalStorageKeys,
} from 'librechat-data-provider';
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
import store from '~/store';
@ -14,6 +20,21 @@ const useNavigateToConvo = (index = 0) => {
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
const { hasSetConversation, setConversation } = store.useCreateConversationAtom(index);
const fetchFreshData = async (conversationId?: string | null) => {
if (!conversationId) {
return;
}
try {
const data = await queryClient.fetchQuery([QueryKeys.conversation, conversationId], () =>
dataService.getConversationById(conversationId),
);
logger.log('conversation', 'Fetched fresh conversation data', data);
setConversation(data);
} catch (error) {
console.error('Error fetching conversation data on navigation', error);
}
};
const navigateToConvo = (
conversation?: TConversation | null,
_resetLatestMessage = true,
@ -23,6 +44,7 @@ const useNavigateToConvo = (index = 0) => {
logger.warn('conversation', 'Conversation not provided to `navigateToConvo`');
return;
}
logger.log('conversation', 'Navigating to conversation', conversation);
hasSetConversation.current = true;
setSubmission(null);
if (_resetLatestMessage) {
@ -60,6 +82,10 @@ const useNavigateToConvo = (index = 0) => {
clearAllConversations(true);
setConversation(convo);
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`);
if (convo.conversationId !== Constants.NEW_CONVO && convo.conversationId) {
queryClient.invalidateQueries([QueryKeys.conversation, convo.conversationId]);
fetchFreshData(convo.conversationId);
}
};
const navigateWithLastTools = (

View file

@ -11,7 +11,7 @@ import {
} from 'librechat-data-provider';
import type { TPreset, TEndpointsConfig, TStartupConfig } from 'librechat-data-provider';
import type { ZodAny } from 'zod';
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools } from '~/utils';
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools, logger } from '~/utils';
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
import { useChatContext, useChatFormContext } from '~/Providers';
import useSubmitMessage from '~/hooks/Messages/useSubmitMessage';
@ -159,6 +159,7 @@ export default function useQueryParams({
});
/* We don't reset the latest message, only when changing settings mid-converstion */
logger.log('conversation', 'Switching conversation from query params', currentConvo);
newConversation({
template: currentConvo,
preset: newPreset,

View file

@ -9,7 +9,7 @@ import type {
TEndpointsConfig,
} from 'librechat-data-provider';
import type { MentionOption, ConvoGenerator } from '~/common';
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools } from '~/utils';
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools, logger } from '~/utils';
import { useChatContext } from '~/Providers';
import { useDefaultConvo } from '~/hooks';
import store from '~/store';
@ -86,6 +86,7 @@ export default function useSelectMention({
});
/* We don't reset the latest message, only when changing settings mid-converstion */
logger.info('conversation', 'Switching conversation to new spec (modular)', conversation);
newConversation({
template: currentConvo,
preset,
@ -95,6 +96,7 @@ export default function useSelectMention({
return;
}
logger.info('conversation', 'Switching conversation to new spec', conversation);
newConversation({
template: { ...(template as Partial<TConversation>) },
preset,
@ -172,6 +174,11 @@ export default function useSelectMention({
});
/* We don't reset the latest message, only when changing settings mid-converstion */
logger.info(
'conversation',
'Switching conversation to new endpoint/model (modular)',
currentConvo,
);
newConversation({
template: currentConvo,
preset: currentConvo,
@ -181,6 +188,7 @@ export default function useSelectMention({
return;
}
logger.info('conversation', 'Switching conversation to new endpoint/model', template);
newConversation({
template: { ...(template as Partial<TConversation>) },
preset: { ...kwargs, spec: null, iconURL: null, modelLabel: null, endpoint: newEndpoint },
@ -230,6 +238,7 @@ export default function useSelectMention({
});
/* We don't reset the latest message, only when changing settings mid-converstion */
logger.info('conversation', 'Switching conversation to new preset (modular)', currentConvo);
newConversation({
template: currentConvo,
preset: newPreset,
@ -239,6 +248,7 @@ export default function useSelectMention({
return;
}
logger.info('conversation', 'Switching conversation to new preset', template);
newConversation({ preset: newPreset, keepAddedConvos: isModular });
},
[

View file

@ -31,6 +31,7 @@ import useAssistantListMap from './Assistants/useAssistantListMap';
import { useResetChatBadges } from './useChatBadges';
import { usePauseGlobalAudio } from './Audio';
import { mainTextareaId } from '~/common';
import { logger } from '~/utils';
import store from '~/store';
const useNewConvo = (index = 0) => {
@ -151,6 +152,7 @@ const useNewConvo = (index = 0) => {
if (!(keepAddedConvos ?? false)) {
clearAllConversations(true);
}
logger.log('conversation', 'Setting conversation from `useNewConvo`', conversation);
setConversation(conversation);
setSubmission({} as TSubmission);
if (!(keepLatestMessage ?? false)) {

View file

@ -1,16 +1,16 @@
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Constants, EModelEndpoint } from 'librechat-data-provider';
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
import type { TPreset } from 'librechat-data-provider';
import {
useGetConvoIdQuery,
useHealthCheck,
useGetEndpointsQuery,
useGetConvoIdQuery,
useGetStartupConfig,
useGetEndpointsQuery,
} from '~/data-provider';
import { useNewConvo, useAppStartup, useAssistantListMap } from '~/hooks';
import { getDefaultModelSpec, getModelSpecIconURL } from '~/utils';
import { getDefaultModelSpec, getModelSpecIconURL, logger } from '~/utils';
import { ToolCallsMapProvider } from '~/Providers';
import ChatView from '~/components/Chat/ChatView';
import useAuthRedirect from './useAuthRedirect';
@ -38,11 +38,6 @@ export default function ChatRoute() {
const { hasSetConversation, conversation } = store.useCreateConversationAtom(index);
const { newConversation } = useNewConvo();
// Reset the guard flag whenever conversationId changes
useEffect(() => {
hasSetConversation.current = false;
}, [conversationId]);
const modelsQuery = useGetModelsQuery({
enabled: isAuthenticated,
refetchOnMount: 'always',
@ -53,6 +48,9 @@ export default function ChatRoute() {
const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated });
const assistantListMap = useAssistantListMap();
/** This effect is mainly for the first conversation state change on first load of the page.
* Adjusting this may have unintended consequences on the conversation state.
*/
useEffect(() => {
const shouldSetConvo =
(startupConfig && !hasSetConversation.current && !modelsQuery.data?.initial) ?? false;
@ -63,7 +61,7 @@ export default function ChatRoute() {
if (conversationId === Constants.NEW_CONVO && endpointsQuery.data && modelsQuery.data) {
const spec = getDefaultModelSpec(startupConfig);
logger.log('conversation', 'ChatRoute, new convo effect', conversation);
newConversation({
modelsData: modelsQuery.data,
template: conversation ? conversation : undefined,
@ -80,6 +78,7 @@ export default function ChatRoute() {
hasSetConversation.current = true;
} else if (initialConvoQuery.data && endpointsQuery.data && modelsQuery.data) {
logger.log('conversation', 'ChatRoute initialConvoQuery', initialConvoQuery.data);
newConversation({
template: initialConvoQuery.data,
/* this is necessary to load all existing settings */
@ -94,6 +93,7 @@ export default function ChatRoute() {
assistantListMap[EModelEndpoint.azureAssistants]
) {
const spec = getDefaultModelSpec(startupConfig);
logger.log('conversation', 'ChatRoute new convo, assistants effect', conversation);
newConversation({
modelsData: modelsQuery.data,
template: conversation ? conversation : undefined,
@ -112,6 +112,7 @@ export default function ChatRoute() {
assistantListMap[EModelEndpoint.assistants] &&
assistantListMap[EModelEndpoint.azureAssistants]
) {
logger.log('conversation', 'ChatRoute convo, assistants effect', initialConvoQuery.data);
newConversation({
template: initialConvoQuery.data,
preset: initialConvoQuery.data as TPreset,
@ -127,7 +128,6 @@ export default function ChatRoute() {
endpointsQuery.data,
modelsQuery.data,
assistantListMap,
conversationId,
]);
if (endpointsQuery.isLoading || modelsQuery.isLoading) {