mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🐛 fix: Resolve 'Icon is Not a Function' Error in PresetItems (#5260)
* refactor: improve typing * fix: "TypeError: Icon is not a function" with proper use of Functional Component and Improved Typing
This commit is contained in:
parent
0855677a36
commit
24beda3d69
6 changed files with 57 additions and 49 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -110,11 +110,17 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -122,22 +128,23 @@ const PresetItems: FC<{
|
|||
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}`}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ const PresetsMenu: FC = () => {
|
|||
exportPreset,
|
||||
} = usePresets();
|
||||
const { preset } = useChatContext();
|
||||
|
||||
const presets = presetsQuery.data || [];
|
||||
return (
|
||||
<Root>
|
||||
<Trigger asChild>
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 = '',
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
@ -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 = [
|
||||
|
|
@ -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) => {
|
||||
|
|
@ -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 };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue