🛡️ fix: Harden Model Spec Icon Rendering (#13356)

* fix: Harden model spec icon rendering

* docs: Clarify model spec icon fallback
This commit is contained in:
Danny Avila 2026-05-27 22:01:58 -07:00 committed by GitHub
parent f95fa55cce
commit 3544cd972a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 6 deletions

View file

@ -15,7 +15,7 @@ type IconType = (props: IconMapProps) => React.JSX.Element;
const SpecIcon: React.FC<SpecIconProps> = ({ currentSpec, endpointsConfig }) => {
const iconURL = getModelSpecIconURL(currentSpec);
const { endpoint } = currentSpec.preset;
const endpoint = currentSpec.preset?.endpoint;
const endpointIconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL');
const iconKey = getIconKey({ endpoint, endpointsConfig, endpointIconURL });
let Icon: IconType;

View file

@ -0,0 +1,68 @@
import { render, screen } from '@testing-library/react';
import { EModelEndpoint } from 'librechat-data-provider';
import type { TModelSpec, TEndpointsConfig } from 'librechat-data-provider';
import SpecIcon from '../SpecIcon';
jest.mock('~/hooks/Endpoint/Icons', () => {
const React = jest.requireActual<typeof import('react')>('react');
const createIcon =
(iconKey: string) =>
({ endpoint, iconURL }: { endpoint?: string | null; iconURL?: string }) =>
React.createElement('span', {
'data-testid': 'endpoint-icon',
'data-icon-key': iconKey,
'data-endpoint': endpoint ?? '',
'data-icon-url': iconURL ?? '',
});
return {
icons: {
google: createIcon('google'),
openAI: createIcon('openAI'),
unknown: createIcon('unknown'),
},
};
});
jest.mock('~/components/Endpoints/URLIcon', () => {
const React = jest.requireActual<typeof import('react')>('react');
return {
URLIcon: ({ iconURL, endpoint }: { iconURL: string; endpoint?: string }) =>
React.createElement('span', {
'data-testid': 'url-icon',
'data-icon-url': iconURL,
'data-endpoint': endpoint ?? '',
}),
};
});
describe('SpecIcon', () => {
const endpointsConfig = {} as TEndpointsConfig;
it('renders the explicit spec icon when runtime spec data is missing preset', () => {
const currentSpec = {
name: 'gemini-test',
label: 'Gemini Test',
iconURL: EModelEndpoint.google,
} as TModelSpec;
render(<SpecIcon currentSpec={currentSpec} endpointsConfig={endpointsConfig} />);
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute(
'data-icon-key',
EModelEndpoint.google,
);
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-endpoint', '');
});
it('falls back to the unknown icon when runtime spec data has no icon or preset', () => {
const currentSpec = {
name: 'gemini-test',
label: 'Gemini Test',
} as TModelSpec;
render(<SpecIcon currentSpec={currentSpec} endpointsConfig={endpointsConfig} />);
expect(screen.getByTestId('endpoint-icon')).toHaveAttribute('data-icon-key', 'unknown');
});
});

View file

@ -0,0 +1,53 @@
import { EModelEndpoint } from 'librechat-data-provider';
import type { TModelSpec } from 'librechat-data-provider';
import { getModelSpecIconURL } from '../endpoints';
describe('getModelSpecIconURL', () => {
it('returns the explicit model spec icon before preset values', () => {
const modelSpec = {
name: 'gemini-test',
label: 'Gemini Test',
iconURL: EModelEndpoint.google,
preset: {
endpoint: EModelEndpoint.openAI,
iconURL: EModelEndpoint.anthropic,
},
} as TModelSpec;
expect(getModelSpecIconURL(modelSpec)).toBe(EModelEndpoint.google);
});
it('falls back to the preset icon URL when no spec icon is defined', () => {
const modelSpec = {
name: 'gemini-test',
label: 'Gemini Test',
preset: {
endpoint: EModelEndpoint.google,
iconURL: EModelEndpoint.openAI,
},
} as TModelSpec;
expect(getModelSpecIconURL(modelSpec)).toBe(EModelEndpoint.openAI);
});
it('falls back to the preset endpoint when no icon URL is defined', () => {
const modelSpec = {
name: 'gemini-test',
label: 'Gemini Test',
preset: {
endpoint: EModelEndpoint.google,
},
} as TModelSpec;
expect(getModelSpecIconURL(modelSpec)).toBe(EModelEndpoint.google);
});
it('returns an empty icon when a runtime model spec is missing preset data', () => {
const modelSpec = {
name: 'gemini-test',
label: 'Gemini Test',
} as TModelSpec;
expect(getModelSpecIconURL(modelSpec)).toBe('');
});
});

View file

@ -335,12 +335,9 @@ export function mergeQuerySettingsWithSpec(
};
}
/** Gets the default spec iconURL by order or definition.
*
* First, the admin defined default, then last selected spec, followed by first spec
*/
/** Gets the model spec iconURL by explicit icon, preset icon, then preset endpoint. */
export function getModelSpecIconURL(modelSpec: t.TModelSpec) {
return modelSpec.iconURL ?? modelSpec.preset.iconURL ?? modelSpec.preset.endpoint ?? '';
return modelSpec.iconURL ?? modelSpec.preset?.iconURL ?? modelSpec.preset?.endpoint ?? '';
}
/** Gets the default frontend-facing endpoint, dependent on iconURL definition.