🖼️ 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:
Danny Avila 2026-06-05 20:54:57 -04:00 committed by GitHub
parent c374d08b64
commit 4b3fd63803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 107 additions and 7 deletions

View file

@ -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}

View file

@ -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',
);
});
});

View file

@ -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} />;