feat: visual indicator for global agent, remove author when serving to non-author

This commit is contained in:
Danny Avila 2024-09-04 21:02:01 -04:00
parent 1bc0689134
commit 2c6874013a
No known key found for this signature in database
GPG key ID: 2DD9CC89B9B50364
6 changed files with 77 additions and 29 deletions

View file

@ -83,6 +83,7 @@ const getListAgents = async (searchParameter) => {
id: 1,
name: 1,
avatar: 1,
projectIds: 1,
}).lean();
const hasMore = agents.length > 0;

View file

@ -80,6 +80,10 @@ const getAgentHandler = async (req, res) => {
return res.status(404).json({ error: 'Agent not found' });
}
if (agent.author !== author) {
delete agent.author;
}
return res.status(200).json(agent);
} catch (error) {
logger.error('[/Agents/:id] Error retrieving agent', error);

View file

@ -1,8 +1,8 @@
import { Capabilities } 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 & {
files?: Array<[string, ExtendedFile]>;
code_files?: Array<[string, ExtendedFile]>;
@ -23,5 +23,5 @@ export type AgentForm = {
model: string | null;
model_parameters: AgentModelParameters;
tools?: string[];
provider?: AgentProvider | Option;
provider?: AgentProvider | OptionWithIcon;
} & AgentCapabilities;

View file

@ -1,10 +1,11 @@
import { Plus } from 'lucide-react';
import { Plus, EarthIcon } from 'lucide-react';
import { useCallback, useEffect, useRef } from 'react';
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
import { Capabilities, defaultAgentFormValues } from 'librechat-data-provider';
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
import type { UseFormReset } from 'react-hook-form';
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
import { cn, createDropdownSetter, createProviderOption, processAgentOption } from '~/utils';
import { useListAgentsQuery, useGetAgentByIdQuery } from '~/data-provider';
import SelectDropDown from '~/components/ui/SelectDropDown';
@ -31,8 +32,16 @@ export default function AgentSelect({
// const fileMap = useFileMapContext();
const lastSelectedAgent = useRef<string | null>(null);
const { data: startupConfig } = useGetStartupConfig();
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 ?? '', {
@ -41,11 +50,15 @@ export default function AgentSelect({
const resetAgentForm = useCallback(
(fullAgent: Agent) => {
const { instanceProjectId } = startupConfig ?? {};
const isGlobal =
(instanceProjectId != null && fullAgent.projectIds?.includes(instanceProjectId)) ?? false;
const update = {
...fullAgent,
provider: createProviderOption(fullAgent.provider),
label: fullAgent.name ?? '',
value: fullAgent.id || '',
icon: isGlobal ? <EarthIcon className={'icon-lg text-green-400'} /> : null,
};
const actions: AgentCapabilities = {
@ -138,6 +151,7 @@ export default function AgentSelect({
const hasAgentValue = !!(typeof currentAgentValue === 'object'
? currentAgentValue.value != null && currentAgentValue.value !== ''
: typeof currentAgentValue !== 'undefined');
return (
<SelectDropDown
value={!hasAgentValue ? createAgent : (currentAgentValue as TAgentOption)}
@ -151,9 +165,11 @@ export default function AgentSelect({
]
}
iconSide="left"
optionIconSide="right"
showAbove={false}
showLabel={false}
emptyTitle={true}
showOptionIcon={true}
containerClassName="flex-grow"
searchClassName="dark:from-gray-850"
searchPlaceholder={localize('com_agents_search_name')}

View file

@ -25,6 +25,7 @@ type SelectDropDownProps = {
showAbove?: boolean;
showLabel?: boolean;
iconSide?: 'left' | 'right';
optionIconSide?: 'left' | 'right';
renderOption?: () => React.ReactNode;
containerClassName?: string;
currentValueClass?: string;
@ -43,12 +44,12 @@ function SelectDropDown({
value,
disabled,
setValue,
tabIndex,
availableValues,
showAbove = false,
showLabel = true,
emptyTitle = false,
iconSide = 'right',
optionIconSide = 'left',
placeholder,
containerClassName,
optionsListClass,
@ -59,7 +60,7 @@ function SelectDropDown({
renderOption,
searchClassName,
searchPlaceholder,
showOptionIcon,
showOptionIcon = false,
}: SelectDropDownProps) {
const localize = useLocalize();
const transitionProps = { className: 'top-full mt-3' };
@ -71,7 +72,7 @@ function SelectDropDown({
if (emptyTitle) {
title = '';
} else if (!title) {
} else if (!(title ?? '')) {
title = localize('com_ui_model');
}
@ -81,11 +82,12 @@ function SelectDropDown({
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
availableOptions: availableValues,
placeholder: searchPlaceholder,
getTextKeyOverride: (option) => ((option as Option)?.label || '').toUpperCase(),
getTextKeyOverride: (option) => ((option as Option).label ?? '').toUpperCase(),
className: searchClassName,
});
const hasSearchRender = Boolean(searchRender);
const options = hasSearchRender ? filteredValues : availableValues;
const renderIcon = showOptionIcon && value != null && (value as OptionWithIcon).icon != null;
return (
<div className={cn('flex items-center justify-center gap-2 ', containerClassName ?? '')}>
@ -121,20 +123,27 @@ function SelectDropDown({
{!showLabel && !emptyTitle && (
<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">
{(value as OptionWithIcon).icon}
</span>
)}
{value ? (
typeof value !== 'string' ? (
value?.label ?? ''
) : (
value
)
) : (
<span className="text-gray-500 dark:text-gray-400">{placeholder}</span>
{renderIcon && (
<span className="icon-md absolute right-0 mr-8 flex items-center">
{(value as OptionWithIcon).icon}
</span>
)}
{(() => {
if (!value) {
return <span className="text-text-secondary">{placeholder}</span>;
}
if (typeof value !== 'string') {
return value.label ?? '';
}
return value;
})()}
</span>
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
@ -188,10 +197,10 @@ function SelectDropDown({
}
const currentLabel =
typeof option === 'string' ? option : option?.label ?? option?.value ?? '';
const currentValue = typeof option === 'string' ? option : option?.value ?? '';
typeof option === 'string' ? option : option.label ?? option.value ?? '';
const currentValue = typeof option === 'string' ? option : option.value ?? '';
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;
if (typeof activeValue !== 'string') {
activeValue = activeValue?.value ?? '';
@ -217,7 +226,16 @@ function SelectDropDown({
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}
</span>
{currentValue === activeValue && (

View file

@ -1,3 +1,4 @@
import { EarthIcon } from 'lucide-react';
import { alternateName } from 'librechat-data-provider';
import type { Agent, TFile } from 'librechat-data-provider';
import type { DropdownValueSetter, TAgentOption } from '~/common';
@ -39,14 +40,22 @@ export const createProviderOption = (provider: string) => ({
type FileTuple = [string, Partial<TFile>];
type FileList = Array<FileTuple>;
export const processAgentOption = (
_agent?: Agent,
fileMap?: Record<string, TFile>,
): TAgentOption => {
export const processAgentOption = ({
agent: _agent,
fileMap,
instanceProjectId,
}: {
agent?: Agent;
fileMap?: Record<string, TFile | undefined>;
instanceProjectId?: string;
}): TAgentOption => {
const isGlobal =
(instanceProjectId != null && _agent?.projectIds?.includes(instanceProjectId)) ?? false;
const agent: TAgentOption = {
...(_agent ?? ({} as Agent)),
label: _agent?.name ?? '',
value: _agent?.id ?? '',
icon: isGlobal ? <EarthIcon className="icon-md text-green-400" /> : null,
// files: _agent?.file_ids ? ([] as FileList) : undefined,
// code_files: _agent?.tool_resources?.code_interpreter?.file_ids
// ? ([] as FileList)
@ -58,7 +67,7 @@ export const processAgentOption = (
}
const handleFile = (file_id: string, list?: FileList) => {
const file = fileMap?.[file_id];
const file = fileMap[file_id];
if (file) {
list?.push([file_id, file]);
} else {
@ -82,7 +91,7 @@ export const processAgentOption = (
}
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),
);
}