refactor(MessageHandler -> useServerStream): convert all relating files to TS and correct typings based on this change: properly refactor MessageHandler to a custom hook, where it's passed a submission object to instantiate the stream. This is the bare minimum groundwork for potentially having multiple streams running, which would be a big project to modularize a lot of the global state into maps/multiple streams, particular useful for having multiple views in place

This commit is contained in:
Danny Avila 2023-08-18 12:10:30 -04:00 committed by Danny Avila
parent 8b4d3c2c21
commit c74c68a135
5 changed files with 83 additions and 87 deletions

View file

@ -1,7 +1,7 @@
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { parseConvo, getResponseSender } from 'librechat-data-provider'; 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'; import store from '~/store';
type TAskProps = { type TAskProps = {
@ -98,7 +98,7 @@ const useMessageHandler = () => {
error: false, error: false,
}; };
const submission = { const submission: TSubmission = {
conversation: { conversation: {
...currentConversation, ...currentConversation,
conversationId, conversationId,

View file

@ -1,20 +1,29 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'; import { useResetRecoilState, useSetRecoilState } from 'recoil';
import { SSE, createPayload } from 'librechat-data-provider'; 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 { useAuthContext } from '~/hooks/AuthContext';
import store from '~/store'; import store from '~/store';
export default function MessageHandler() { type TResData = {
const submission = useRecoilValue(store.submission); plugin: TPlugin;
const setIsSubmitting = useSetRecoilState(store.isSubmitting); final?: boolean;
initial?: boolean;
requestMessage: TMessage;
responseMessage: TMessage;
conversation: TConversation;
};
export default function useServerStream(submission: TSubmission | null) {
const setMessages = useSetRecoilState(store.messages); const setMessages = useSetRecoilState(store.messages);
const setIsSubmitting = useSetRecoilState(store.isSubmitting);
const setConversation = useSetRecoilState(store.conversation); const setConversation = useSetRecoilState(store.conversation);
const resetLatestMessage = useResetRecoilState(store.latestMessage); const resetLatestMessage = useResetRecoilState(store.latestMessage);
const { token } = useAuthContext(); const { token } = useAuthContext();
const { refreshConversations } = store.useConversations(); const { refreshConversations } = store.useConversations();
const messageHandler = (data, submission) => { const messageHandler = (data: string, submission: TSubmission) => {
const { const {
messages, messages,
message, message,
@ -30,9 +39,9 @@ export default function MessageHandler() {
{ {
...initialResponse, ...initialResponse,
text: data, text: data,
parentMessageId: message?.overrideParentMessageId, parentMessageId: message?.overrideParentMessageId ?? null,
messageId: message?.overrideParentMessageId + '_', messageId: message?.overrideParentMessageId + '_',
plugin: plugin ? plugin : null, plugin: plugin ?? null,
submitting: true, submitting: true,
// unfinished: true // unfinished: true
}, },
@ -46,7 +55,7 @@ export default function MessageHandler() {
text: data, text: data,
parentMessageId: message?.messageId, parentMessageId: message?.messageId,
messageId: message?.messageId + '_', messageId: message?.messageId + '_',
plugin: plugin ? plugin : null, plugin: plugin ?? null,
submitting: true, submitting: true,
// unfinished: 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 { requestMessage, responseMessage, conversation } = data;
const { messages, isRegenerate = false, isEdited = false } = submission; 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 { const {
messages, messages,
message, message,
@ -98,7 +107,7 @@ export default function MessageHandler() {
...(isEdited ? messages.slice(0, -1) : messages), ...(isEdited ? messages.slice(0, -1) : messages),
{ {
...initialResponse, ...initialResponse,
parentMessageId: message?.overrideParentMessageId, parentMessageId: message?.overrideParentMessageId ?? null,
messageId: message?.overrideParentMessageId + '_', messageId: message?.overrideParentMessageId + '_',
submitting: true, submitting: true,
}, },
@ -117,14 +126,16 @@ export default function MessageHandler() {
} }
const { conversationId } = message; const { conversationId } = message;
setConversation((prevState) => ({ setConversation((prevState) =>
...prevState, tConversationSchema.parse({
conversationId, ...prevState,
})); conversationId,
}),
);
resetLatestMessage(); resetLatestMessage();
}; };
const finalHandler = (data, submission) => { const finalHandler = (data: TResData, submission: TSubmission) => {
const { requestMessage, responseMessage, conversation } = data; const { requestMessage, responseMessage, conversation } = data;
const { messages, isRegenerate = false, isEdited = false } = submission; 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; const { messages, message } = submission;
console.log('Error:', data); console.log('Error:', data);
const errorResponse = { const errorResponse = tMessageSchema.parse({
...data, ...data,
error: true, error: true,
parentMessageId: message?.messageId, parentMessageId: message?.messageId,
}; });
setIsSubmitting(false); setIsSubmitting(false);
setMessages([...messages, message, errorResponse]); setMessages([...messages, message, errorResponse]);
return; return;
}; };
const abortConversation = (conversationId) => { const abortConversation = (conversationId = '', submission: TSubmission) => {
console.log(submission); console.log(submission);
const { endpoint } = submission?.conversation || {}; const { endpoint } = submission?.conversation || {};
@ -212,7 +223,7 @@ export default function MessageHandler() {
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
}); });
events.onmessage = (e) => { events.onmessage = (e: MessageEvent) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
if (data.final) { if (data.final) {
@ -227,8 +238,8 @@ export default function MessageHandler() {
createdHandler(data, { ...submission, message }); createdHandler(data, { ...submission, message });
console.log('created', message); console.log('created', message);
} else { } else {
let text = data.text || data.response; const text = data.text || data.response;
let { initial, plugin } = data; const { initial, plugin } = data;
if (initial) { if (initial) {
console.log(data); console.log(data);
} }
@ -242,9 +253,9 @@ export default function MessageHandler() {
events.onopen = () => console.log('connection is opened'); events.onopen = () => console.log('connection is opened');
events.oncancel = () => 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.'); console.log('error in opening conn.');
events.close(); events.close();
@ -268,6 +279,4 @@ export default function MessageHandler() {
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [submission]); }, [submission]);
return null;
} }

View file

@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { import {
useGetEndpointsQuery, useGetEndpointsQuery,
@ -9,8 +9,7 @@ import {
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { Nav, MobileNav } from '~/components/Nav'; import { Nav, MobileNav } from '~/components/Nav';
import { useAuthContext } from '~/hooks/AuthContext'; import { useAuthContext, useServerStream } from '~/hooks';
import MessageHandler from './MessageHandler';
import store from '~/store'; import store from '~/store';
export default function Root() { export default function Root() {
@ -19,6 +18,9 @@ export default function Root() {
return savedNavVisible !== null ? JSON.parse(savedNavVisible) : false; return savedNavVisible !== null ? JSON.parse(savedNavVisible) : false;
}); });
const submission = useRecoilValue(store.submission);
useServerStream(submission ?? null);
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
const setEndpointsConfig = useSetRecoilState(store.endpointsConfig); const setEndpointsConfig = useSetRecoilState(store.endpointsConfig);
const setPresets = useSetRecoilState(store.presets); const setPresets = useSetRecoilState(store.presets);
@ -71,7 +73,6 @@ export default function Root() {
</div> </div>
</div> </div>
</div> </div>
<MessageHandler />
</> </>
); );
} }

View file

@ -12,32 +12,6 @@ export enum EModelEndpoint {
export const eModelEndpointSchema = z.nativeEnum(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<typeof tMessageSchema>;
export const tPluginAuthConfigSchema = z.object({ export const tPluginAuthConfigSchema = z.object({
authField: z.string(), authField: z.string(),
label: z.string(), label: z.string(),
@ -76,6 +50,36 @@ export const tAgentOptionsSchema = z.object({
temperature: z.number(), 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<typeof tMessageSchema>;
export const tConversationSchema = z.object({ export const tConversationSchema = z.object({
conversationId: z.string().nullable(), conversationId: z.string().nullable(),
title: z.string(), title: z.string(),

View file

@ -1,4 +1,4 @@
import type { TMessage, EModelEndpoint, TConversation, TEndpointOption } from './schemas'; import type { TPlugin, TMessage, TConversation, TEndpointOption } from './schemas';
export * from './schemas'; export * from './schemas';
@ -7,32 +7,14 @@ export type TMessages = TMessage[];
export type TMessagesAtom = TMessages | null; export type TMessagesAtom = TMessages | null;
export type TSubmission = { export type TSubmission = {
clientId?: string; plugin?: TPlugin;
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;
message: TMessage; message: TMessage;
isEdited?: boolean;
messages: TMessage[];
isRegenerate?: boolean;
conversationId?: string;
initialResponse: TMessage;
conversation: TConversation;
endpointOption: TEndpointOption; endpointOption: TEndpointOption;
}; };
@ -167,7 +149,7 @@ export type TResetPassword = {
}; };
export type TStartupConfig = { export type TStartupConfig = {
appTitle: boolean; appTitle: string;
googleLoginEnabled: boolean; googleLoginEnabled: boolean;
openidLoginEnabled: boolean; openidLoginEnabled: boolean;
githubLoginEnabled: boolean; githubLoginEnabled: boolean;