mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-06 07:47:20 +02:00
🧭 fix: Robust 404 Conversation Not Found Redirect (#11853)
* fix: route to new conversation when conversation not found * Addressed PR feedback * fix: Robust 404 conversation redirect handling - Extract `isNotFoundError` utility to `utils/errors.ts` so axios stays contained in one place rather than leaking into route/query layers - Add `initialConvoQuery.isError` to the useEffect dependency array so the redirect actually fires when the 404 response arrives after other deps have already settled (was the root cause of the blank screen) - Show a warning toast so users understand why they were redirected - Add `com_ui_conversation_not_found` i18n key * fix: Enhance error handling in getResponseStatus function - Update the getResponseStatus function to ensure it correctly returns the status from error objects only if the status is a number. This improves robustness in error handling by preventing potential type issues. * fix: Improve conversation not found handling in ChatRoute - Enhance error handling when a conversation is not found by checking additional conditions before showing a warning toast. - Update the newConversation function to include model data and preset options, improving user experience during error scenarios. * fix: Log error details for conversation not found in ChatRoute - Added logging for the initial conversation query error when a conversation is not found, improving debugging capabilities and error tracking in the ChatRoute component. --------- Co-authored-by: Dan Lew <daniel@mightyacorn.com>
This commit is contained in:
parent
252a5cc7ca
commit
42718faad2
5 changed files with 66 additions and 4 deletions
|
|
@ -31,7 +31,7 @@ import type {
|
||||||
SharedLinksResponse,
|
SharedLinksResponse,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { ConversationCursorData } from '~/utils/convos';
|
import type { ConversationCursorData } from '~/utils/convos';
|
||||||
import { findConversationInInfinite } from '~/utils';
|
import { findConversationInInfinite, isNotFoundError } from '~/utils';
|
||||||
|
|
||||||
export const useGetPresetsQuery = (
|
export const useGetPresetsQuery = (
|
||||||
config?: UseQueryOptions<TPreset[]>,
|
config?: UseQueryOptions<TPreset[]>,
|
||||||
|
|
@ -71,6 +71,12 @@ export const useGetConvoIdQuery = (
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
retry: (failureCount, error) => {
|
||||||
|
if (isNotFoundError(error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return failureCount < 3;
|
||||||
|
},
|
||||||
...config,
|
...config,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -842,6 +842,7 @@
|
||||||
"com_ui_controls": "Controls",
|
"com_ui_controls": "Controls",
|
||||||
"com_ui_conversation": "conversation",
|
"com_ui_conversation": "conversation",
|
||||||
"com_ui_conversation_label": "{{title}} conversation",
|
"com_ui_conversation_label": "{{title}} conversation",
|
||||||
|
"com_ui_conversation_not_found": "Conversation not found",
|
||||||
"com_ui_conversations": "conversations",
|
"com_ui_conversations": "conversations",
|
||||||
"com_ui_convo_archived": "Conversation archived",
|
"com_ui_convo_archived": "Conversation archived",
|
||||||
"com_ui_convo_delete_error": "Failed to delete conversation",
|
"com_ui_convo_delete_error": "Failed to delete conversation",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Spinner } from '@librechat/client';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||||
|
import { Spinner, useToastContext } from '@librechat/client';
|
||||||
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { TPreset } from 'librechat-data-provider';
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
|
import {
|
||||||
|
useNewConvo,
|
||||||
|
useAppStartup,
|
||||||
|
useAssistantListMap,
|
||||||
|
useIdChangeEffect,
|
||||||
|
useLocalize,
|
||||||
|
} from '~/hooks';
|
||||||
import { useGetConvoIdQuery, useGetStartupConfig, useGetEndpointsQuery } from '~/data-provider';
|
import { useGetConvoIdQuery, useGetStartupConfig, useGetEndpointsQuery } from '~/data-provider';
|
||||||
import { useNewConvo, useAppStartup, useAssistantListMap, useIdChangeEffect } from '~/hooks';
|
import { getDefaultModelSpec, getModelSpecPreset, logger, isNotFoundError } from '~/utils';
|
||||||
import { getDefaultModelSpec, getModelSpecPreset, logger } from '~/utils';
|
|
||||||
import { ToolCallsMapProvider } from '~/Providers';
|
import { ToolCallsMapProvider } from '~/Providers';
|
||||||
import ChatView from '~/components/Chat/ChatView';
|
import ChatView from '~/components/Chat/ChatView';
|
||||||
|
import { NotificationSeverity } from '~/common';
|
||||||
import useAuthRedirect from './useAuthRedirect';
|
import useAuthRedirect from './useAuthRedirect';
|
||||||
import temporaryStore from '~/store/temporary';
|
import temporaryStore from '~/store/temporary';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
@ -33,6 +40,8 @@ export default function ChatRoute() {
|
||||||
useIdChangeEffect(conversationId);
|
useIdChangeEffect(conversationId);
|
||||||
const { hasSetConversation, conversation } = store.useCreateConversationAtom(index);
|
const { hasSetConversation, conversation } = store.useCreateConversationAtom(index);
|
||||||
const { newConversation } = useNewConvo();
|
const { newConversation } = useNewConvo();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
const modelsQuery = useGetModelsQuery({
|
const modelsQuery = useGetModelsQuery({
|
||||||
enabled: isAuthenticated,
|
enabled: isAuthenticated,
|
||||||
|
|
@ -92,6 +101,29 @@ export default function ChatRoute() {
|
||||||
keepLatestMessage: true,
|
keepLatestMessage: true,
|
||||||
});
|
});
|
||||||
hasSetConversation.current = true;
|
hasSetConversation.current = true;
|
||||||
|
} else if (
|
||||||
|
conversationId &&
|
||||||
|
endpointsQuery.data &&
|
||||||
|
modelsQuery.data &&
|
||||||
|
initialConvoQuery.isError &&
|
||||||
|
isNotFoundError(initialConvoQuery.error)
|
||||||
|
) {
|
||||||
|
const result = getDefaultModelSpec(startupConfig);
|
||||||
|
const spec = result?.default ?? result?.last;
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_conversation_not_found'),
|
||||||
|
severity: NotificationSeverity.WARNING,
|
||||||
|
});
|
||||||
|
logger.log(
|
||||||
|
'conversation',
|
||||||
|
'ChatRoute initialConvoQuery isNotFoundError',
|
||||||
|
initialConvoQuery.error,
|
||||||
|
);
|
||||||
|
newConversation({
|
||||||
|
modelsData: modelsQuery.data,
|
||||||
|
...(spec ? { preset: getModelSpecPreset(spec) } : {}),
|
||||||
|
});
|
||||||
|
hasSetConversation.current = true;
|
||||||
} else if (
|
} else if (
|
||||||
conversationId === Constants.NEW_CONVO &&
|
conversationId === Constants.NEW_CONVO &&
|
||||||
assistantListMap[EModelEndpoint.assistants] &&
|
assistantListMap[EModelEndpoint.assistants] &&
|
||||||
|
|
@ -125,6 +157,7 @@ export default function ChatRoute() {
|
||||||
roles,
|
roles,
|
||||||
startupConfig,
|
startupConfig,
|
||||||
initialConvoQuery.data,
|
initialConvoQuery.data,
|
||||||
|
initialConvoQuery.isError,
|
||||||
endpointsQuery.data,
|
endpointsQuery.data,
|
||||||
modelsQuery.data,
|
modelsQuery.data,
|
||||||
assistantListMap,
|
assistantListMap,
|
||||||
|
|
|
||||||
21
client/src/utils/errors.ts
Normal file
21
client/src/utils/errors.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTTP response status code from an error, regardless of the
|
||||||
|
* HTTP client used. Handles Axios errors first, then falls back to checking
|
||||||
|
* for a plain `status` property so callers never need to import axios.
|
||||||
|
*/
|
||||||
|
export const getResponseStatus = (error: unknown): number | undefined => {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
return error.response?.status;
|
||||||
|
}
|
||||||
|
if (error != null && typeof error === 'object' && 'status' in error) {
|
||||||
|
const { status } = error as { status: unknown };
|
||||||
|
if (typeof status === 'number') {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNotFoundError = (error: unknown): boolean => getResponseStatus(error) === 404;
|
||||||
|
|
@ -3,6 +3,7 @@ import type { UIActionResult } from '@mcp-ui/client';
|
||||||
import { TAskFunction } from '~/common';
|
import { TAskFunction } from '~/common';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
|
export * from './errors';
|
||||||
export * from './map';
|
export * from './map';
|
||||||
export * from './json';
|
export * from './json';
|
||||||
export * from './files';
|
export * from './files';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue