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(), }), );