Skip to content

Commit

Permalink
🐛 fix: Resolve 'Icon is Not a Function' Error in PresetItems (#5260)
Browse files Browse the repository at this point in the history
* refactor: improve typing

* fix: "TypeError: Icon is not a function" with proper use of Functional Component and Improved Typing
  • Loading branch information
danny-avila authored Jan 11, 2025
1 parent 0855677 commit 24beda3
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 49 deletions.
9 changes: 8 additions & 1 deletion client/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,14 @@ export type IconMapProps = {
size?: number;
};

export type AgentIconMapProps = IconMapProps & { agentName: string };
export type IconComponent = React.ComponentType<IconMapProps>;
export type AgentIconComponent = React.ComponentType<AgentIconMapProps>;
export type IconComponentTypes = IconComponent | AgentIconComponent;
export type IconsRecord = {
[key in t.EModelEndpoint | 'unknown' | string]: IconComponentTypes | null | undefined;
};

export type AgentIconMapProps = IconMapProps & { agentName?: string };

export type NavLink = {
title: string;
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Chat/Menus/Endpoints/Icons.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EModelEndpoint } from 'librechat-data-provider';
import type { IconMapProps, AgentIconMapProps } from '~/common';
import type { IconMapProps, AgentIconMapProps, IconsRecord } from '~/common';
import { Feather } from 'lucide-react';
import {
MinimalPlugin,
Expand Down Expand Up @@ -42,7 +42,7 @@ const AssistantAvatar = ({
};

const AgentAvatar = ({ className = '', avatar = '', agentName, size }: AgentIconMapProps) => {
if (agentName && avatar) {
if (agentName != null && agentName && avatar) {
return (
<img
src={avatar}
Expand All @@ -61,7 +61,7 @@ const Bedrock = ({ className = '' }: IconMapProps) => {
return <BedrockIcon className={cn(className, 'h-full w-full')} />;
};

export const icons = {
export const icons: IconsRecord = {
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
[EModelEndpoint.openAI]: GPTIcon,
[EModelEndpoint.gptPlugins]: MinimalPlugin,
Expand Down
41 changes: 24 additions & 17 deletions client/src/components/Chat/Menus/Presets/PresetItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { cn } from '~/utils';
import store from '~/store';

const PresetItems: FC<{
presets: TPreset[];
presets?: Array<TPreset | undefined>;
onSetDefaultPreset: (preset: TPreset, remove?: boolean) => void;
onSelectPreset: (preset: TPreset) => void;
onChangePreset: (preset: TPreset) => void;
Expand Down Expand Up @@ -110,34 +110,41 @@ const PresetItems: FC<{
</div>
</div>
)}
<Flipper flipKey={presets.map(({ presetId }) => presetId).join('.')}>
<Flipper
flipKey={presets
?.map((preset) => preset?.presetId)
.filter((p) => p)
.join('.')}
>
{presets &&
presets.length > 0 &&
presets.map((preset, i) => {
if (!preset || !preset.presetId) {
const presetId = preset?.presetId ?? '';
if (!preset || !presetId) {
return null;
}

const iconKey = getIconKey({ endpoint: preset.endpoint, endpointsConfig });
const Icon = icons[iconKey];

return (
<Close asChild key={`preset-${preset.presetId}`}>
<div key={`preset-${preset.presetId}`}>
<Flipped flipId={preset.presetId}>
<Close asChild key={`preset-${presetId}`}>
<div key={`preset-${presetId}`}>
<Flipped flipId={presetId}>
<MenuItem
key={`preset-item-${preset.presetId}`}
key={`preset-item-${presetId}`}
textClassName="text-xs max-w-[150px] sm:max-w-[200px] truncate md:max-w-full "
title={getPresetTitle(preset)}
onClick={() => onSelectPreset(preset)}
icon={
Icon &&
Icon({
context: 'menu-item',
iconURL: getEndpointField(endpointsConfig, preset.endpoint, 'iconURL'),
className: 'icon-md mr-1 dark:text-white',
endpoint: preset.endpoint,
})
Icon != null && (
<Icon
context="menu-item"
iconURL={getEndpointField(endpointsConfig, preset.endpoint, 'iconURL')}
className="icon-md mr-1 dark:text-white"
endpoint={preset.endpoint}
/>
)
}
selected={false}
data-testid={`preset-item-${preset}`}
Expand All @@ -146,17 +153,17 @@ const PresetItems: FC<{
<button
className={cn(
'm-0 h-full rounded-md bg-transparent p-2 text-gray-400 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200',
defaultPreset?.presetId === preset.presetId
defaultPreset?.presetId === presetId
? ''
: 'sm:invisible sm:group-hover:visible',
)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSetDefaultPreset(preset, defaultPreset?.presetId === preset.presetId);
onSetDefaultPreset(preset, defaultPreset?.presetId === presetId);
}}
>
<PinIcon unpin={defaultPreset?.presetId === preset.presetId} />
<PinIcon unpin={defaultPreset?.presetId === presetId} />
</button>
<button
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
Expand Down
4 changes: 1 addition & 3 deletions client/src/components/Chat/Menus/PresetsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ const PresetsMenu: FC = () => {
exportPreset,
} = usePresets();
const { preset } = useChatContext();

const presets = presetsQuery.data || [];
return (
<Root>
<Trigger asChild>
Expand Down Expand Up @@ -54,7 +52,7 @@ const PresetsMenu: FC = () => {
className="mt-2 max-h-[495px] overflow-x-hidden rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white md:min-w-[400px]"
>
<PresetItems
presets={presets}
presets={presetsQuery.data}
onSetDefaultPreset={onSetDefaultPreset}
onSelectPreset={onSelectPreset}
onChangePreset={onChangePreset}
Expand Down
4 changes: 2 additions & 2 deletions client/src/utils/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
isAssistantsEndpoint,
} from 'librechat-data-provider';
import type * as t from 'librechat-data-provider';
import type { LocalizeFunction } from '~/common';
import type { LocalizeFunction, IconsRecord } from '~/common';

export const getEntityName = ({
name = '',
Expand Down Expand Up @@ -222,7 +222,7 @@ export function getIconKey({
endpointsConfig?: t.TEndpointsConfig;
endpointType?: string | null;
endpointIconURL?: string;
}) {
}): keyof IconsRecord {
const endpointType = _eType ?? getEndpointField(endpointsConfig, endpoint, 'type') ?? '';
const endpointIconURL = iconURL ?? getEndpointField(endpointsConfig, endpoint, 'iconURL') ?? '';
if (endpointIconURL && EModelEndpoint[endpointIconURL] != null) {
Expand Down
42 changes: 19 additions & 23 deletions client/src/utils/presets.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import type { TPreset, TPlugin } from 'librechat-data-provider';
import { EModelEndpoint } from 'librechat-data-provider';

export const getPresetIcon = (preset: TPreset, Icon) => {
return Icon({
size: 20,
endpoint: preset?.endpoint,
model: preset?.model,
error: false,
className: 'icon-md',
isCreatedByUser: false,
});
};

type TEndpoints = Array<string | EModelEndpoint>;

export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
Expand All @@ -27,7 +16,7 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
toneStyle,
} = preset;
let title = '';
let modelInfo = model || '';
let modelInfo = model ?? '';
let label = '';

const usesChatGPTLabel: TEndpoints = [
Expand All @@ -37,24 +26,31 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
];
const usesModelLabel: TEndpoints = [EModelEndpoint.google, EModelEndpoint.anthropic];

if (endpoint && usesChatGPTLabel.includes(endpoint)) {
label = chatGptLabel || '';
} else if (endpoint && usesModelLabel.includes(endpoint)) {
label = modelLabel || '';
if (endpoint != null && endpoint && usesChatGPTLabel.includes(endpoint)) {
label = chatGptLabel ?? '';
} else if (endpoint != null && endpoint && usesModelLabel.includes(endpoint)) {
label = modelLabel ?? '';
} else if (endpoint === EModelEndpoint.bingAI) {
modelInfo = jailbreak ? 'Sydney' : modelInfo;
label = toneStyle ? `: ${toneStyle}` : '';
modelInfo = jailbreak === true ? 'Sydney' : modelInfo;
label = toneStyle != null && toneStyle ? `: ${toneStyle}` : '';
}

if (label && presetTitle && label.toLowerCase().includes(presetTitle.toLowerCase())) {
if (
label &&
presetTitle != null &&
presetTitle &&
label.toLowerCase().includes(presetTitle.toLowerCase())
) {
title = label + ': ';
label = '';
} else if (presetTitle && presetTitle.trim() !== 'New Chat') {
} else if (presetTitle != null && presetTitle && presetTitle.trim() !== 'New Chat') {
title = presetTitle + ': ';
}

if (mention) {
return `${modelInfo}${label ? ` | ${label}` : ''}${promptPrefix ? ` | ${promptPrefix}` : ''}${
if (mention === true) {
return `${modelInfo}${label ? ` | ${label}` : ''}${
promptPrefix != null && promptPrefix ? ` | ${promptPrefix}` : ''
}${
tools
? ` | ${tools
.map((tool: TPlugin | string) => {
Expand All @@ -74,7 +70,7 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
/** Remove unavailable tools from the preset */
export const removeUnavailableTools = (
preset: TPreset,
availableTools: Record<string, TPlugin>,
availableTools: Record<string, TPlugin | undefined>,
) => {
const newPreset = { ...preset };

Expand Down

0 comments on commit 24beda3

Please sign in to comment.