From ead1c3c7970a5e1a565237abd9201d64583b958c Mon Sep 17 00:00:00 2001 From: Danny Avila <110412045+danny-avila@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:30:38 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Error=20Handling=20Improv?= =?UTF-8?q?ements=20(#1518)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style(Icon): remove error bubble from message icon * fix(custom): `initializeClient` now throws error if apiKey or baseURL are admin provided but no env var was found * refactor(tPresetSchema): match `conversationId` type to `tConversationSchema` but optional, use `extendedModelEndpointSchema` for `endpoint` * fix(useSSE): minor improvements - use `completed` set to avoid submitting unecessary abort request - set preset with `newConversation` calls using initial conversation settings to prevent default Preset override as well as default settings - return if there is a parsing error within `onerror` as expected errors from server are properly formatted --- .../Endpoints/custom/initializeClient.js | 10 ++++ client/src/components/Endpoints/Icon.tsx | 6 +-- client/src/hooks/useSSE.ts | 50 +++++++++++++++---- packages/data-provider/package.json | 2 +- packages/data-provider/src/schemas.ts | 3 +- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/api/server/services/Endpoints/custom/initializeClient.js b/api/server/services/Endpoints/custom/initializeClient.js index 93182fa89e..0c0ad9e7e2 100644 --- a/api/server/services/Endpoints/custom/initializeClient.js +++ b/api/server/services/Endpoints/custom/initializeClient.js @@ -4,6 +4,8 @@ const { isUserProvided, extractEnvVariable } = require('~/server/utils'); const getCustomConfig = require('~/cache/getCustomConfig'); const { OpenAIClient } = require('~/app'); +const envVarRegex = /^\${(.+)}$/; + const { PROXY } = process.env; const initializeClient = async ({ req, res, endpointOption }) => { @@ -20,6 +22,14 @@ const initializeClient = async ({ req, res, endpointOption }) => { const CUSTOM_API_KEY = extractEnvVariable(endpointConfig.apiKey); const CUSTOM_BASE_URL = extractEnvVariable(endpointConfig.baseURL); + if (CUSTOM_API_KEY.match(envVarRegex)) { + throw new Error(`Missing API Key for ${endpoint}.`); + } + + if (CUSTOM_BASE_URL.match(envVarRegex)) { + throw new Error(`Missing Base URL for ${endpoint}.`); + } + const customOptions = { addParams: endpointConfig.addParams, dropParams: endpointConfig.dropParams, diff --git a/client/src/components/Endpoints/Icon.tsx b/client/src/components/Endpoints/Icon.tsx index 5d9d3fff7c..fe30f1c6b0 100644 --- a/client/src/components/Endpoints/Icon.tsx +++ b/client/src/components/Endpoints/Icon.tsx @@ -16,7 +16,7 @@ import { cn } from '~/utils'; const Icon: React.FC = (props) => { const { user } = useAuthContext(); - const { size = 30, isCreatedByUser, button, model = '', endpoint, error, jailbreak } = props; + const { size = 30, isCreatedByUser, button, model = '', endpoint, jailbreak } = props; if (isCreatedByUser) { const username = user?.name || 'User'; @@ -130,11 +130,11 @@ const Icon: React.FC = (props) => { )} > {icon} - {error && ( + {/* {error && ( ! - )} + )} */} ); } diff --git a/client/src/hooks/useSSE.ts b/client/src/hooks/useSSE.ts index 0a6ca109d3..94629b2d7a 100644 --- a/client/src/hooks/useSSE.ts +++ b/client/src/hooks/useSSE.ts @@ -1,14 +1,15 @@ import { v4 } from 'uuid'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { /* @ts-ignore */ SSE, EndpointURLs, createPayload, + tPresetSchema, tMessageSchema, - tConvoUpdateSchema, EModelEndpoint, + tConvoUpdateSchema, removeNullishValues, } from 'librechat-data-provider'; import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query'; @@ -31,6 +32,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const setStorage = useSetStorage(); const { conversationId: paramId } = useParams(); const { token, isAuthenticated } = useAuthContext(); + const [completed, setCompleted] = useState(new Set()); const { addConvo, setMessages, @@ -172,6 +174,8 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const { requestMessage, responseMessage, conversation } = data; const { messages, conversation: submissionConvo, isRegenerate = false } = submission; + setCompleted((prev) => new Set(prev.add(submission?.initialResponse?.messageId))); + // update the messages if (isRegenerate) { setMessages([...messages, responseMessage]); @@ -212,7 +216,10 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const errorHandler = ({ data, submission }: { data?: TResData; submission: TSubmission }) => { const { messages, message, initialResponse } = submission; + setCompleted((prev) => new Set(prev.add(initialResponse.messageId))); + const conversationId = message?.conversationId ?? submission?.conversationId; + const parseErrorResponse = (data: TResData | Partial) => { const metadata = data['responseMessage'] ?? data; const errorMessage = { @@ -237,7 +244,10 @@ export default function useSSE(submission: TSubmission | null, index = 0) { conversationId: convoId, }); setMessages([...messages, message, errorResponse]); - newConversation({ template: { conversationId: convoId } }); + newConversation({ + template: { conversationId: convoId }, + preset: tPresetSchema.parse(submission?.conversation), + }); setIsSubmitting(false); return; } @@ -246,7 +256,10 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const convoId = v4(); const errorResponse = parseErrorResponse(data); setMessages([...messages, message, errorResponse]); - newConversation({ template: { conversationId: convoId } }); + newConversation({ + template: { conversationId: convoId }, + preset: tPresetSchema.parse(submission?.conversation), + }); setIsSubmitting(false); return; } @@ -260,7 +273,10 @@ export default function useSSE(submission: TSubmission | null, index = 0) { setMessages([...messages, message, errorResponse]); if (data.conversationId && paramId === 'new') { - newConversation({ template: { conversationId: data.conversationId } }); + newConversation({ + template: { conversationId: data.conversationId }, + preset: tPresetSchema.parse(submission?.conversation), + }); } setIsSubmitting(false); @@ -334,7 +350,10 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const errorResponse = tMessageSchema.parse(errorMessage); setMessages([...submission.messages, submission.message, errorResponse]); - newConversation({ template: { conversationId: convoId } }); + newConversation({ + template: { conversationId: convoId }, + preset: tPresetSchema.parse(submission?.conversation), + }); setIsSubmitting(false); }); return; @@ -389,8 +408,20 @@ export default function useSSE(submission: TSubmission | null, index = 0) { events.onopen = () => console.log('connection is opened'); - events.oncancel = () => - abortConversation(message?.conversationId ?? submission?.conversationId, submission); + events.oncancel = () => { + const streamKey = submission?.initialResponse?.messageId; + if (completed.has(streamKey)) { + setIsSubmitting(false); + setCompleted((prev) => { + prev.delete(streamKey); + return new Set(prev); + }); + return; + } + + setCompleted((prev) => new Set(prev.add(streamKey))); + return abortConversation(message?.conversationId ?? submission?.conversationId, submission); + }; events.onerror = function (e: MessageEvent) { console.log('error in server stream.'); @@ -403,10 +434,11 @@ export default function useSSE(submission: TSubmission | null, index = 0) { } catch (error) { console.error(error); console.log(e); + setIsSubmitting(false); + return; } errorHandler({ data, submission: { ...submission, message } }); - events.oncancel(); }; setIsSubmitting(true); diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index 339e60732f..c0b0a05209 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.3.4", + "version": "0.3.5", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 950cc18e4c..caa951fa6a 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -200,11 +200,12 @@ export const tPresetSchema = tConversationSchema }) .merge( z.object({ - conversationId: z.string().optional(), + conversationId: z.string().nullable().optional(), presetId: z.string().nullable().optional(), title: z.string().nullable().optional(), defaultPreset: z.boolean().optional(), order: z.number().optional(), + endpoint: extendedModelEndpointSchema.nullable(), }), );