🔧 refactor: Consolidate Logging, Model Selection & Actions Optimizations, Minor Fixes (#6553)

* 🔧 feat: Enhance logging configuration for production and debug environments

* 🔒 feat: Implement encryption and decryption functions for sensitive values in ActionService with URL encoding/decoding

* refactor: optimize action service for agent tools

* refactor: optimize action processing for Assistants API

* fix: handle case where agent is not found in loadAgent function

* refactor: improve error handling in API calls by throwing new Error with logAxiosError output

* chore: bump @librechat/agents to 2.3.95, fixes "Invalid tool call structure: No preceding AIMessage with tool_call_ids"

* refactor: enhance error logging in logAxiosError function to include response status

* refactor: remove unused useModelSelection hook from Endpoint

* refactor: add support for assistants in useSelectorEffects hook

* refactor: replace string easing with imported easings in Landing component

* chore: remove duplicate translation

* refactor: update model selection logic and improve localization for UI elements

* refactor: replace endpoint value checks with helper functions for agents and assistants

* refactor: optimize display value logic and utilize useMemo for performance improvements

* refactor: clean up imports and optimize display/icon value logic in endpoint components, fix spec selection

* refactor: enhance error logging in axios utility to include stack traces for better debugging

* refactor: update logging configuration to use DEBUG_LOGGING and streamline log level handling

* refactor: adjust className for export menu button to improve layout consistency and remove unused title prop from ShareButton

* refactor: update import path for logAxiosError utility to improve module organization and clarity

* refactor: implement debounced search value setter in ModelSelectorContext for improved performance
This commit is contained in:
Danny Avila 2025-03-26 14:10:52 -04:00 committed by GitHub
parent 801b602e27
commit 299cabd6ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 970 additions and 1135 deletions

View file

@ -79,7 +79,7 @@ export default function ExportAndShareMenu({
<Ariakit.MenuButton
id="export-menu-button"
aria-label="Export options"
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
>
<Upload
className="icon-md text-text-secondary"
@ -103,7 +103,6 @@ export default function ExportAndShareMenu({
<ShareButton
triggerRef={shareButtonRef}
conversationId={conversation.conversationId ?? ''}
title={conversation.title ?? ''}
open={showShareDialog}
onOpenChange={setShowShareDialog}
/>

View file

@ -1,6 +1,6 @@
import { useMemo, useCallback, useState, useEffect } from 'react';
import { easings } from '@react-spring/web';
import { useMemo, useCallback } from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import type * as t from 'librechat-data-provider';
import { useChatContext, useAgentsMapContext, useAssistantsMapContext } from '~/Providers';
import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
import { BirthdayIcon, TooltipAnchor, SplitText } from '~/components';
@ -117,7 +117,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
textAlign="center"
animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }}
animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }}
easing="easeOutCubic"
easing={easings.easeOutCubic}
threshold={0}
rootMargin="0px"
/>
@ -131,7 +131,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
textAlign="center"
animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }}
animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }}
easing="easeOutCubic"
easing={easings.easeOutCubic}
threshold={0}
rootMargin="0px"
/>

View file

@ -1,10 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import type { ModelSelectorProps } from '~/common';
import { ModelSelectorProvider, useModelSelectorContext } from './ModelSelectorContext';
import { renderModelSpecs, renderEndpoints, renderSearchResults } from './components';
import { getSelectedIcon, getDisplayValue } from './utils';
import { CustomMenu as Menu } from './CustomMenu';
import DialogManager from './DialogManager';
import { getSelectedIcon } from './utils';
import { useLocalize } from '~/hooks';
function ModelSelectorContent() {
@ -22,7 +22,6 @@ function ModelSelectorContent() {
// Functions
setSearchValue,
getDisplayValue,
setSelectedValues,
// Dialog
keyDialogOpen,
@ -30,18 +29,31 @@ function ModelSelectorContent() {
keyDialogEndpoint,
} = useModelSelectorContext();
const selectedIcon = getSelectedIcon({
mappedEndpoints: mappedEndpoints ?? [],
selectedValues,
modelSpecs,
endpointsConfig,
});
const selectedDisplayValue = getDisplayValue();
const selectedIcon = useMemo(
() =>
getSelectedIcon({
mappedEndpoints: mappedEndpoints ?? [],
selectedValues,
modelSpecs,
endpointsConfig,
}),
[mappedEndpoints, selectedValues, modelSpecs, endpointsConfig],
);
const selectedDisplayValue = useMemo(
() =>
getDisplayValue({
localize,
modelSpecs,
selectedValues,
mappedEndpoints,
}),
[localize, modelSpecs, selectedValues, mappedEndpoints],
);
const trigger = (
<button
className="my-1 flex h-10 w-full max-w-[70vw] items-center justify-center gap-2 rounded-xl border border-border-light bg-surface-secondary px-3 py-2 text-sm text-text-primary hover:bg-surface-tertiary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
aria-label={localize('com_endpoint_select_model')}
aria-label={localize('com_ui_select_model')}
>
{selectedIcon && React.isValidElement(selectedIcon) && (
<div className="flex flex-shrink-0 items-center justify-center overflow-hidden">

View file

@ -1,9 +1,10 @@
import React, { startTransition, createContext, useContext, useState, useMemo } from 'react';
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import debounce from 'lodash/debounce';
import React, { createContext, useContext, useState, useMemo } from 'react';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type * as t from 'librechat-data-provider';
import type { Endpoint, SelectedValues } from '~/common';
import { useAgentsMapContext, useAssistantsMapContext, useChatContext } from '~/Providers';
import { useEndpoints, useSelectorEffects, useKeyDialog, useLocalize } from '~/hooks';
import { useEndpoints, useSelectorEffects, useKeyDialog } from '~/hooks';
import useSelectMention from '~/hooks/Input/useSelectMention';
import { useGetEndpointsQuery } from '~/data-provider';
import { filterItems } from './utils';
@ -22,7 +23,6 @@ type ModelSelectorContextType = {
endpointsConfig: t.TEndpointsConfig;
// Functions
getDisplayValue: () => string;
endpointRequiresUserKey: (endpoint: string) => boolean;
setSelectedValues: React.Dispatch<React.SetStateAction<SelectedValues>>;
setSearchValue: (value: string) => void;
@ -53,7 +53,6 @@ export function ModelSelectorProvider({
modelSpecs,
interfaceConfig,
}: ModelSelectorProviderProps) {
const localize = useLocalize();
const agentsMap = useAgentsMapContext();
const assistantsMap = useAssistantsMapContext();
const { data: endpointsConfig } = useGetEndpointsQuery();
@ -101,10 +100,13 @@ export function ModelSelectorProvider({
}, [searchValue, modelSpecs, mappedEndpoints, agentsMap, assistantsMap]);
// Functions
const setSearchValue = (value: string) => {
startTransition(() => setSearchValueState(value));
};
const setDebouncedSearchValue = useMemo(
() =>
debounce((value: string) => {
setSearchValueState(value);
}, 200),
[],
);
const setEndpointSearchValue = (endpoint: string, value: string) => {
setEndpointSearchValues((prev) => ({
...prev,
@ -113,10 +115,16 @@ export function ModelSelectorProvider({
};
const handleSelectSpec = (spec: t.TModelSpec) => {
let model = spec.preset.model ?? null;
onSelectSpec?.(spec);
if (isAgentsEndpoint(spec.preset.endpoint)) {
model = spec.preset.agent_id ?? '';
} else if (isAssistantsEndpoint(spec.preset.endpoint)) {
model = spec.preset.assistant_id ?? '';
}
setSelectedValues({
endpoint: spec.preset.endpoint,
model: spec.preset.model ?? null,
model,
modelSpec: spec.name,
});
};
@ -154,46 +162,6 @@ export function ModelSelectorProvider({
});
};
const getDisplayValue = () => {
if (selectedValues.modelSpec) {
const spec = modelSpecs.find((s) => s.name === selectedValues.modelSpec);
return spec?.label || localize('com_endpoint_select_model');
}
if (selectedValues.model && selectedValues.endpoint) {
const endpoint = mappedEndpoints.find((e) => e.value === selectedValues.endpoint);
if (!endpoint) {
return localize('com_endpoint_select_model');
}
if (
endpoint.value === EModelEndpoint.agents &&
endpoint.agentNames &&
endpoint.agentNames[selectedValues.model]
) {
return endpoint.agentNames[selectedValues.model];
}
if (
(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
endpoint.assistantNames &&
endpoint.assistantNames[selectedValues.model]
) {
return endpoint.assistantNames[selectedValues.model];
}
return selectedValues.model;
}
if (selectedValues.endpoint) {
const endpoint = mappedEndpoints.find((e) => e.value === selectedValues.endpoint);
return endpoint?.label || localize('com_endpoint_select_model');
}
return localize('com_endpoint_select_model');
};
const value = {
// State
searchValue,
@ -208,14 +176,13 @@ export function ModelSelectorProvider({
endpointsConfig,
// Functions
setSearchValue,
getDisplayValue,
handleSelectSpec,
handleSelectModel,
setSelectedValues,
handleSelectEndpoint,
setEndpointSearchValue,
endpointRequiresUserKey,
setSearchValue: setDebouncedSearchValue,
// Dialog
...keyProps,
};

View file

@ -116,17 +116,15 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
</div>
}
>
{(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
endpoint.models === undefined ? (
<div className="flex items-center justify-center p-2">
<Spinner />
</div>
) : filteredModels ? (
renderEndpointModels(endpoint, endpoint.models || [], selectedModel, filteredModels)
) : (
endpoint.models && renderEndpointModels(endpoint, endpoint.models, selectedModel)
)}
{isAssistantsEndpoint(endpoint.value) && endpoint.models === undefined ? (
<div className="flex items-center justify-center p-2">
<Spinner />
</div>
) : filteredModels ? (
renderEndpointModels(endpoint, endpoint.models || [], selectedModel, filteredModels)
) : (
endpoint.models && renderEndpointModels(endpoint, endpoint.models, selectedModel)
)}
</Menu>
);
} else {

View file

@ -1,5 +1,5 @@
import React from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
import { useModelSelectorContext } from '../ModelSelectorContext';
import { CustomMenuItem as MenuItem } from '../CustomMenu';
@ -16,18 +16,12 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
const avatarUrl = endpoint?.modelIcons?.[modelId ?? ''] || null;
// Use custom names if available
if (
endpoint &&
modelId &&
endpoint.value === EModelEndpoint.agents &&
endpoint.agentNames?.[modelId]
) {
if (endpoint && modelId && isAgentsEndpoint(endpoint.value) && endpoint.agentNames?.[modelId]) {
modelName = endpoint.agentNames[modelId];
} else if (
endpoint &&
modelId &&
(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames?.[modelId]
) {
modelName = endpoint.assistantNames[modelId];
@ -44,9 +38,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
<div className="flex h-5 w-5 items-center justify-center overflow-hidden rounded-full">
<img src={avatarUrl} alt={modelName ?? ''} className="h-full w-full object-cover" />
</div>
) : (endpoint.value === EModelEndpoint.agents ||
endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
) : (isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)) &&
endpoint.icon ? (
<div className="flex h-5 w-5 items-center justify-center overflow-hidden rounded-full">
{endpoint.icon}

View file

@ -1,5 +1,5 @@
import React, { Fragment } from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { TModelSpec } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
import { useModelSelectorContext } from '../ModelSelectorContext';
@ -105,14 +105,13 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
: endpoint.models.filter((modelId) => {
let modelName = modelId;
if (
endpoint.value === EModelEndpoint.agents &&
isAgentsEndpoint(endpoint.value) &&
endpoint.agentNames &&
endpoint.agentNames[modelId]
) {
modelName = endpoint.agentNames[modelId];
} else if (
(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames &&
endpoint.assistantNames[modelId]
) {
@ -138,14 +137,13 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
{filteredModels.map((modelId) => {
let modelName = modelId;
if (
endpoint.value === EModelEndpoint.agents &&
isAgentsEndpoint(endpoint.value) &&
endpoint.agentNames &&
endpoint.agentNames[modelId]
) {
modelName = endpoint.agentNames[modelId];
} else if (
(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames &&
endpoint.assistantNames[modelId]
) {

View file

@ -1,12 +1,13 @@
import React from 'react';
import { Bot } from 'lucide-react';
import { EModelEndpoint } from 'librechat-data-provider';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type {
TModelSpec,
TAgentsMap,
TAssistantsMap,
TEndpointsConfig,
TModelSpec,
} from 'librechat-data-provider';
import type { useLocalize } from '~/hooks';
import SpecIcon from '~/components/Chat/Menus/Endpoints/components/SpecIcon';
import { Endpoint, SelectedValues } from '~/common';
@ -39,17 +40,13 @@ export function filterItems<
return true;
}
if (item.value === EModelEndpoint.agents && agentsMap && modelId in agentsMap) {
if (isAgentsEndpoint(item.value) && agentsMap && modelId in agentsMap) {
const agentName = agentsMap[modelId]?.name;
return typeof agentName === 'string' && agentName.toLowerCase().includes(searchTermLower);
}
if (
(item.value === EModelEndpoint.assistants ||
item.value === EModelEndpoint.azureAssistants) &&
assistantsMap
) {
const endpoint = item.value;
if (isAssistantsEndpoint(item.value) && assistantsMap) {
const endpoint = item.value ?? '';
const assistant = assistantsMap[endpoint][modelId];
if (assistant && typeof assistant.name === 'string') {
return assistant.name.toLowerCase().includes(searchTermLower);
@ -80,11 +77,10 @@ export function filterModels(
return models.filter((modelId) => {
let modelName = modelId;
if (endpoint.value === EModelEndpoint.agents && agentsMap && agentsMap[modelId]) {
if (isAgentsEndpoint(endpoint.value) && agentsMap && agentsMap[modelId]) {
modelName = agentsMap[modelId].name || modelId;
} else if (
(endpoint.value === EModelEndpoint.assistants ||
endpoint.value === EModelEndpoint.azureAssistants) &&
isAssistantsEndpoint(endpoint.value) &&
assistantsMap &&
assistantsMap[endpoint.value]
) {
@ -160,3 +156,52 @@ export function getSelectedIcon({
return null;
}
export const getDisplayValue = ({
localize,
mappedEndpoints,
selectedValues,
modelSpecs,
}: {
localize: ReturnType<typeof useLocalize>;
selectedValues: SelectedValues;
mappedEndpoints: Endpoint[];
modelSpecs: TModelSpec[];
}) => {
if (selectedValues.modelSpec) {
const spec = modelSpecs.find((s) => s.name === selectedValues.modelSpec);
return spec?.label || spec?.name || localize('com_ui_select_model');
}
if (selectedValues.model && selectedValues.endpoint) {
const endpoint = mappedEndpoints.find((e) => e.value === selectedValues.endpoint);
if (!endpoint) {
return localize('com_ui_select_model');
}
if (
isAgentsEndpoint(endpoint.value) &&
endpoint.agentNames &&
endpoint.agentNames[selectedValues.model]
) {
return endpoint.agentNames[selectedValues.model];
}
if (
isAssistantsEndpoint(endpoint.value) &&
endpoint.assistantNames &&
endpoint.assistantNames[selectedValues.model]
) {
return endpoint.assistantNames[selectedValues.model];
}
return selectedValues.model;
}
if (selectedValues.endpoint) {
const endpoint = mappedEndpoints.find((e) => e.value === selectedValues.endpoint);
return endpoint?.label || localize('com_ui_select_model');
}
return localize('com_ui_select_model');
};