mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 17:30:16 +01:00
feat: visual indicator for global agent, remove author when serving to non-author
This commit is contained in:
parent
1bc0689134
commit
2c6874013a
6 changed files with 77 additions and 29 deletions
|
|
@ -83,6 +83,7 @@ const getListAgents = async (searchParameter) => {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatar: 1,
|
avatar: 1,
|
||||||
|
projectIds: 1,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
const hasMore = agents.length > 0;
|
const hasMore = agents.length > 0;
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ const getAgentHandler = async (req, res) => {
|
||||||
return res.status(404).json({ error: 'Agent not found' });
|
return res.status(404).json({ error: 'Agent not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agent.author !== author) {
|
||||||
|
delete agent.author;
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).json(agent);
|
return res.status(200).json(agent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/Agents/:id] Error retrieving agent', error);
|
logger.error('[/Agents/:id] Error retrieving agent', error);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Capabilities } from 'librechat-data-provider';
|
import { Capabilities } from 'librechat-data-provider';
|
||||||
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
||||||
import type { Option, ExtendedFile } from './types';
|
import type { OptionWithIcon, ExtendedFile } from './types';
|
||||||
|
|
||||||
export type TAgentOption = Option &
|
export type TAgentOption = OptionWithIcon &
|
||||||
Agent & {
|
Agent & {
|
||||||
files?: Array<[string, ExtendedFile]>;
|
files?: Array<[string, ExtendedFile]>;
|
||||||
code_files?: Array<[string, ExtendedFile]>;
|
code_files?: Array<[string, ExtendedFile]>;
|
||||||
|
|
@ -23,5 +23,5 @@ export type AgentForm = {
|
||||||
model: string | null;
|
model: string | null;
|
||||||
model_parameters: AgentModelParameters;
|
model_parameters: AgentModelParameters;
|
||||||
tools?: string[];
|
tools?: string[];
|
||||||
provider?: AgentProvider | Option;
|
provider?: AgentProvider | OptionWithIcon;
|
||||||
} & AgentCapabilities;
|
} & AgentCapabilities;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus, EarthIcon } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
import { Capabilities, defaultAgentFormValues } from 'librechat-data-provider';
|
import { Capabilities, defaultAgentFormValues } from 'librechat-data-provider';
|
||||||
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
|
|
||||||
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
||||||
import type { UseMutationResult } from '@tanstack/react-query';
|
import type { UseMutationResult } from '@tanstack/react-query';
|
||||||
import type { UseFormReset } from 'react-hook-form';
|
import type { UseFormReset } from 'react-hook-form';
|
||||||
|
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
|
||||||
import { cn, createDropdownSetter, createProviderOption, processAgentOption } from '~/utils';
|
import { cn, createDropdownSetter, createProviderOption, processAgentOption } from '~/utils';
|
||||||
import { useListAgentsQuery, useGetAgentByIdQuery } from '~/data-provider';
|
import { useListAgentsQuery, useGetAgentByIdQuery } from '~/data-provider';
|
||||||
import SelectDropDown from '~/components/ui/SelectDropDown';
|
import SelectDropDown from '~/components/ui/SelectDropDown';
|
||||||
|
|
@ -31,8 +32,16 @@ export default function AgentSelect({
|
||||||
// const fileMap = useFileMapContext();
|
// const fileMap = useFileMapContext();
|
||||||
const lastSelectedAgent = useRef<string | null>(null);
|
const lastSelectedAgent = useRef<string | null>(null);
|
||||||
|
|
||||||
|
const { data: startupConfig } = useGetStartupConfig();
|
||||||
const { data: agents = null } = useListAgentsQuery(undefined, {
|
const { data: agents = null } = useListAgentsQuery(undefined, {
|
||||||
select: (res) => res.data.map((agent) => processAgentOption(agent /*, fileMap */)),
|
select: (res) =>
|
||||||
|
res.data.map((agent) =>
|
||||||
|
processAgentOption({
|
||||||
|
agent,
|
||||||
|
instanceProjectId: startupConfig?.instanceProjectId,
|
||||||
|
/* fileMap */
|
||||||
|
}),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const agentQuery = useGetAgentByIdQuery(selectedAgentId ?? '', {
|
const agentQuery = useGetAgentByIdQuery(selectedAgentId ?? '', {
|
||||||
|
|
@ -41,11 +50,15 @@ export default function AgentSelect({
|
||||||
|
|
||||||
const resetAgentForm = useCallback(
|
const resetAgentForm = useCallback(
|
||||||
(fullAgent: Agent) => {
|
(fullAgent: Agent) => {
|
||||||
|
const { instanceProjectId } = startupConfig ?? {};
|
||||||
|
const isGlobal =
|
||||||
|
(instanceProjectId != null && fullAgent.projectIds?.includes(instanceProjectId)) ?? false;
|
||||||
const update = {
|
const update = {
|
||||||
...fullAgent,
|
...fullAgent,
|
||||||
provider: createProviderOption(fullAgent.provider),
|
provider: createProviderOption(fullAgent.provider),
|
||||||
label: fullAgent.name ?? '',
|
label: fullAgent.name ?? '',
|
||||||
value: fullAgent.id || '',
|
value: fullAgent.id || '',
|
||||||
|
icon: isGlobal ? <EarthIcon className={'icon-lg text-green-400'} /> : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions: AgentCapabilities = {
|
const actions: AgentCapabilities = {
|
||||||
|
|
@ -138,6 +151,7 @@ export default function AgentSelect({
|
||||||
const hasAgentValue = !!(typeof currentAgentValue === 'object'
|
const hasAgentValue = !!(typeof currentAgentValue === 'object'
|
||||||
? currentAgentValue.value != null && currentAgentValue.value !== ''
|
? currentAgentValue.value != null && currentAgentValue.value !== ''
|
||||||
: typeof currentAgentValue !== 'undefined');
|
: typeof currentAgentValue !== 'undefined');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectDropDown
|
<SelectDropDown
|
||||||
value={!hasAgentValue ? createAgent : (currentAgentValue as TAgentOption)}
|
value={!hasAgentValue ? createAgent : (currentAgentValue as TAgentOption)}
|
||||||
|
|
@ -151,9 +165,11 @@ export default function AgentSelect({
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
iconSide="left"
|
iconSide="left"
|
||||||
|
optionIconSide="right"
|
||||||
showAbove={false}
|
showAbove={false}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
emptyTitle={true}
|
emptyTitle={true}
|
||||||
|
showOptionIcon={true}
|
||||||
containerClassName="flex-grow"
|
containerClassName="flex-grow"
|
||||||
searchClassName="dark:from-gray-850"
|
searchClassName="dark:from-gray-850"
|
||||||
searchPlaceholder={localize('com_agents_search_name')}
|
searchPlaceholder={localize('com_agents_search_name')}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ type SelectDropDownProps = {
|
||||||
showAbove?: boolean;
|
showAbove?: boolean;
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
iconSide?: 'left' | 'right';
|
iconSide?: 'left' | 'right';
|
||||||
|
optionIconSide?: 'left' | 'right';
|
||||||
renderOption?: () => React.ReactNode;
|
renderOption?: () => React.ReactNode;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
currentValueClass?: string;
|
currentValueClass?: string;
|
||||||
|
|
@ -43,12 +44,12 @@ function SelectDropDown({
|
||||||
value,
|
value,
|
||||||
disabled,
|
disabled,
|
||||||
setValue,
|
setValue,
|
||||||
tabIndex,
|
|
||||||
availableValues,
|
availableValues,
|
||||||
showAbove = false,
|
showAbove = false,
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
emptyTitle = false,
|
emptyTitle = false,
|
||||||
iconSide = 'right',
|
iconSide = 'right',
|
||||||
|
optionIconSide = 'left',
|
||||||
placeholder,
|
placeholder,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
optionsListClass,
|
optionsListClass,
|
||||||
|
|
@ -59,7 +60,7 @@ function SelectDropDown({
|
||||||
renderOption,
|
renderOption,
|
||||||
searchClassName,
|
searchClassName,
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
showOptionIcon,
|
showOptionIcon = false,
|
||||||
}: SelectDropDownProps) {
|
}: SelectDropDownProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const transitionProps = { className: 'top-full mt-3' };
|
const transitionProps = { className: 'top-full mt-3' };
|
||||||
|
|
@ -71,7 +72,7 @@ function SelectDropDown({
|
||||||
|
|
||||||
if (emptyTitle) {
|
if (emptyTitle) {
|
||||||
title = '';
|
title = '';
|
||||||
} else if (!title) {
|
} else if (!(title ?? '')) {
|
||||||
title = localize('com_ui_model');
|
title = localize('com_ui_model');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,11 +82,12 @@ function SelectDropDown({
|
||||||
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
|
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
|
||||||
availableOptions: availableValues,
|
availableOptions: availableValues,
|
||||||
placeholder: searchPlaceholder,
|
placeholder: searchPlaceholder,
|
||||||
getTextKeyOverride: (option) => ((option as Option)?.label || '').toUpperCase(),
|
getTextKeyOverride: (option) => ((option as Option).label ?? '').toUpperCase(),
|
||||||
className: searchClassName,
|
className: searchClassName,
|
||||||
});
|
});
|
||||||
const hasSearchRender = Boolean(searchRender);
|
const hasSearchRender = Boolean(searchRender);
|
||||||
const options = hasSearchRender ? filteredValues : availableValues;
|
const options = hasSearchRender ? filteredValues : availableValues;
|
||||||
|
const renderIcon = showOptionIcon && value != null && (value as OptionWithIcon).icon != null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center justify-center gap-2 ', containerClassName ?? '')}>
|
<div className={cn('flex items-center justify-center gap-2 ', containerClassName ?? '')}>
|
||||||
|
|
@ -121,20 +123,27 @@ function SelectDropDown({
|
||||||
{!showLabel && !emptyTitle && (
|
{!showLabel && !emptyTitle && (
|
||||||
<span className="text-xs text-gray-700 dark:text-gray-500">{title}:</span>
|
<span className="text-xs text-gray-700 dark:text-gray-500">{title}:</span>
|
||||||
)}
|
)}
|
||||||
{showOptionIcon && value && (value as OptionWithIcon)?.icon && (
|
{renderIcon && optionIconSide !== 'right' && (
|
||||||
<span className="icon-md flex items-center">
|
<span className="icon-md flex items-center">
|
||||||
{(value as OptionWithIcon).icon}
|
{(value as OptionWithIcon).icon}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{value ? (
|
{renderIcon && (
|
||||||
typeof value !== 'string' ? (
|
<span className="icon-md absolute right-0 mr-8 flex items-center">
|
||||||
value?.label ?? ''
|
{(value as OptionWithIcon).icon}
|
||||||
) : (
|
</span>
|
||||||
value
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-500 dark:text-gray-400">{placeholder}</span>
|
|
||||||
)}
|
)}
|
||||||
|
{(() => {
|
||||||
|
if (!value) {
|
||||||
|
return <span className="text-text-secondary">{placeholder}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return value.label ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
})()}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
|
@ -188,10 +197,10 @@ function SelectDropDown({
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLabel =
|
const currentLabel =
|
||||||
typeof option === 'string' ? option : option?.label ?? option?.value ?? '';
|
typeof option === 'string' ? option : option.label ?? option.value ?? '';
|
||||||
const currentValue = typeof option === 'string' ? option : option?.value ?? '';
|
const currentValue = typeof option === 'string' ? option : option.value ?? '';
|
||||||
const currentIcon =
|
const currentIcon =
|
||||||
typeof option === 'string' ? null : (option?.icon as React.ReactNode) ?? null;
|
typeof option === 'string' ? null : (option.icon as React.ReactNode) ?? null;
|
||||||
let activeValue: string | number | null | Option = value;
|
let activeValue: string | number | null | Option = value;
|
||||||
if (typeof activeValue !== 'string') {
|
if (typeof activeValue !== 'string') {
|
||||||
activeValue = activeValue?.value ?? '';
|
activeValue = activeValue?.value ?? '';
|
||||||
|
|
@ -217,7 +226,16 @@ function SelectDropDown({
|
||||||
iconSide === 'left' ? 'ml-4' : '',
|
iconSide === 'left' ? 'ml-4' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{currentIcon && <span className="mr-1">{currentIcon}</span>}
|
{currentIcon != null && (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'mr-1',
|
||||||
|
optionIconSide === 'right' ? 'absolute right-0 pr-2' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{currentIcon}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{currentLabel}
|
{currentLabel}
|
||||||
</span>
|
</span>
|
||||||
{currentValue === activeValue && (
|
{currentValue === activeValue && (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { EarthIcon } from 'lucide-react';
|
||||||
import { alternateName } from 'librechat-data-provider';
|
import { alternateName } from 'librechat-data-provider';
|
||||||
import type { Agent, TFile } from 'librechat-data-provider';
|
import type { Agent, TFile } from 'librechat-data-provider';
|
||||||
import type { DropdownValueSetter, TAgentOption } from '~/common';
|
import type { DropdownValueSetter, TAgentOption } from '~/common';
|
||||||
|
|
@ -39,14 +40,22 @@ export const createProviderOption = (provider: string) => ({
|
||||||
type FileTuple = [string, Partial<TFile>];
|
type FileTuple = [string, Partial<TFile>];
|
||||||
type FileList = Array<FileTuple>;
|
type FileList = Array<FileTuple>;
|
||||||
|
|
||||||
export const processAgentOption = (
|
export const processAgentOption = ({
|
||||||
_agent?: Agent,
|
agent: _agent,
|
||||||
fileMap?: Record<string, TFile>,
|
fileMap,
|
||||||
): TAgentOption => {
|
instanceProjectId,
|
||||||
|
}: {
|
||||||
|
agent?: Agent;
|
||||||
|
fileMap?: Record<string, TFile | undefined>;
|
||||||
|
instanceProjectId?: string;
|
||||||
|
}): TAgentOption => {
|
||||||
|
const isGlobal =
|
||||||
|
(instanceProjectId != null && _agent?.projectIds?.includes(instanceProjectId)) ?? false;
|
||||||
const agent: TAgentOption = {
|
const agent: TAgentOption = {
|
||||||
...(_agent ?? ({} as Agent)),
|
...(_agent ?? ({} as Agent)),
|
||||||
label: _agent?.name ?? '',
|
label: _agent?.name ?? '',
|
||||||
value: _agent?.id ?? '',
|
value: _agent?.id ?? '',
|
||||||
|
icon: isGlobal ? <EarthIcon className="icon-md text-green-400" /> : null,
|
||||||
// files: _agent?.file_ids ? ([] as FileList) : undefined,
|
// files: _agent?.file_ids ? ([] as FileList) : undefined,
|
||||||
// code_files: _agent?.tool_resources?.code_interpreter?.file_ids
|
// code_files: _agent?.tool_resources?.code_interpreter?.file_ids
|
||||||
// ? ([] as FileList)
|
// ? ([] as FileList)
|
||||||
|
|
@ -58,7 +67,7 @@ export const processAgentOption = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFile = (file_id: string, list?: FileList) => {
|
const handleFile = (file_id: string, list?: FileList) => {
|
||||||
const file = fileMap?.[file_id];
|
const file = fileMap[file_id];
|
||||||
if (file) {
|
if (file) {
|
||||||
list?.push([file_id, file]);
|
list?.push([file_id, file]);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -82,7 +91,7 @@ export const processAgentOption = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agent.code_files && _agent?.tool_resources?.code_interpreter?.file_ids) {
|
if (agent.code_files && _agent?.tool_resources?.code_interpreter?.file_ids) {
|
||||||
_agent.tool_resources?.code_interpreter?.file_ids?.forEach((file_id) =>
|
_agent.tool_resources.code_interpreter.file_ids.forEach((file_id) =>
|
||||||
handleFile(file_id, agent.code_files),
|
handleFile(file_id, agent.code_files),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue