Skip to content

Commit fe0d6ae

Browse files
authored
🔧 fix: Improve Endpoint Handling and Address Edge Cases (danny-avila#1486)
* fix(TEndpointsConfig): resolve property access issues with typesafe helper function * fix: undefined or null endpoint edge case * refactor(mapEndpoints -> endpoints): renamed module to be more general for endpoint handling, wrote unit tests, export all helpers
1 parent cb0f922 commit fe0d6ae

File tree

24 files changed

+275
-99
lines changed

24 files changed

+275
-99
lines changed

client/src/components/Chat/Input/ChatForm.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,23 @@ export default function ChatForm({ index = 0 }) {
4545
<div className="flex w-full items-center">
4646
<div className="[&:has(textarea:focus)]:border-token-border-xheavy border-token-border-heavy shadow-xs dark:shadow-xs relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border border-black/10 bg-white shadow-[0_0_0_2px_rgba(255,255,255,0.95)] dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:shadow-[0_0_0_2px_rgba(52,53,65,0.95)] [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]">
4747
<Images files={files} setFiles={setFiles} setFilesLoading={setFilesLoading} />
48-
<Textarea
49-
value={text}
50-
disabled={requiresKey}
51-
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setText(e.target.value)}
52-
setText={setText}
53-
submitMessage={submitMessage}
54-
endpoint={endpoint}
55-
/>
48+
{endpoint && (
49+
<Textarea
50+
value={text}
51+
disabled={requiresKey}
52+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setText(e.target.value)}
53+
setText={setText}
54+
submitMessage={submitMessage}
55+
endpoint={endpoint}
56+
/>
57+
)}
5658
<AttachFile endpoint={endpoint ?? ''} disabled={requiresKey} />
5759
{isSubmitting && showStopButton ? (
5860
<StopButton stop={handleStopGenerating} setShowStopButton={setShowStopButton} />
5961
) : (
60-
<SendButton text={text} disabled={filesLoading || isSubmitting || requiresKey} />
62+
endpoint && (
63+
<SendButton text={text} disabled={filesLoading || isSubmitting || requiresKey} />
64+
)
6165
)}
6266
</div>
6367
</div>

client/src/components/Chat/Landing.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
33
import { EModelEndpoint } from 'librechat-data-provider';
44
import { icons } from './Menus/Endpoints/Icons';
55
import { useChatContext } from '~/Providers';
6+
import { getEndpointField } from '~/utils';
67
import { useLocalize } from '~/hooks';
78

89
export default function Landing({ Header }: { Header?: ReactNode }) {
@@ -19,21 +20,24 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
1920
endpoint = EModelEndpoint.openAI;
2021
}
2122

22-
const iconKey = endpointsConfig?.[endpoint ?? '']?.type ? 'unknown' : endpoint ?? 'unknown';
23+
const endpointType = getEndpointField(endpointsConfig, endpoint, 'type');
24+
const iconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL');
25+
const iconKey = endpointType ? 'unknown' : endpoint ?? 'unknown';
2326

2427
return (
2528
<div className="relative h-full">
2629
<div className="absolute left-0 right-0">{Header && Header}</div>
2730
<div className="flex h-full flex-col items-center justify-center">
2831
<div className="mb-3 h-[72px] w-[72px]">
2932
<div className="gizmo-shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black">
30-
{icons[iconKey]({
31-
size: 41,
32-
context: 'landing',
33-
className: 'h-2/3 w-2/3',
34-
endpoint: endpoint as EModelEndpoint | string,
35-
iconURL: endpointsConfig?.[endpoint ?? ''].iconURL,
36-
})}
33+
{endpoint &&
34+
icons[iconKey]({
35+
size: 41,
36+
context: 'landing',
37+
className: 'h-2/3 w-2/3',
38+
endpoint: endpoint,
39+
iconURL: iconURL,
40+
})}
3741
</div>
3842
</div>
3943
<div className="mb-5 text-2xl font-medium dark:text-white">

client/src/components/Chat/Menus/Endpoints/MenuItem.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import type { FC } from 'react';
77
import type { TPreset } from 'librechat-data-provider';
88
import { useLocalize, useUserKey, useDefaultConvo } from '~/hooks';
99
import { SetKeyDialog } from '~/components/Input/SetKeyDialog';
10+
import { cn, getEndpointField } from '~/utils';
1011
import { useChatContext } from '~/Providers';
11-
import store from '~/store';
1212
import { icons } from './Icons';
13-
import { cn } from '~/utils';
13+
import store from '~/store';
1414

1515
type MenuItemProps = {
1616
title: string;
@@ -50,7 +50,7 @@ const MenuItem: FC<MenuItemProps> = ({
5050
const template: Partial<TPreset> = { endpoint: newEndpoint, conversationId: 'new' };
5151
const { conversationId } = conversation ?? {};
5252
if (modularChat && conversationId && conversationId !== 'new') {
53-
template.endpointType = endpointsConfig?.[newEndpoint]?.type;
53+
template.endpointType = getEndpointField(endpointsConfig, newEndpoint, 'type');
5454

5555
const currentConvo = getDefaultConversation({
5656
/* target endpointType is necessary to avoid endpoint mixing */
@@ -66,7 +66,7 @@ const MenuItem: FC<MenuItemProps> = ({
6666
}
6767
};
6868

69-
const endpointType = endpointsConfig?.[endpoint ?? '']?.type;
69+
const endpointType = getEndpointField(endpointsConfig, endpoint, 'type');
7070
const iconKey = endpointType ? 'unknown' : endpoint ?? 'unknown';
7171
const Icon = icons[iconKey];
7272

@@ -88,7 +88,7 @@ const MenuItem: FC<MenuItemProps> = ({
8888
endpoint={endpoint}
8989
context={'menu-item'}
9090
className="icon-md shrink-0 dark:text-white"
91-
iconURL={endpointsConfig?.[endpoint ?? '']?.iconURL}
91+
iconURL={getEndpointField(endpointsConfig, endpoint, 'iconURL')}
9292
/>
9393
}
9494
<div>
@@ -167,7 +167,7 @@ const MenuItem: FC<MenuItemProps> = ({
167167
endpoint={endpoint}
168168
endpointType={endpointType}
169169
onOpenChange={setDialogOpen}
170-
userProvideURL={endpointsConfig?.[endpoint ?? '']?.userProvideURL}
170+
userProvideURL={getEndpointField(endpointsConfig, endpoint, 'userProvideURL')}
171171
/>
172172
)}
173173
</>

client/src/components/Chat/Menus/Endpoints/MenuItems.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Close } from '@radix-ui/react-popover';
33
import { EModelEndpoint, alternateName } from 'librechat-data-provider';
44
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
55
import MenuSeparator from '../UI/MenuSeparator';
6+
import { getEndpointField } from '~/utils';
67
import MenuItem from './MenuItem';
78

89
const EndpointItems: FC<{
@@ -19,7 +20,11 @@ const EndpointItems: FC<{
1920
} else if (!endpointsConfig?.[endpoint]) {
2021
return null;
2122
}
22-
const userProvidesKey = endpointsConfig?.[endpoint]?.userProvide;
23+
const userProvidesKey: boolean | null | undefined = getEndpointField(
24+
endpointsConfig,
25+
endpoint,
26+
'userProvide',
27+
);
2328
return (
2429
<Close asChild key={`endpoint-${endpoint}`}>
2530
<div key={`endpoint-${endpoint}`}>

client/src/components/Chat/Menus/Presets/PresetItems.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import type { TPreset } from 'librechat-data-provider';
88
import FileUpload from '~/components/Input/EndpointMenu/FileUpload';
99
import { PinIcon, EditIcon, TrashIcon } from '~/components/svg';
1010
import DialogTemplate from '~/components/ui/DialogTemplate';
11+
import { getPresetTitle, getEndpointField } from '~/utils';
1112
import { Dialog, DialogTrigger } from '~/components/ui/';
1213
import { MenuSeparator, MenuItem } from '../UI';
1314
import { icons } from '../Endpoints/Icons';
14-
import { getPresetTitle } from '~/utils';
1515
import { useLocalize } from '~/hooks';
1616
import store from '~/store';
1717

@@ -95,7 +95,7 @@ const PresetItems: FC<{
9595
return null;
9696
}
9797

98-
const iconKey = endpointsConfig?.[preset.endpoint ?? '']?.type
98+
const iconKey = getEndpointField(endpointsConfig, preset.endpoint, 'type')
9999
? 'unknown'
100100
: preset.endpoint ?? 'unknown';
101101

@@ -111,7 +111,7 @@ const PresetItems: FC<{
111111
onClick={() => onSelectPreset(preset)}
112112
icon={icons[iconKey]({
113113
context: 'menu-item',
114-
iconURL: endpointsConfig?.[preset.endpoint ?? ''].iconURL,
114+
iconURL: getEndpointField(endpointsConfig, preset.endpoint, 'iconURL'),
115115
className: 'icon-md mr-1 dark:text-white',
116116
endpoint: preset.endpoint,
117117
})}

client/src/components/Conversations/Convo.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
useGetEndpointsQuery,
66
useUpdateConversationMutation,
77
} from 'librechat-data-provider/react-query';
8+
import { EModelEndpoint } from 'librechat-data-provider';
89
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
910
import { useConversations, useNavigateToConvo } from '~/hooks';
1011
import { MinimalIcon } from '~/components/Endpoints';
1112
import { NotificationSeverity } from '~/common';
1213
import { useToastContext } from '~/Providers';
1314
import DeleteButton from './NewDeleteButton';
15+
import { getEndpointField } from '~/utils';
1416
import RenameButton from './RenameButton';
1517
import store from '~/store';
1618

@@ -41,7 +43,7 @@ export default function Conversation({ conversation, retainView, toggleNav, i })
4143
document.title = title;
4244

4345
// set conversation to the new conversation
44-
if (conversation?.endpoint === 'gptPlugins') {
46+
if (conversation?.endpoint === EModelEndpoint.gptPlugins) {
4547
let lastSelectedTools = [];
4648
try {
4749
lastSelectedTools = JSON.parse(localStorage.getItem('lastSelectedTools') ?? '') ?? [];
@@ -90,7 +92,7 @@ export default function Conversation({ conversation, retainView, toggleNav, i })
9092

9193
const icon = MinimalIcon({
9294
size: 20,
93-
iconURL: endpointsConfig?.[conversation.endpoint ?? '']?.iconURL,
95+
iconURL: getEndpointField(endpointsConfig, conversation.endpoint, 'iconURL'),
9496
endpoint: conversation.endpoint,
9597
endpointType: conversation.endpointType,
9698
model: conversation.model,

client/src/components/Input/EndpointMenu/EndpointItem.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { alternateName } from 'librechat-data-provider';
44
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
55
import { DropdownMenuRadioItem } from '~/components';
66
import { SetKeyDialog } from '../SetKeyDialog';
7+
import { cn, getEndpointField } from '~/utils';
78
import { Icon } from '~/components/Endpoints';
89
import { useLocalize } from '~/hooks';
9-
import { cn } from '~/utils';
1010

1111
export default function ModelItem({
1212
endpoint,
@@ -29,7 +29,11 @@ export default function ModelItem({
2929
isCreatedByUser: false,
3030
});
3131

32-
const userProvidesKey = endpointsConfig?.[endpoint]?.userProvide;
32+
const userProvidesKey: boolean | null | undefined = getEndpointField(
33+
endpointsConfig,
34+
endpoint,
35+
'userProvide',
36+
);
3337
const localize = useLocalize();
3438

3539
// regular model

client/src/components/Input/TextChat.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import React, { useEffect, useContext, useRef, useState, useCallback } from 'react';
21
import TextareaAutosize from 'react-textarea-autosize';
32
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
4-
import SubmitButton from './SubmitButton';
3+
import React, { useEffect, useContext, useRef, useState, useCallback } from 'react';
54

6-
import OptionsBar from './OptionsBar';
75
import { EndpointMenu } from './EndpointMenu';
6+
import SubmitButton from './SubmitButton';
7+
import OptionsBar from './OptionsBar';
88
import Footer from './Footer';
9+
910
import { useMessageHandler, ThemeContext } from '~/hooks';
10-
import { cn } from '~/utils';
11+
import { cn, getEndpointField } from '~/utils';
1112
import store from '~/store';
1213

1314
interface TextChatProps {
@@ -195,7 +196,7 @@ export default function TextChat({ isSearchView = false }: TextChatProps) {
195196
isSubmitting={isSubmitting}
196197
userProvidesKey={
197198
conversation?.endpoint
198-
? endpointsConfig?.[conversation.endpoint]?.userProvide
199+
? getEndpointField(endpointsConfig, conversation.endpoint, 'userProvide')
199200
: undefined
200201
}
201202
hasText={hasText}

client/src/hooks/Conversations/useGetSender.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function useGetSender() {
77
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
88
return useCallback(
99
(endpointOption: TEndpointOption) => {
10-
const { modelDisplayLabel } = endpointsConfig[endpointOption.endpoint ?? ''] ?? {};
10+
const { modelDisplayLabel } = endpointsConfig?.[endpointOption.endpoint ?? ''] ?? {};
1111
return getResponseSender({ ...endpointOption, modelDisplayLabel });
1212
},
1313
[endpointsConfig],

client/src/hooks/Conversations/usePresets.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {
1313
} from '~/data-provider';
1414
import { useChatContext, useToastContext } from '~/Providers';
1515
import useNavigateToConvo from '~/hooks/useNavigateToConvo';
16+
import { cleanupPreset, getEndpointField } from '~/utils';
1617
import useDefaultConvo from '~/hooks/useDefaultConvo';
1718
import { useAuthContext } from '~/hooks/AuthContext';
1819
import { NotificationSeverity } from '~/common';
1920
import useLocalize from '~/hooks/useLocalize';
20-
import { cleanupPreset } from '~/utils';
2121
import store from '~/store';
2222

2323
export default function usePresets() {
@@ -162,12 +162,13 @@ export default function usePresets() {
162162

163163
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
164164

165-
const currentEndpointType = endpointsConfig?.[endpoint ?? '']?.type ?? '';
166-
const endpointType = endpointsConfig?.[newPreset?.endpoint ?? '']?.type;
165+
const currentEndpointType = getEndpointField(endpointsConfig, endpoint, 'type');
166+
const endpointType = getEndpointField(endpointsConfig, newPreset.endpoint, 'type');
167167

168168
if (
169-
(modularEndpoints.has(endpoint ?? '') || modularEndpoints.has(currentEndpointType)) &&
170-
(modularEndpoints.has(newPreset?.endpoint ?? '') || modularEndpoints.has(endpointType)) &&
169+
(modularEndpoints.has(endpoint ?? '') || modularEndpoints.has(currentEndpointType ?? '')) &&
170+
(modularEndpoints.has(newPreset?.endpoint ?? '') ||
171+
modularEndpoints.has(endpointType ?? '')) &&
171172
(endpoint === newPreset?.endpoint || modularChat)
172173
) {
173174
const currentConvo = getDefaultConversation({

0 commit comments

Comments
 (0)