mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20: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;
|
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 = {
|
export type NavLink = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
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 { Feather } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
MinimalPlugin,
|
MinimalPlugin,
|
||||||
|
|
@ -42,7 +42,7 @@ const AssistantAvatar = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const AgentAvatar = ({ className = '', avatar = '', agentName, size }: AgentIconMapProps) => {
|
const AgentAvatar = ({ className = '', avatar = '', agentName, size }: AgentIconMapProps) => {
|
||||||
if (agentName && avatar) {
|
if (agentName != null && agentName && avatar) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={avatar}
|
src={avatar}
|
||||||
|
|
@ -61,7 +61,7 @@ const Bedrock = ({ className = '' }: IconMapProps) => {
|
||||||
return <BedrockIcon className={cn(className, 'h-full w-full')} />;
|
return <BedrockIcon className={cn(className, 'h-full w-full')} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const icons = {
|
export const icons: IconsRecord = {
|
||||||
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
|
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
|
||||||
[EModelEndpoint.openAI]: GPTIcon,
|
[EModelEndpoint.openAI]: GPTIcon,
|
||||||
[EModelEndpoint.gptPlugins]: MinimalPlugin,
|
[EModelEndpoint.gptPlugins]: MinimalPlugin,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { cn } from '~/utils';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
const PresetItems: FC<{
|
const PresetItems: FC<{
|
||||||
presets: TPreset[];
|
presets?: Array<TPreset | undefined>;
|
||||||
onSetDefaultPreset: (preset: TPreset, remove?: boolean) => void;
|
onSetDefaultPreset: (preset: TPreset, remove?: boolean) => void;
|
||||||
onSelectPreset: (preset: TPreset) => void;
|
onSelectPreset: (preset: TPreset) => void;
|
||||||
onChangePreset: (preset: TPreset) => void;
|
onChangePreset: (preset: TPreset) => void;
|
||||||
|
|
@ -110,11 +110,17 @@ const PresetItems: FC<{
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Flipper flipKey={presets.map(({ presetId }) => presetId).join('.')}>
|
<Flipper
|
||||||
|
flipKey={presets
|
||||||
|
?.map((preset) => preset?.presetId)
|
||||||
|
.filter((p) => p)
|
||||||
|
.join('.')}
|
||||||
|
>
|
||||||
{presets &&
|
{presets &&
|
||||||
presets.length > 0 &&
|
presets.length > 0 &&
|
||||||
presets.map((preset, i) => {
|
presets.map((preset, i) => {
|
||||||
if (!preset || !preset.presetId) {
|
const presetId = preset?.presetId ?? '';
|
||||||
|
if (!preset || !presetId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,22 +128,23 @@ const PresetItems: FC<{
|
||||||
const Icon = icons[iconKey];
|
const Icon = icons[iconKey];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Close asChild key={`preset-${preset.presetId}`}>
|
<Close asChild key={`preset-${presetId}`}>
|
||||||
<div key={`preset-${preset.presetId}`}>
|
<div key={`preset-${presetId}`}>
|
||||||
<Flipped flipId={preset.presetId}>
|
<Flipped flipId={presetId}>
|
||||||
<MenuItem
|
<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 "
|
textClassName="text-xs max-w-[150px] sm:max-w-[200px] truncate md:max-w-full "
|
||||||
title={getPresetTitle(preset)}
|
title={getPresetTitle(preset)}
|
||||||
onClick={() => onSelectPreset(preset)}
|
onClick={() => onSelectPreset(preset)}
|
||||||
icon={
|
icon={
|
||||||
Icon &&
|
Icon != null && (
|
||||||
Icon({
|
<Icon
|
||||||
context: 'menu-item',
|
context="menu-item"
|
||||||
iconURL: getEndpointField(endpointsConfig, preset.endpoint, 'iconURL'),
|
iconURL={getEndpointField(endpointsConfig, preset.endpoint, 'iconURL')}
|
||||||
className: 'icon-md mr-1 dark:text-white',
|
className="icon-md mr-1 dark:text-white"
|
||||||
endpoint: preset.endpoint,
|
endpoint={preset.endpoint}
|
||||||
})
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
selected={false}
|
selected={false}
|
||||||
data-testid={`preset-item-${preset}`}
|
data-testid={`preset-item-${preset}`}
|
||||||
|
|
@ -146,17 +153,17 @@ const PresetItems: FC<{
|
||||||
<button
|
<button
|
||||||
className={cn(
|
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',
|
'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',
|
: 'sm:invisible sm:group-hover:visible',
|
||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
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>
|
||||||
<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"
|
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,
|
exportPreset,
|
||||||
} = usePresets();
|
} = usePresets();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
|
|
||||||
const presets = presetsQuery.data || [];
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Trigger asChild>
|
<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]"
|
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
|
<PresetItems
|
||||||
presets={presets}
|
presets={presetsQuery.data}
|
||||||
onSetDefaultPreset={onSetDefaultPreset}
|
onSetDefaultPreset={onSetDefaultPreset}
|
||||||
onSelectPreset={onSelectPreset}
|
onSelectPreset={onSelectPreset}
|
||||||
onChangePreset={onChangePreset}
|
onChangePreset={onChangePreset}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
isAssistantsEndpoint,
|
isAssistantsEndpoint,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type * as t 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 = ({
|
export const getEntityName = ({
|
||||||
name = '',
|
name = '',
|
||||||
|
|
@ -222,7 +222,7 @@ export function getIconKey({
|
||||||
endpointsConfig?: t.TEndpointsConfig;
|
endpointsConfig?: t.TEndpointsConfig;
|
||||||
endpointType?: string | null;
|
endpointType?: string | null;
|
||||||
endpointIconURL?: string;
|
endpointIconURL?: string;
|
||||||
}) {
|
}): keyof IconsRecord {
|
||||||
const endpointType = _eType ?? getEndpointField(endpointsConfig, endpoint, 'type') ?? '';
|
const endpointType = _eType ?? getEndpointField(endpointsConfig, endpoint, 'type') ?? '';
|
||||||
const endpointIconURL = iconURL ?? getEndpointField(endpointsConfig, endpoint, 'iconURL') ?? '';
|
const endpointIconURL = iconURL ?? getEndpointField(endpointsConfig, endpoint, 'iconURL') ?? '';
|
||||||
if (endpointIconURL && EModelEndpoint[endpointIconURL] != null) {
|
if (endpointIconURL && EModelEndpoint[endpointIconURL] != null) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,6 @@
|
||||||
import type { TPreset, TPlugin } from 'librechat-data-provider';
|
import type { TPreset, TPlugin } from 'librechat-data-provider';
|
||||||
import { EModelEndpoint } 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>;
|
type TEndpoints = Array<string | EModelEndpoint>;
|
||||||
|
|
||||||
export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
|
export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
|
||||||
|
|
@ -27,7 +16,7 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
|
||||||
toneStyle,
|
toneStyle,
|
||||||
} = preset;
|
} = preset;
|
||||||
let title = '';
|
let title = '';
|
||||||
let modelInfo = model || '';
|
let modelInfo = model ?? '';
|
||||||
let label = '';
|
let label = '';
|
||||||
|
|
||||||
const usesChatGPTLabel: TEndpoints = [
|
const usesChatGPTLabel: TEndpoints = [
|
||||||
|
|
@ -37,24 +26,31 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
|
||||||
];
|
];
|
||||||
const usesModelLabel: TEndpoints = [EModelEndpoint.google, EModelEndpoint.anthropic];
|
const usesModelLabel: TEndpoints = [EModelEndpoint.google, EModelEndpoint.anthropic];
|
||||||
|
|
||||||
if (endpoint && usesChatGPTLabel.includes(endpoint)) {
|
if (endpoint != null && endpoint && usesChatGPTLabel.includes(endpoint)) {
|
||||||
label = chatGptLabel || '';
|
label = chatGptLabel ?? '';
|
||||||
} else if (endpoint && usesModelLabel.includes(endpoint)) {
|
} else if (endpoint != null && endpoint && usesModelLabel.includes(endpoint)) {
|
||||||
label = modelLabel || '';
|
label = modelLabel ?? '';
|
||||||
} else if (endpoint === EModelEndpoint.bingAI) {
|
} else if (endpoint === EModelEndpoint.bingAI) {
|
||||||
modelInfo = jailbreak ? 'Sydney' : modelInfo;
|
modelInfo = jailbreak === true ? 'Sydney' : modelInfo;
|
||||||
label = toneStyle ? `: ${toneStyle}` : '';
|
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 + ': ';
|
title = label + ': ';
|
||||||
label = '';
|
label = '';
|
||||||
} else if (presetTitle && presetTitle.trim() !== 'New Chat') {
|
} else if (presetTitle != null && presetTitle && presetTitle.trim() !== 'New Chat') {
|
||||||
title = presetTitle + ': ';
|
title = presetTitle + ': ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mention) {
|
if (mention === true) {
|
||||||
return `${modelInfo}${label ? ` | ${label}` : ''}${promptPrefix ? ` | ${promptPrefix}` : ''}${
|
return `${modelInfo}${label ? ` | ${label}` : ''}${
|
||||||
|
promptPrefix != null && promptPrefix ? ` | ${promptPrefix}` : ''
|
||||||
|
}${
|
||||||
tools
|
tools
|
||||||
? ` | ${tools
|
? ` | ${tools
|
||||||
.map((tool: TPlugin | string) => {
|
.map((tool: TPlugin | string) => {
|
||||||
|
|
@ -74,7 +70,7 @@ export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
|
||||||
/** Remove unavailable tools from the preset */
|
/** Remove unavailable tools from the preset */
|
||||||
export const removeUnavailableTools = (
|
export const removeUnavailableTools = (
|
||||||
preset: TPreset,
|
preset: TPreset,
|
||||||
availableTools: Record<string, TPlugin>,
|
availableTools: Record<string, TPlugin | undefined>,
|
||||||
) => {
|
) => {
|
||||||
const newPreset = { ...preset };
|
const newPreset = { ...preset };
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue