From 4b3fd63803741350ede47f94a88ce69484e61a59 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 5 Jun 2026 20:54:57 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=96=BC=EF=B8=8F=20fix:=20Support=20Known?= =?UTF-8?q?=20Endpoint=20Group=20Icons=20(#13542)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Support known endpoint group icons * fix: Resolve known endpoint group icon edge cases --- .../Menus/Endpoints/components/GroupIcon.tsx | 17 ++++- .../components/__tests__/GroupIcon.test.tsx | 64 +++++++++++++++++++ client/src/hooks/Endpoint/UnknownIcon.tsx | 33 ++++++++-- 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 client/src/components/Chat/Menus/Endpoints/components/__tests__/GroupIcon.test.tsx diff --git a/client/src/components/Chat/Menus/Endpoints/components/GroupIcon.tsx b/client/src/components/Chat/Menus/Endpoints/components/GroupIcon.tsx index eb1081435d..3c4cb840a4 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/GroupIcon.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/GroupIcon.tsx @@ -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 = ({ iconURL, groupName }) => { ); } + const resolvedIconURL = getKnownEndpointAsset(iconURL); + + if (!resolvedIconURL && hasKnownEndpointIcon(iconURL)) { + const Icon: IconType = icons.unknown as IconType; + return ( + + ); + } + return (
{groupName} { + const React = jest.requireActual('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(); + + expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-icon-key', 'openAI'); + }); + + it('resolves known endpoint asset aliases case-insensitively', () => { + render(); + + expect(screen.getByRole('img', { name: 'OpenRouter' })).toHaveAttribute( + 'src', + 'assets/openrouter.png', + ); + }); + + it('resolves known endpoint asset aliases to shipped file paths', () => { + render(); + + expect(screen.getByRole('img', { name: 'Helicone' })).toHaveAttribute( + 'src', + 'assets/helicone.svg', + ); + }); + + it('renders known endpoint aliases backed by components', () => { + render(); + + 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(); + + expect(screen.getByRole('img', { name: 'OpenRouter' })).toHaveAttribute( + 'src', + '/assets/openrouter.png', + ); + }); +}); diff --git a/client/src/hooks/Endpoint/UnknownIcon.tsx b/client/src/hooks/Endpoint/UnknownIcon.tsx index be7531a34a..e7b245a797 100644 --- a/client/src/hooks/Endpoint/UnknownIcon.tsx +++ b/client/src/hooks/Endpoint/UnknownIcon.tsx @@ -4,28 +4,49 @@ import { CustomMinimalIcon, XAIcon, MoonshotIcon } from '@librechat/client'; import { IconContext } from '~/common'; import { cn } from '~/utils'; -const knownEndpointAssets = { +const knownEndpointAssets: Record = { [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([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 {`${endpoint}; } - const assetPath: string = knownEndpointAssets[currentEndpoint] ?? ''; + const assetPath = getKnownEndpointAsset(currentEndpoint); if (!assetPath) { return ;