Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type ModelSelectorContextType = {
agentsMap: t.TAgentsMap | undefined;
assistantsMap: t.TAssistantsMap | undefined;
endpointsConfig: t.TEndpointsConfig;
hideBaseModels: boolean;

// Functions
endpointRequiresUserKey: (endpoint: string) => boolean;
Expand Down Expand Up @@ -209,6 +210,8 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
});
};

const hideBaseModels = startupConfig?.modelSpecs?.hideBaseModels ?? false;

const value = {
// State
searchValue,
Expand All @@ -221,6 +224,7 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
assistantsMap,
mappedEndpoints,
endpointsConfig,
hideBaseModels,

// Functions
handleSelectSpec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
assistantsMap,
modelSpecs,
selectedValues,
hideBaseModels,
handleOpenKeyDialog,
handleSelectEndpoint,
endpointSearchValues,
Expand Down Expand Up @@ -173,23 +174,24 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
{endpointSpecs.map((spec: TModelSpec) => (
<ModelSpecItem key={spec.name} spec={spec} isSelected={selectedSpec === spec.name} />
))}
{/* Render endpoint models */}
{filteredModels
? renderEndpointModels(
endpoint,
endpoint.models || [],
selectedModel,
filteredModels,
endpointIndex,
)
: endpoint.models &&
renderEndpointModels(
endpoint,
endpoint.models,
selectedModel,
undefined,
endpointIndex,
)}
{/* Render endpoint models (hidden when hideBaseModels is enabled and there are specs for this endpoint) */}
{!(hideBaseModels && endpointSpecs.length > 0) &&
(filteredModels
? renderEndpointModels(
endpoint,
endpoint.models || [],
selectedModel,
filteredModels,
endpointIndex,
)
: endpoint.models &&
renderEndpointModels(
endpoint,
endpoint.models,
selectedModel,
undefined,
endpointIndex,
))}
</>
)}
</Menu>
Expand Down
3 changes: 3 additions & 0 deletions librechat.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ endpoints:
# - Only needs to be set on one spec per group (first one is used)
# - Can be a URL or a built-in endpoint key (e.g., "openAI", "anthropic", "groq")
# modelSpecs:
# enforce: false # If true, only model specs can be used, not base models
# prioritize: true # If true, model specs are shown before base models
# hideBaseModels: false # If true, hides base models when specs exist for an endpoint
# list:
# # Example 1: Nested under an endpoint (grouped with openAI endpoint)
# - name: "gpt-4o"
Expand Down
62 changes: 62 additions & 0 deletions packages/data-provider/specs/models.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { specsConfigSchema } from '../src/models';
import { EModelEndpoint } from '../src/schemas';

describe('specsConfigSchema', () => {
const validModelSpec = {
name: 'test-model',
label: 'Test Model',
preset: {
endpoint: EModelEndpoint.openAI,
},
};

describe('hideBaseModels option', () => {
it('should default hideBaseModels to false when not provided', () => {
const config = {
list: [validModelSpec],
};

const result = specsConfigSchema.safeParse(config);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.hideBaseModels).toBe(false);
}
});

it('should accept hideBaseModels: true', () => {
const config = {
hideBaseModels: true,
list: [validModelSpec],
};

const result = specsConfigSchema.safeParse(config);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.hideBaseModels).toBe(true);
}
});

it('should accept hideBaseModels: false', () => {
const config = {
hideBaseModels: false,
list: [validModelSpec],
};

const result = specsConfigSchema.safeParse(config);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.hideBaseModels).toBe(false);
}
});

it('should reject hideBaseModels with non-boolean value', () => {
const config = {
hideBaseModels: 'yes',
list: [validModelSpec],
};

const result = specsConfigSchema.safeParse(config);
expect(result.success).toBe(false);
});
});
});
1 change: 1 addition & 0 deletions packages/data-provider/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const tModelSpecSchema = z.object({
export const specsConfigSchema = z.object({
enforce: z.boolean().default(false),
prioritize: z.boolean().default(true),
hideBaseModels: z.boolean().default(false),
list: z.array(tModelSpecSchema).min(1),
addedEndpoints: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
});
Expand Down
Loading