mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +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,
|
||||
name: 1,
|
||||
avatar: 1,
|
||||
projectIds: 1,
|
||||
}).lean();
|
||||
|
||||
const hasMore = agents.length > 0;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue