mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔧 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:
parent
b9ebdd4aa5
commit
e630c0a00d
13 changed files with 76 additions and 127 deletions
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { TModelSpec, TInterfaceConfig } from 'librechat-data-provider';
|
||||
import { TModelSpec, TStartupConfig } from 'librechat-data-provider';
|
||||
|
||||
export interface Endpoint {
|
||||
value: string;
|
||||
label: string;
|
||||
hasModels: boolean;
|
||||
models?: string[];
|
||||
models?: Array<{ name: string; isGlobal?: boolean }>;
|
||||
icon: React.ReactNode;
|
||||
agentNames?: Record<string, string>;
|
||||
assistantNames?: Record<string, string>;
|
||||
|
|
@ -19,6 +19,6 @@ export interface SelectedValues {
|
|||
}
|
||||
|
||||
export interface ModelSelectorProps {
|
||||
interfaceConfig: TInterfaceConfig;
|
||||
startupConfig: TStartupConfig | undefined;
|
||||
modelSpecs: TModelSpec[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,17 +496,6 @@ export interface ExtendedFile {
|
|||
metadata?: t.TFile['metadata'];
|
||||
}
|
||||
|
||||
export interface ExtendedEndpoint {
|
||||
value: EModelEndpoint;
|
||||
label: string;
|
||||
hasModels: boolean;
|
||||
icon: JSX.Element | null;
|
||||
models?: string[];
|
||||
agentNames?: Record<string, string>;
|
||||
assistantNames?: Record<string, string>;
|
||||
modelIcons?: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export interface ModelItemProps {
|
||||
modelName: string;
|
||||
endpoint: EModelEndpoint;
|
||||
|
|
|
|||
|
|
@ -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 />}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import type {
|
|||
Assistant,
|
||||
TEndpointsConfig,
|
||||
TAgentsMap,
|
||||
TInterfaceConfig,
|
||||
TAssistantsMap,
|
||||
TStartupConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { ExtendedEndpoint } from '~/common';
|
||||
import type { Endpoint } from '~/common';
|
||||
import { mapEndpoints, getIconKey, getEndpointField } from '~/utils';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
|
@ -25,16 +25,18 @@ export const useEndpoints = ({
|
|||
agentsMap,
|
||||
assistantsMap,
|
||||
endpointsConfig,
|
||||
interfaceConfig,
|
||||
startupConfig,
|
||||
}: {
|
||||
agentsMap?: TAgentsMap;
|
||||
assistantsMap?: TAssistantsMap;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
interfaceConfig: TInterfaceConfig;
|
||||
startupConfig: TStartupConfig | undefined;
|
||||
}) => {
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const { conversation } = useChatContext();
|
||||
const { data: endpoints = [] } = useGetEndpointsQuery({ select: mapEndpoints });
|
||||
const { instanceProjectId } = startupConfig ?? {};
|
||||
const interfaceConfig = startupConfig?.interface ?? {};
|
||||
|
||||
const { endpoint } = conversation ?? {};
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ export const useEndpoints = ({
|
|||
[endpointsConfig],
|
||||
);
|
||||
|
||||
const mappedEndpoints: ExtendedEndpoint[] = useMemo(() => {
|
||||
const mappedEndpoints: Endpoint[] = useMemo(() => {
|
||||
return filteredEndpoints.map((ep) => {
|
||||
const endpointType = getEndpointField(endpointsConfig, ep, 'type');
|
||||
const iconKey = getIconKey({ endpoint: ep, endpointsConfig, endpointType });
|
||||
|
|
@ -98,7 +100,7 @@ export const useEndpoints = ({
|
|||
(modelsQuery.data?.[ep]?.length ?? 0) > 0);
|
||||
|
||||
// Base result object with formatted default icon
|
||||
const result: ExtendedEndpoint = {
|
||||
const result: Endpoint = {
|
||||
value: ep,
|
||||
label: alternateName[ep] || ep,
|
||||
hasModels,
|
||||
|
|
@ -114,7 +116,11 @@ export const useEndpoints = ({
|
|||
|
||||
// Handle agents case
|
||||
if (ep === EModelEndpoint.agents && agents.length > 0) {
|
||||
result.models = agents.map((agent) => agent.id);
|
||||
result.models = agents.map((agent) => ({
|
||||
name: agent.id,
|
||||
isGlobal:
|
||||
(instanceProjectId != null && agent.projectIds?.includes(instanceProjectId)) ?? false,
|
||||
}));
|
||||
result.agentNames = agents.reduce((acc, agent) => {
|
||||
acc[agent.id] = agent.name || '';
|
||||
return acc;
|
||||
|
|
@ -127,7 +133,10 @@ export const useEndpoints = ({
|
|||
|
||||
// Handle assistants case
|
||||
else if (ep === EModelEndpoint.assistants && assistants.length > 0) {
|
||||
result.models = assistants.map((assistant: { id: string }) => assistant.id);
|
||||
result.models = assistants.map((assistant: { id: string }) => ({
|
||||
name: assistant.id,
|
||||
isGlobal: false,
|
||||
}));
|
||||
result.assistantNames = assistants.reduce(
|
||||
(acc: Record<string, string>, assistant: Assistant) => {
|
||||
acc[assistant.id] = assistant.name || '';
|
||||
|
|
@ -143,7 +152,10 @@ export const useEndpoints = ({
|
|||
{},
|
||||
);
|
||||
} else if (ep === EModelEndpoint.azureAssistants && azureAssistants.length > 0) {
|
||||
result.models = azureAssistants.map((assistant: { id: string }) => assistant.id);
|
||||
result.models = azureAssistants.map((assistant: { id: string }) => ({
|
||||
name: assistant.id,
|
||||
isGlobal: false,
|
||||
}));
|
||||
result.assistantNames = azureAssistants.reduce(
|
||||
(acc: Record<string, string>, assistant: Assistant) => {
|
||||
acc[assistant.id] = assistant.name || '';
|
||||
|
|
@ -166,7 +178,10 @@ export const useEndpoints = ({
|
|||
ep !== EModelEndpoint.assistants &&
|
||||
(modelsQuery.data?.[ep]?.length ?? 0) > 0
|
||||
) {
|
||||
result.models = modelsQuery.data?.[ep];
|
||||
result.models = modelsQuery.data?.[ep]?.map((model) => ({
|
||||
name: model,
|
||||
isGlobal: false,
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { ExtendedEndpoint } from '~/common';
|
||||
|
||||
export const filterMenuItems = (
|
||||
searchTerm: string,
|
||||
mappedEndpoints: ExtendedEndpoint[],
|
||||
agents: any[],
|
||||
assistants: any[],
|
||||
modelsData: any,
|
||||
): ExtendedEndpoint[] => {
|
||||
if (!searchTerm.trim()) {
|
||||
return mappedEndpoints;
|
||||
}
|
||||
|
||||
const lowercaseSearchTerm = searchTerm.toLowerCase();
|
||||
|
||||
return mappedEndpoints
|
||||
.map((ep) => {
|
||||
if (ep.hasModels) {
|
||||
if (isAgentsEndpoint(ep.value)) {
|
||||
const filteredAgents = agents.filter((agent) =>
|
||||
agent.name?.toLowerCase().includes(lowercaseSearchTerm),
|
||||
);
|
||||
if (ep.label.toLowerCase().includes(lowercaseSearchTerm) || filteredAgents.length > 0) {
|
||||
return {
|
||||
...ep,
|
||||
models: filteredAgents.map((agent) => agent.id),
|
||||
agentNames: filteredAgents.reduce((acc: Record<string, string>, agent) => {
|
||||
acc[agent.id] = agent.name || '';
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} else if (isAssistantsEndpoint(ep.value)) {
|
||||
const filteredAssistants = assistants.filter((assistant) =>
|
||||
assistant.name?.toLowerCase().includes(lowercaseSearchTerm),
|
||||
);
|
||||
if (
|
||||
ep.label.toLowerCase().includes(lowercaseSearchTerm) ||
|
||||
filteredAssistants.length > 0
|
||||
) {
|
||||
return {
|
||||
...ep,
|
||||
models: filteredAssistants.map((assistant) => assistant.id),
|
||||
assistantNames: filteredAssistants.reduce(
|
||||
(acc: Record<string, string>, assistant) => {
|
||||
acc[assistant.id] = assistant.name || '';
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
const allModels = modelsData?.[ep.value] ?? [];
|
||||
const filteredModels = allModels.filter((model: string) =>
|
||||
model.toLowerCase().includes(lowercaseSearchTerm),
|
||||
);
|
||||
if (ep.label.toLowerCase().includes(lowercaseSearchTerm) || filteredModels.length > 0) {
|
||||
return { ...ep, models: filteredModels };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return ep.label.toLowerCase().includes(lowercaseSearchTerm) ? { ...ep, models: [] } : null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as ExtendedEndpoint[];
|
||||
};
|
||||
|
||||
export default filterMenuItems;
|
||||
|
|
@ -20,7 +20,6 @@ export { default as logger } from './logger';
|
|||
export { default as buildTree } from './buildTree';
|
||||
export { default as getLoginError } from './getLoginError';
|
||||
export { default as cleanupPreset } from './cleanupPreset';
|
||||
export { default as filterMenuItems } from './endpointFilter';
|
||||
export { default as buildDefaultConvo } from './buildDefaultConvo';
|
||||
export { default as getDefaultEndpoint } from './getDefaultEndpoint';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue