mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-25 17:06:24 +00:00
🛡️ fix: Harden Model Spec Icon Rendering (#13356)
* fix: Harden model spec icon rendering * docs: Clarify model spec icon fallback
This commit is contained in:
parent
f95fa55cce
commit
3544cd972a
4 changed files with 124 additions and 6 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
53
client/src/utils/__tests__/getModelSpecIconURL.test.ts
Normal file
53
client/src/utils/__tests__/getModelSpecIconURL.test.ts
Normal 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('');
|
||||
});
|
||||
});
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue