mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 06:52:47 +00:00
🖼️ fix: Support Known Endpoint Group Icons (#13542)
* fix: Support known endpoint group icons * fix: Resolve known endpoint group icon edge cases
This commit is contained in:
parent
c374d08b64
commit
4b3fd63803
3 changed files with 107 additions and 7 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import React, { memo, useState } from 'react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import type { IconMapProps } from '~/common';
|
||||
import { getKnownEndpointAsset, hasKnownEndpointIcon } from '~/hooks/Endpoint/UnknownIcon';
|
||||
import { icons } from '~/hooks/Endpoint/Icons';
|
||||
|
||||
interface GroupIconProps {
|
||||
|
|
@ -42,13 +43,27 @@ const GroupIcon: React.FC<GroupIconProps> = ({ iconURL, groupName }) => {
|
|||
);
|
||||
}
|
||||
|
||||
const resolvedIconURL = getKnownEndpointAsset(iconURL);
|
||||
|
||||
if (!resolvedIconURL && hasKnownEndpointIcon(iconURL)) {
|
||||
const Icon: IconType = icons.unknown as IconType;
|
||||
return (
|
||||
<Icon
|
||||
size={20}
|
||||
endpoint={iconURL}
|
||||
context="menu-item"
|
||||
className="icon-md shrink-0 text-text-primary"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="icon-md shrink-0 overflow-hidden rounded-full"
|
||||
style={{ width: 20, height: 20 }}
|
||||
>
|
||||
<img
|
||||
src={iconURL}
|
||||
src={resolvedIconURL || iconURL}
|
||||
alt={groupName}
|
||||
className="h-full w-full object-cover"
|
||||
onError={handleImageError}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import GroupIcon from '../GroupIcon';
|
||||
|
||||
jest.mock('~/hooks/Endpoint/Icons', () => {
|
||||
const React = jest.requireActual<typeof import('react')>('react');
|
||||
const createIcon =
|
||||
(iconKey: string) =>
|
||||
({ className, endpoint }: { className?: string; endpoint?: string | null }) =>
|
||||
React.createElement('span', {
|
||||
className,
|
||||
'data-testid': 'endpoint-icon',
|
||||
'data-icon-key': iconKey,
|
||||
'data-endpoint': endpoint ?? '',
|
||||
});
|
||||
|
||||
return {
|
||||
icons: {
|
||||
openAI: createIcon('openAI'),
|
||||
unknown: createIcon('unknown'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('GroupIcon', () => {
|
||||
it('renders built-in endpoint icon keys', () => {
|
||||
render(<GroupIcon iconURL="openAI" groupName="OpenAI" />);
|
||||
|
||||
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-icon-key', 'openAI');
|
||||
});
|
||||
|
||||
it('resolves known endpoint asset aliases case-insensitively', () => {
|
||||
render(<GroupIcon iconURL="OpenRouter" groupName="OpenRouter" />);
|
||||
|
||||
expect(screen.getByRole('img', { name: 'OpenRouter' })).toHaveAttribute(
|
||||
'src',
|
||||
'assets/openrouter.png',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves known endpoint asset aliases to shipped file paths', () => {
|
||||
render(<GroupIcon iconURL="Helicone" groupName="Helicone" />);
|
||||
|
||||
expect(screen.getByRole('img', { name: 'Helicone' })).toHaveAttribute(
|
||||
'src',
|
||||
'assets/helicone.svg',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders known endpoint aliases backed by components', () => {
|
||||
render(<GroupIcon iconURL="Moonshot" groupName="Moonshot" />);
|
||||
|
||||
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-icon-key', 'unknown');
|
||||
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-endpoint', 'Moonshot');
|
||||
});
|
||||
|
||||
it('renders configured image URLs directly', () => {
|
||||
render(<GroupIcon iconURL="/assets/openrouter.png" groupName="OpenRouter" />);
|
||||
|
||||
expect(screen.getByRole('img', { name: 'OpenRouter' })).toHaveAttribute(
|
||||
'src',
|
||||
'/assets/openrouter.png',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -4,28 +4,49 @@ import { CustomMinimalIcon, XAIcon, MoonshotIcon } from '@librechat/client';
|
|||
import { IconContext } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const knownEndpointAssets = {
|
||||
const knownEndpointAssets: Record<string, string> = {
|
||||
[KnownEndpoints.anyscale]: 'assets/anyscale.png',
|
||||
[KnownEndpoints.apipie]: 'assets/apipie.png',
|
||||
[KnownEndpoints.cohere]: 'assets/cohere.png',
|
||||
[KnownEndpoints.deepseek]: 'assets/deepseek.svg',
|
||||
[KnownEndpoints.fireworks]: 'assets/fireworks.png',
|
||||
[KnownEndpoints.google]: 'assets/google.svg',
|
||||
google: 'assets/google.svg',
|
||||
[KnownEndpoints.groq]: 'assets/groq.png',
|
||||
[KnownEndpoints.helicone]: 'assets/helicone.png',
|
||||
[KnownEndpoints.helicone]: 'assets/helicone.svg',
|
||||
[KnownEndpoints.huggingface]: 'assets/huggingface.svg',
|
||||
[KnownEndpoints.mistral]: 'assets/mistral.png',
|
||||
[KnownEndpoints.mlx]: 'assets/mlx.png',
|
||||
[KnownEndpoints.ollama]: 'assets/ollama.png',
|
||||
[KnownEndpoints.openai]: 'assets/openai.svg',
|
||||
openai: 'assets/openai.svg',
|
||||
[KnownEndpoints.openrouter]: 'assets/openrouter.png',
|
||||
[KnownEndpoints.perplexity]: 'assets/perplexity.png',
|
||||
[KnownEndpoints.qwen]: 'assets/qwen.svg',
|
||||
qwen: 'assets/qwen.svg',
|
||||
[KnownEndpoints.shuttleai]: 'assets/shuttleai.png',
|
||||
[KnownEndpoints['together.ai']]: 'assets/together.png',
|
||||
[KnownEndpoints.unify]: 'assets/unify.webp',
|
||||
};
|
||||
|
||||
const knownEndpointComponents = new Set<string>([KnownEndpoints.moonshot, KnownEndpoints.xai]);
|
||||
|
||||
export function getKnownEndpointAsset(endpoint?: string | null): string {
|
||||
if (!endpoint) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return knownEndpointAssets[endpoint.toLowerCase()] ?? '';
|
||||
}
|
||||
|
||||
export function hasKnownEndpointIcon(endpoint?: string | null): boolean {
|
||||
if (!endpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentEndpoint = endpoint.toLowerCase();
|
||||
return (
|
||||
getKnownEndpointAsset(currentEndpoint) !== '' || knownEndpointComponents.has(currentEndpoint)
|
||||
);
|
||||
}
|
||||
|
||||
const knownEndpointClasses = {
|
||||
[KnownEndpoints.cohere]: {
|
||||
[IconContext.landing]: 'p-2',
|
||||
|
|
@ -81,7 +102,7 @@ function UnknownIcon({
|
|||
return <img className={className} src={iconURL} alt={`${endpoint} Icon`} />;
|
||||
}
|
||||
|
||||
const assetPath: string = knownEndpointAssets[currentEndpoint] ?? '';
|
||||
const assetPath = getKnownEndpointAsset(currentEndpoint);
|
||||
|
||||
if (!assetPath) {
|
||||
return <CustomMinimalIcon className={className} />;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue