diff --git a/client/src/hooks/useMessageHandler.ts b/client/src/hooks/useMessageHandler.ts index 21deadb18d..47c9a44eeb 100644 --- a/client/src/hooks/useMessageHandler.ts +++ b/client/src/hooks/useMessageHandler.ts @@ -1,7 +1,7 @@ import { v4 } from 'uuid'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { parseConvo, getResponseSender } from 'librechat-data-provider'; -import type { TMessage } from 'librechat-data-provider'; +import type { TMessage, TSubmission } from 'librechat-data-provider'; import store from '~/store'; type TAskProps = { @@ -98,7 +98,7 @@ const useMessageHandler = () => { error: false, }; - const submission = { + const submission: TSubmission = { conversation: { ...currentConversation, conversationId, diff --git a/client/src/routes/MessageHandler.jsx b/client/src/hooks/useServerStream.ts similarity index 79% rename from client/src/routes/MessageHandler.jsx rename to client/src/hooks/useServerStream.ts index 8769497eb4..f8ecbcf72e 100644 --- a/client/src/routes/MessageHandler.jsx +++ b/client/src/hooks/useServerStream.ts @@ -1,20 +1,29 @@ import { useEffect } from 'react'; -import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'; -import { SSE, createPayload } from 'librechat-data-provider'; +import { useResetRecoilState, useSetRecoilState } from 'recoil'; +import { SSE, createPayload, tMessageSchema, tConversationSchema } from 'librechat-data-provider'; +import type { TPlugin, TMessage, TConversation, TSubmission } from 'librechat-data-provider'; import { useAuthContext } from '~/hooks/AuthContext'; import store from '~/store'; -export default function MessageHandler() { - const submission = useRecoilValue(store.submission); - const setIsSubmitting = useSetRecoilState(store.isSubmitting); +type TResData = { + plugin: TPlugin; + final?: boolean; + initial?: boolean; + requestMessage: TMessage; + responseMessage: TMessage; + conversation: TConversation; +}; + +export default function useServerStream(submission: TSubmission | null) { const setMessages = useSetRecoilState(store.messages); + const setIsSubmitting = useSetRecoilState(store.isSubmitting); const setConversation = useSetRecoilState(store.conversation); const resetLatestMessage = useResetRecoilState(store.latestMessage); const { token } = useAuthContext(); const { refreshConversations } = store.useConversations(); - const messageHandler = (data, submission) => { + const messageHandler = (data: string, submission: TSubmission) => { const { messages, message, @@ -30,9 +39,9 @@ export default function MessageHandler() { { ...initialResponse, text: data, - parentMessageId: message?.overrideParentMessageId, + parentMessageId: message?.overrideParentMessageId ?? null, messageId: message?.overrideParentMessageId + '_', - plugin: plugin ? plugin : null, + plugin: plugin ?? null, submitting: true, // unfinished: true }, @@ -46,7 +55,7 @@ export default function MessageHandler() { text: data, parentMessageId: message?.messageId, messageId: message?.messageId + '_', - plugin: plugin ? plugin : null, + plugin: plugin ?? null, submitting: true, // unfinished: true }, @@ -54,7 +63,7 @@ export default function MessageHandler() { } }; - const cancelHandler = (data, submission) => { + const cancelHandler = (data: TResData, submission: TSubmission) => { const { requestMessage, responseMessage, conversation } = data; const { messages, isRegenerate = false, isEdited = false } = submission; @@ -84,7 +93,7 @@ export default function MessageHandler() { })); }; - const createdHandler = (data, submission) => { + const createdHandler = (data: TResData, submission: TSubmission) => { const { messages, message, @@ -98,7 +107,7 @@ export default function MessageHandler() { ...(isEdited ? messages.slice(0, -1) : messages), { ...initialResponse, - parentMessageId: message?.overrideParentMessageId, + parentMessageId: message?.overrideParentMessageId ?? null, messageId: message?.overrideParentMessageId + '_', submitting: true, }, @@ -117,14 +126,16 @@ export default function MessageHandler() { } const { conversationId } = message; - setConversation((prevState) => ({ - ...prevState, - conversationId, - })); + setConversation((prevState) => + tConversationSchema.parse({ + ...prevState, + conversationId, + }), + ); resetLatestMessage(); }; - const finalHandler = (data, submission) => { + const finalHandler = (data: TResData, submission: TSubmission) => { const { requestMessage, responseMessage, conversation } = data; const { messages, isRegenerate = false, isEdited = false } = submission; @@ -154,21 +165,21 @@ export default function MessageHandler() { })); }; - const errorHandler = (data, submission) => { + const errorHandler = (data: TResData, submission: TSubmission) => { const { messages, message } = submission; console.log('Error:', data); - const errorResponse = { + const errorResponse = tMessageSchema.parse({ ...data, error: true, parentMessageId: message?.messageId, - }; + }); setIsSubmitting(false); setMessages([...messages, message, errorResponse]); return; }; - const abortConversation = (conversationId) => { + const abortConversation = (conversationId = '', submission: TSubmission) => { console.log(submission); const { endpoint } = submission?.conversation || {}; @@ -212,7 +223,7 @@ export default function MessageHandler() { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, }); - events.onmessage = (e) => { + events.onmessage = (e: MessageEvent) => { const data = JSON.parse(e.data); if (data.final) { @@ -227,8 +238,8 @@ export default function MessageHandler() { createdHandler(data, { ...submission, message }); console.log('created', message); } else { - let text = data.text || data.response; - let { initial, plugin } = data; + const text = data.text || data.response; + const { initial, plugin } = data; if (initial) { console.log(data); } @@ -242,9 +253,9 @@ export default function MessageHandler() { events.onopen = () => console.log('connection is opened'); events.oncancel = () => - abortConversation(message?.conversationId || submission?.conversationId); + abortConversation(message?.conversationId ?? submission?.conversationId, submission); - events.onerror = function (e) { + events.onerror = function (e: MessageEvent) { console.log('error in opening conn.'); events.close(); @@ -268,6 +279,4 @@ export default function MessageHandler() { }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [submission]); - - return null; } diff --git a/client/src/routes/Root.jsx b/client/src/routes/Root.tsx similarity index 91% rename from client/src/routes/Root.jsx rename to client/src/routes/Root.tsx index 71acb0686c..36b9ace6b7 100644 --- a/client/src/routes/Root.jsx +++ b/client/src/routes/Root.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useEffect, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Outlet } from 'react-router-dom'; import { useGetEndpointsQuery, @@ -9,8 +9,7 @@ import { } from 'librechat-data-provider'; import { Nav, MobileNav } from '~/components/Nav'; -import { useAuthContext } from '~/hooks/AuthContext'; -import MessageHandler from './MessageHandler'; +import { useAuthContext, useServerStream } from '~/hooks'; import store from '~/store'; export default function Root() { @@ -19,6 +18,9 @@ export default function Root() { return savedNavVisible !== null ? JSON.parse(savedNavVisible) : false; }); + const submission = useRecoilValue(store.submission); + useServerStream(submission ?? null); + const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); const setEndpointsConfig = useSetRecoilState(store.endpointsConfig); const setPresets = useSetRecoilState(store.presets); @@ -71,7 +73,6 @@ export default function Root() { - ); } diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index cd2fb3af48..a66860390e 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -12,32 +12,6 @@ export enum EModelEndpoint { export const eModelEndpointSchema = z.nativeEnum(EModelEndpoint); -export const tMessageSchema = z.object({ - messageId: z.string(), - clientId: z.string().nullable().optional(), - conversationId: z.string().nullable(), - parentMessageId: z.string().nullable(), - sender: z.string(), - text: z.string(), - isCreatedByUser: z.boolean(), - error: z.boolean(), - createdAt: z - .string() - .optional() - .default(() => new Date().toISOString()), - updatedAt: z - .string() - .optional() - .default(() => new Date().toISOString()), - current: z.boolean().optional(), - unfinished: z.boolean().optional(), - submitting: z.boolean().optional(), - searchResult: z.boolean().optional(), - finish_reason: z.string().optional(), -}); - -export type TMessage = z.input; - export const tPluginAuthConfigSchema = z.object({ authField: z.string(), label: z.string(), @@ -76,6 +50,36 @@ export const tAgentOptionsSchema = z.object({ temperature: z.number(), }); +export const tMessageSchema = z.object({ + messageId: z.string(), + clientId: z.string().nullable().optional(), + conversationId: z.string().nullable(), + parentMessageId: z.string().nullable(), + responseMessageId: z.string().nullable().optional(), + overrideParentMessageId: z.string().nullable().optional(), + plugin: tPluginSchema.nullable().optional(), + sender: z.string(), + text: z.string(), + generation: z.string().nullable().optional(), + isCreatedByUser: z.boolean(), + error: z.boolean(), + createdAt: z + .string() + .optional() + .default(() => new Date().toISOString()), + updatedAt: z + .string() + .optional() + .default(() => new Date().toISOString()), + current: z.boolean().optional(), + unfinished: z.boolean().optional(), + submitting: z.boolean().optional(), + searchResult: z.boolean().optional(), + finish_reason: z.string().optional(), +}); + +export type TMessage = z.input; + export const tConversationSchema = z.object({ conversationId: z.string().nullable(), title: z.string(), diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 81c2e75304..40bb5ad1e0 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -1,4 +1,4 @@ -import type { TMessage, EModelEndpoint, TConversation, TEndpointOption } from './schemas'; +import type { TPlugin, TMessage, TConversation, TEndpointOption } from './schemas'; export * from './schemas'; @@ -7,32 +7,14 @@ export type TMessages = TMessage[]; export type TMessagesAtom = TMessages | null; export type TSubmission = { - clientId?: string; - context?: string; - conversationId?: string; - conversationSignature?: string; - current: boolean; - endpoint: EModelEndpoint | null; - invocationId: number; - isCreatedByUser: boolean; - jailbreak: boolean; - jailbreakConversationId?: string; - messageId: string; - overrideParentMessageId?: string | boolean; - parentMessageId?: string; - sender: string; - systemMessage?: string; - text: string; - toneStyle?: string; - model?: string; - promptPrefix?: string; - temperature?: number; - top_p?: number; - presence_penalty?: number; - frequence_penalty?: number; - isEdited?: boolean; - conversation: TConversation; + plugin?: TPlugin; message: TMessage; + isEdited?: boolean; + messages: TMessage[]; + isRegenerate?: boolean; + conversationId?: string; + initialResponse: TMessage; + conversation: TConversation; endpointOption: TEndpointOption; }; @@ -167,7 +149,7 @@ export type TResetPassword = { }; export type TStartupConfig = { - appTitle: boolean; + appTitle: string; googleLoginEnabled: boolean; openidLoginEnabled: boolean; githubLoginEnabled: boolean;