+
diff --git a/client/src/data-provider/connection.ts b/client/src/data-provider/connection.ts
new file mode 100644
index 0000000000..4ef7876d76
--- /dev/null
+++ b/client/src/data-provider/connection.ts
@@ -0,0 +1,48 @@
+import { useCallback, useRef } from 'react';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
+import { QueryKeys, Time, dataService } from 'librechat-data-provider';
+import { logger } from '~/utils';
+
+export const useHealthCheck = () => {
+ useQuery([QueryKeys.health], () => dataService.healthCheck(), {
+ refetchInterval: Time.TEN_MINUTES,
+ retry: false,
+ onError: (error) => {
+ console.error('Health check failed:', error);
+ },
+ cacheTime: 0,
+ staleTime: 0,
+ refetchOnWindowFocus: (query) => {
+ if (!query.state.dataUpdatedAt) {
+ return true;
+ }
+
+ const lastUpdated = new Date(query.state.dataUpdatedAt);
+ const tenMinutesAgo = new Date(Date.now() - Time.TEN_MINUTES);
+
+ logger.log(`Last health check: ${lastUpdated.toISOString()}`);
+ logger.log(`Ten minutes ago: ${tenMinutesAgo.toISOString()}`);
+
+ return lastUpdated < tenMinutesAgo;
+ },
+ });
+};
+
+export const useInteractionHealthCheck = () => {
+ const queryClient = useQueryClient();
+ const lastInteractionTimeRef = useRef(Date.now());
+
+ const checkHealthOnInteraction = useCallback(() => {
+ const currentTime = Date.now();
+ if (currentTime - lastInteractionTimeRef.current > Time.FIVE_MINUTES) {
+ logger.log(
+ 'Checking health on interaction. Time elapsed:',
+ currentTime - lastInteractionTimeRef.current,
+ );
+ queryClient.invalidateQueries([QueryKeys.health]);
+ lastInteractionTimeRef.current = currentTime;
+ }
+ }, [queryClient]);
+
+ return checkHealthOnInteraction;
+};
diff --git a/client/src/data-provider/index.ts b/client/src/data-provider/index.ts
index 7da93a279e..14f82f312e 100644
--- a/client/src/data-provider/index.ts
+++ b/client/src/data-provider/index.ts
@@ -1,3 +1,4 @@
+export * from './connection';
export * from './mutations';
export * from './prompts';
export * from './queries';
diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts
index 07c00c80ec..e230113b2f 100644
--- a/client/src/hooks/Chat/useChatFunctions.ts
+++ b/client/src/hooks/Chat/useChatFunctions.ts
@@ -18,8 +18,8 @@ import type {
import type { SetterOrUpdater } from 'recoil';
import type { TAskFunction, ExtendedFile } from '~/common';
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
+import { getEndpointField, logger, scrollToEnd } from '~/utils';
import useGetSender from '~/hooks/Conversations/useGetSender';
-import { getEndpointField, logger } from '~/utils';
import useUserKey from '~/hooks/Input/useUserKey';
import store from '~/store';
@@ -249,6 +249,8 @@ export default function useChatFunctions({
if (index === 0 && setLatestMessage) {
setLatestMessage(initialResponse);
}
+
+ scrollToEnd();
setSubmission(submission);
logger.log('Submission:');
logger.dir(submission, { depth: null });
diff --git a/client/src/hooks/Input/useTextarea.ts b/client/src/hooks/Input/useTextarea.ts
index ff2114fc90..e90bca54e0 100644
--- a/client/src/hooks/Input/useTextarea.ts
+++ b/client/src/hooks/Input/useTextarea.ts
@@ -8,6 +8,7 @@ import { forceResize, insertTextAtCursor, getAssistantName } from '~/utils';
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
import useGetSender from '~/hooks/Conversations/useGetSender';
import useFileHandling from '~/hooks/Files/useFileHandling';
+import { useInteractionHealthCheck } from '~/data-provider';
import { useChatContext } from '~/Providers/ChatContext';
import useLocalize from '~/hooks/useLocalize';
import { globalAudioId } from '~/common';
@@ -29,6 +30,7 @@ export default function useTextarea({
const isComposing = useRef(false);
const { handleFiles } = useFileHandling();
const assistantMap = useAssistantsMapContext();
+ const checkHealth = useInteractionHealthCheck();
const enterToSend = useRecoilValue(store.enterToSend);
const {
@@ -152,6 +154,8 @@ export default function useTextarea({
return;
}
+ checkHealth();
+
const isNonShiftEnter = e.key === 'Enter' && !e.shiftKey;
const isCtrlEnter = e.key === 'Enter' && e.ctrlKey;
@@ -185,7 +189,7 @@ export default function useTextarea({
submitButtonRef.current?.click();
}
},
- [isSubmitting, filesLoading, enterToSend, textAreaRef, submitButtonRef],
+ [isSubmitting, checkHealth, filesLoading, enterToSend, textAreaRef, submitButtonRef],
);
const handleCompositionStart = () => {
diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx
index bb293b38e7..7474ac0275 100644
--- a/client/src/routes/ChatRoute.tsx
+++ b/client/src/routes/ChatRoute.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
-import { EModelEndpoint } from 'librechat-data-provider';
+import { Constants, EModelEndpoint } from 'librechat-data-provider';
import {
useGetModelsQuery,
useGetStartupConfig,
@@ -8,14 +8,15 @@ import {
} from 'librechat-data-provider/react-query';
import type { TPreset } from 'librechat-data-provider';
import { useNewConvo, useAppStartup, useAssistantListMap } from '~/hooks';
+import { useGetConvoIdQuery, useHealthCheck } from '~/data-provider';
import { getDefaultModelSpec, getModelSpecIconURL } from '~/utils';
-import { useGetConvoIdQuery } from '~/data-provider';
import ChatView from '~/components/Chat/ChatView';
import useAuthRedirect from './useAuthRedirect';
import { Spinner } from '~/components/svg';
import store from '~/store';
export default function ChatRoute() {
+ useHealthCheck();
const { data: startupConfig } = useGetStartupConfig();
const { isAuthenticated, user } = useAuthRedirect();
useAppStartup({ startupConfig, user });
@@ -32,7 +33,7 @@ export default function ChatRoute() {
refetchOnMount: 'always',
});
const initialConvoQuery = useGetConvoIdQuery(conversationId ?? '', {
- enabled: isAuthenticated && conversationId !== 'new',
+ enabled: isAuthenticated && conversationId !== Constants.NEW_CONVO,
});
const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated });
const assistantListMap = useAssistantListMap();
@@ -45,7 +46,7 @@ export default function ChatRoute() {
return;
}
- if (conversationId === 'new' && endpointsQuery.data && modelsQuery.data) {
+ if (conversationId === Constants.NEW_CONVO && endpointsQuery.data && modelsQuery.data) {
const spec = getDefaultModelSpec(startupConfig.modelSpecs?.list);
newConversation({
@@ -73,7 +74,7 @@ export default function ChatRoute() {
});
hasSetConversation.current = true;
} else if (
- conversationId === 'new' &&
+ conversationId === Constants.NEW_CONVO &&
assistantListMap[EModelEndpoint.assistants] &&
assistantListMap[EModelEndpoint.azureAssistants]
) {
diff --git a/client/src/utils/messages.ts b/client/src/utils/messages.ts
index 16d35a518c..e7abf1320b 100644
--- a/client/src/utils/messages.ts
+++ b/client/src/utils/messages.ts
@@ -43,3 +43,12 @@ export const getTextKey = (message?: TMessage | null, convoId?: string | null) =
Constants.COMMON_DIVIDER
}${message.conversationId ?? convoId}`;
};
+
+export const scrollToEnd = () => {
+ setTimeout(() => {
+ const messagesEndElement = document.getElementById('messages-end');
+ if (messagesEndElement) {
+ messagesEndElement.scrollIntoView({ behavior: 'instant' });
+ }
+ }, 500);
+};
diff --git a/client/tailwind.config.cjs b/client/tailwind.config.cjs
index 4b1b9a47b5..f28699919f 100644
--- a/client/tailwind.config.cjs
+++ b/client/tailwind.config.cjs
@@ -6,9 +6,6 @@ module.exports = {
// darkMode: 'class',
darkMode: ['class'],
theme: {
- // colors: {
- // 'gpt-dark-gray': '#171717',
- // },
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['Roboto Mono', 'monospace'],
diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts
index 5403740cee..f91d768ec9 100644
--- a/packages/data-provider/src/api-endpoints.ts
+++ b/packages/data-provider/src/api-endpoints.ts
@@ -1,5 +1,6 @@
import type { AssistantsEndpoint } from './schemas';
+export const health = () => '/api/health';
export const user = () => '/api/user';
export const balance = () => '/api/balance';
diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts
index 4251cb9377..49288f6cf7 100644
--- a/packages/data-provider/src/data-service.ts
+++ b/packages/data-provider/src/data-service.ts
@@ -573,3 +573,7 @@ export function addTagToConversation(
export function rebuildConversationTags(): Promise
{
return request.post(endpoints.conversationTags('rebuild'));
}
+
+export function healthCheck(): Promise {
+ return request.get(endpoints.health());
+}
diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts
index 8535c475bc..a446b84d20 100644
--- a/packages/data-provider/src/keys.ts
+++ b/packages/data-provider/src/keys.ts
@@ -37,6 +37,7 @@ export enum QueryKeys {
randomPrompts = 'randomPrompts',
roles = 'roles',
conversationTags = 'conversationTags',
+ health = 'health',
}
export enum MutationKeys {