🔧 refactor: Enhance Model & Endpoint Configurations with Global Indicators 🌍 (#6578)

* 🔧 fix: Simplify event handling in Badge component by always preventing default behavior and stopping propagation on toggle

* feat: show Global agents icon in ModelSelector

* feat: show Global agents icon in ModelSelector's search results

* refactor(Header): remove unused import

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor(EndpointModelItem): remove unused import of useGetStartupConfig

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Marco Beretta 2025-03-27 23:07:07 +01:00 committed by GitHub
parent b9ebdd4aa5
commit e630c0a00d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 76 additions and 127 deletions

View file

@ -9,7 +9,6 @@ import ExportAndShareMenu from './ExportAndShareMenu';
import { useMediaQuery, useHasAccess } from '~/hooks';
import BookmarkMenu from './Menus/BookmarkMenu';
import AddMultiConvo from './AddMultiConvo';
const defaultInterface = getConfigDefaults().interface;
export default function Header() {
@ -38,7 +37,7 @@ export default function Header() {
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
<div className="mx-2 flex items-center gap-2">
{!navVisible && <HeaderNewChat />}
{<ModelSelector interfaceConfig={interfaceConfig} modelSpecs={modelSpecs} />}
{<ModelSelector startupConfig={startupConfig} modelSpecs={modelSpecs} />}
{interfaceConfig.presets === true && interfaceConfig.modelSelect && <PresetsMenu />}
{hasAccessToBookmarks === true && <BookmarkMenu />}
{hasAccessToMultiConvo === true && <AddMultiConvo />}

View file

@ -98,9 +98,9 @@ function ModelSelectorContent() {
);
}
export default function ModelSelector({ interfaceConfig, modelSpecs }: ModelSelectorProps) {
export default function ModelSelector({ startupConfig, modelSpecs }: ModelSelectorProps) {
return (
<ModelSelectorProvider modelSpecs={modelSpecs} interfaceConfig={interfaceConfig}>
<ModelSelectorProvider modelSpecs={modelSpecs} startupConfig={startupConfig}>
<ModelSelectorContent />
</ModelSelectorProvider>
);

View file

@ -45,13 +45,13 @@ export function useModelSelectorContext() {
interface ModelSelectorProviderProps {
children: React.ReactNode;
modelSpecs: t.TModelSpec[];
interfaceConfig: t.TInterfaceConfig;
startupConfig: t.TStartupConfig | undefined;
}
export function ModelSelectorProvider({
children,
modelSpecs,
interfaceConfig,
startupConfig,
}: ModelSelectorProviderProps) {
const agentsMap = useAgentsMapContext();
const assistantsMap = useAssistantsMapContext();
@ -61,7 +61,7 @@ export function ModelSelectorProvider({
agentsMap,
assistantsMap,
endpointsConfig,
interfaceConfig,
startupConfig,
});
const { onSelectEndpoint, onSelectSpec } = useSelectMention({
// presets,

View file

@ -89,7 +89,13 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
if (endpoint.hasModels) {
const filteredModels = searchValue
? filterModels(endpoint, endpoint.models || [], searchValue, agentsMap, assistantsMap)
? filterModels(
endpoint,
(endpoint.models || []).map((model) => model.name),
searchValue,
agentsMap,
assistantsMap,
)
: null;
const placeholder =
isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)

View file

@ -1,4 +1,5 @@
import React from 'react';
import { EarthIcon } from 'lucide-react';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
import { useModelSelectorContext } from '../ModelSelectorContext';
@ -12,12 +13,16 @@ interface EndpointModelItemProps {
export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointModelItemProps) {
const { handleSelectModel } = useModelSelectorContext();
let isGlobal = false;
let modelName = modelId;
const avatarUrl = endpoint?.modelIcons?.[modelId ?? ''] || null;
// Use custom names if available
if (endpoint && modelId && isAgentsEndpoint(endpoint.value) && endpoint.agentNames?.[modelId]) {
modelName = endpoint.agentNames[modelId];
const modelInfo = endpoint?.models?.find((m) => m.name === modelId);
isGlobal = modelInfo?.isGlobal ?? false;
} else if (
endpoint &&
modelId &&
@ -46,6 +51,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
) : null}
<span>{modelName}</span>
</div>
{isGlobal && <EarthIcon className="ml-auto size-4 text-green-400" />}
{isSelected && (
<svg
width="16"
@ -53,7 +59,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="ml-auto block"
className="block"
>
<path
fillRule="evenodd"
@ -69,11 +75,11 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
export function renderEndpointModels(
endpoint: Endpoint | null,
models: string[],
models: Array<{ name: string; isGlobal?: boolean }>,
selectedModel: string | null,
filteredModels?: string[],
) {
const modelsToRender = filteredModels || models;
const modelsToRender = filteredModels || models.map((model) => model.name);
return modelsToRender.map(
(modelId) =>

View file

@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
import { EarthIcon } from 'lucide-react';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { TModelSpec } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
@ -20,8 +21,6 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
handleSelectModel,
handleSelectEndpoint,
endpointsConfig,
agentsMap,
assistantsMap,
} = useModelSelectorContext();
const {
@ -102,20 +101,20 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
const lowerQuery = searchValue.toLowerCase();
const filteredModels = endpoint.label.toLowerCase().includes(lowerQuery)
? endpoint.models
: endpoint.models.filter((modelId) => {
let modelName = modelId;
: endpoint.models.filter((model) => {
let modelName = model.name;
if (
isAgentsEndpoint(endpoint.value) &&
endpoint.agentNames &&
endpoint.agentNames[modelId]
endpoint.agentNames[model.name]
) {
modelName = endpoint.agentNames[modelId];
modelName = endpoint.agentNames[model.name];
} else if (
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames &&
endpoint.assistantNames[modelId]
endpoint.assistantNames[model.name]
) {
modelName = endpoint.assistantNames[modelId];
modelName = endpoint.assistantNames[model.name];
}
return modelName.toLowerCase().includes(lowerQuery);
});
@ -134,7 +133,10 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
)}
{endpoint.label}
</div>
{filteredModels.map((modelId) => {
{filteredModels.map((model) => {
const modelId = model.name;
let isGlobal = false;
let modelName = modelId;
if (
isAgentsEndpoint(endpoint.value) &&
@ -142,6 +144,8 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
endpoint.agentNames[modelId]
) {
modelName = endpoint.agentNames[modelId];
const modelInfo = endpoint?.models?.find((m) => m.name === modelId);
isGlobal = modelInfo?.isGlobal ?? false;
} else if (
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames &&
@ -168,6 +172,7 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
)}
<span>{modelName}</span>
</div>
{isGlobal && <EarthIcon className="ml-auto size-4 text-green-400" />}
{selectedEndpoint === endpoint.value && selectedModel === modelId && (
<svg
width="16"
@ -175,7 +180,7 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="ml-auto block"
className="block"
>
<path
fillRule="evenodd"

View file

@ -12,7 +12,12 @@ import SpecIcon from '~/components/Chat/Menus/Endpoints/components/SpecIcon';
import { Endpoint, SelectedValues } from '~/common';
export function filterItems<
T extends { label: string; name?: string; value?: string; models?: string[] },
T extends {
label: string;
name?: string;
value?: string;
models?: Array<{ name: string; isGlobal?: boolean }>;
},
>(
items: T[],
searchValue: string,
@ -36,18 +41,18 @@ export function filterItems<
if (item.models && item.models.length > 0) {
return item.models.some((modelId) => {
if (modelId.toLowerCase().includes(searchTermLower)) {
if (modelId.name.toLowerCase().includes(searchTermLower)) {
return true;
}
if (isAgentsEndpoint(item.value) && agentsMap && modelId in agentsMap) {
const agentName = agentsMap[modelId]?.name;
if (isAgentsEndpoint(item.value) && agentsMap && modelId.name in agentsMap) {
const agentName = agentsMap[modelId.name]?.name;
return typeof agentName === 'string' && agentName.toLowerCase().includes(searchTermLower);
}
if (isAssistantsEndpoint(item.value) && assistantsMap) {
const endpoint = item.value ?? '';
const assistant = assistantsMap[endpoint][modelId];
const assistant = assistantsMap[endpoint][modelId.name];
if (assistant && typeof assistant.name === 'string') {
return assistant.name.toLowerCase().includes(searchTermLower);
}

View file

@ -44,9 +44,7 @@ export default function Badge({
}
if (!isEditing && onToggle) {
if (typeof window !== 'undefined' && window.innerWidth >= 768) {
e.preventDefault();
}
e.preventDefault();
e.stopPropagation();
onToggle();
}