diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index 8f2b702a3b..866e25a262 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -31,7 +31,7 @@ import type { SharedLinksResponse, } from 'librechat-data-provider'; import type { ConversationCursorData } from '~/utils/convos'; -import { findConversationInInfinite } from '~/utils'; +import { findConversationInInfinite, isNotFoundError } from '~/utils'; export const useGetPresetsQuery = ( config?: UseQueryOptions, @@ -71,6 +71,12 @@ export const useGetConvoIdQuery = ( refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, + retry: (failureCount, error) => { + if (isNotFoundError(error)) { + return false; + } + return failureCount < 3; + }, ...config, }, ); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index a9f8805d9b..e0dad68431 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -842,6 +842,7 @@ "com_ui_controls": "Controls", "com_ui_conversation": "conversation", "com_ui_conversation_label": "{{title}} conversation", + "com_ui_conversation_not_found": "Conversation not found", "com_ui_conversations": "conversations", "com_ui_convo_archived": "Conversation archived", "com_ui_convo_delete_error": "Failed to delete conversation", diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx index 0670ee1e85..c8fea73470 100644 --- a/client/src/routes/ChatRoute.tsx +++ b/client/src/routes/ChatRoute.tsx @@ -1,15 +1,22 @@ import { useEffect } from 'react'; -import { Spinner } from '@librechat/client'; import { useParams } from 'react-router-dom'; import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { Spinner, useToastContext } from '@librechat/client'; import { Constants, EModelEndpoint } from 'librechat-data-provider'; import { useGetModelsQuery } from 'librechat-data-provider/react-query'; import type { TPreset } from 'librechat-data-provider'; +import { + useNewConvo, + useAppStartup, + useAssistantListMap, + useIdChangeEffect, + useLocalize, +} from '~/hooks'; import { useGetConvoIdQuery, useGetStartupConfig, useGetEndpointsQuery } from '~/data-provider'; -import { useNewConvo, useAppStartup, useAssistantListMap, useIdChangeEffect } from '~/hooks'; -import { getDefaultModelSpec, getModelSpecPreset, logger } from '~/utils'; +import { getDefaultModelSpec, getModelSpecPreset, logger, isNotFoundError } from '~/utils'; import { ToolCallsMapProvider } from '~/Providers'; import ChatView from '~/components/Chat/ChatView'; +import { NotificationSeverity } from '~/common'; import useAuthRedirect from './useAuthRedirect'; import temporaryStore from '~/store/temporary'; import store from '~/store'; @@ -33,6 +40,8 @@ export default function ChatRoute() { useIdChangeEffect(conversationId); const { hasSetConversation, conversation } = store.useCreateConversationAtom(index); const { newConversation } = useNewConvo(); + const { showToast } = useToastContext(); + const localize = useLocalize(); const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated, @@ -92,6 +101,29 @@ export default function ChatRoute() { keepLatestMessage: 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 ( conversationId === Constants.NEW_CONVO && assistantListMap[EModelEndpoint.assistants] && @@ -125,6 +157,7 @@ export default function ChatRoute() { roles, startupConfig, initialConvoQuery.data, + initialConvoQuery.isError, endpointsQuery.data, modelsQuery.data, assistantListMap, diff --git a/client/src/utils/errors.ts b/client/src/utils/errors.ts new file mode 100644 index 0000000000..04666c5313 --- /dev/null +++ b/client/src/utils/errors.ts @@ -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; diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index eede48f244..b8117b2677 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -3,6 +3,7 @@ import type { UIActionResult } from '@mcp-ui/client'; import { TAskFunction } from '~/common'; import logger from './logger'; +export * from './errors'; export * from './map'; export * from './json'; export * from './files';