import { Plus } from 'lucide-react'; import React, { useMemo, useCallback } from 'react'; import { useWatch, useForm, FormProvider } from 'react-hook-form'; import { useGetModelsQuery } from 'librechat-data-provider/react-query'; import { Tools, Constants, SystemRoles, EModelEndpoint, TAgentsEndpoint, PERMISSION_BITS, TEndpointsConfig, isAssistantsEndpoint, } from 'librechat-data-provider'; import type { AgentForm, StringOption } from '~/common'; import { useCreateAgentMutation, useUpdateAgentMutation, useGetAgentByIdQuery, useGetExpandedAgentByIdQuery, } from '~/data-provider'; import { createProviderOption, getDefaultAgentFormValues } from '~/utils'; import { useResourcePermissions } from '~/hooks/useResourcePermissions'; import { useSelectAgent, useLocalize, useAuthContext } from '~/hooks'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import AgentPanelSkeleton from './AgentPanelSkeleton'; import AdvancedPanel from './Advanced/AdvancedPanel'; import { useToastContext } from '~/Providers'; import AgentConfig from './AgentConfig'; import AgentSelect from './AgentSelect'; import AgentFooter from './AgentFooter'; import { Button } from '~/components'; import ModelPanel from './ModelPanel'; import { Panel } from '~/common'; export default function AgentPanel({ agentsConfig, endpointsConfig, }: { agentsConfig: TAgentsEndpoint | null; endpointsConfig: TEndpointsConfig; }) { const localize = useLocalize(); const { user } = useAuthContext(); const { showToast } = useToastContext(); const { activePanel, setActivePanel, setCurrentAgentId, agent_id: current_agent_id, } = useAgentPanelContext(); const { onSelect: onSelectAgent } = useSelectAgent(); const modelsQuery = useGetModelsQuery(); // Basic agent query for initial permission check const basicAgentQuery = useGetAgentByIdQuery(current_agent_id ?? '', { enabled: !!(current_agent_id ?? '') && current_agent_id !== Constants.EPHEMERAL_AGENT_ID, }); const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions( 'agent', basicAgentQuery.data?._id || '', ); const canEdit = hasPermission(PERMISSION_BITS.EDIT); const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', { enabled: !!(current_agent_id ?? '') && current_agent_id !== Constants.EPHEMERAL_AGENT_ID && canEdit && !permissionsLoading, }); const agentQuery = canEdit && expandedAgentQuery.data ? expandedAgentQuery : basicAgentQuery; const models = useMemo(() => modelsQuery.data ?? {}, [modelsQuery.data]); const methods = useForm({ defaultValues: getDefaultAgentFormValues(), }); const { control, handleSubmit, reset } = methods; const agent_id = useWatch({ control, name: 'id' }); const allowedProviders = useMemo( () => new Set(agentsConfig?.allowedProviders), [agentsConfig?.allowedProviders], ); const providers = useMemo( () => Object.keys(endpointsConfig ?? {}) .filter( (key) => !isAssistantsEndpoint(key) && (allowedProviders.size > 0 ? allowedProviders.has(key) : true) && key !== EModelEndpoint.agents && key !== EModelEndpoint.chatGPTBrowser && key !== EModelEndpoint.gptPlugins, ) .map((provider) => createProviderOption(provider)), [endpointsConfig, allowedProviders], ); /* Mutations */ const update = useUpdateAgentMutation({ onSuccess: (data) => { showToast({ message: `${localize('com_assistants_update_success')} ${ data.name ?? localize('com_ui_agent') }`, }); }, onError: (err) => { const error = err as Error & { statusCode?: number; details?: { duplicateVersion?: any; versionIndex?: number }; response?: { status?: number; data?: any }; }; const isDuplicateVersionError = (error.statusCode === 409 && error.details?.duplicateVersion) || (error.response?.status === 409 && error.response?.data?.details?.duplicateVersion); if (isDuplicateVersionError) { let versionIndex: number | undefined = undefined; if (error.details?.versionIndex !== undefined) { versionIndex = error.details.versionIndex; } else if (error.response?.data?.details?.versionIndex !== undefined) { versionIndex = error.response.data.details.versionIndex; } if (versionIndex === undefined || versionIndex < 0) { showToast({ message: localize('com_agents_update_error'), status: 'error', duration: 5000, }); } else { showToast({ message: localize('com_ui_agent_version_duplicate', { versionIndex: versionIndex + 1 }), status: 'error', duration: 10000, }); } return; } showToast({ message: `${localize('com_agents_update_error')}${ error.message ? ` ${localize('com_ui_error')}: ${error.message}` : '' }`, status: 'error', }); }, }); const create = useCreateAgentMutation({ onSuccess: (data) => { setCurrentAgentId(data.id); showToast({ message: `${localize('com_assistants_create_success')} ${ data.name ?? localize('com_ui_agent') }`, }); }, onError: (err) => { const error = err as Error; showToast({ message: `${localize('com_agents_create_error')}${ error.message ? ` ${localize('com_ui_error')}: ${error.message}` : '' }`, status: 'error', }); }, }); const onSubmit = useCallback( (data: AgentForm) => { const tools = data.tools ?? []; if (data.execute_code === true) { tools.push(Tools.execute_code); } if (data.file_search === true) { tools.push(Tools.file_search); } if (data.web_search === true) { tools.push(Tools.web_search); } const { name, artifacts, description, instructions, model: _model, model_parameters, provider: _provider, agent_ids, end_after_tools, hide_sequential_outputs, recursion_limit, category, support_contact, } = data; const model = _model ?? ''; const provider = (typeof _provider === 'string' ? _provider : (_provider as StringOption).value) ?? ''; if (agent_id) { update.mutate({ agent_id, data: { name, artifacts, description, instructions, model, tools, provider, model_parameters, agent_ids, end_after_tools, hide_sequential_outputs, recursion_limit, category, support_contact, }, }); return; } if (!provider || !model) { return showToast({ message: localize('com_agents_missing_provider_model'), status: 'error', }); } if (!name) { return showToast({ message: localize('com_agents_missing_name'), status: 'error', }); } create.mutate({ name, artifacts, description, instructions, model, tools, provider, model_parameters, agent_ids, end_after_tools, hide_sequential_outputs, recursion_limit, category, support_contact, }); }, [agent_id, create, update, showToast, localize], ); const handleSelectAgent = useCallback(() => { if (agent_id) { onSelectAgent(agent_id); } }, [agent_id, onSelectAgent]); const canEditAgent = useMemo(() => { if (!agentQuery.data?.id) { return true; } if (user?.role === SystemRoles.ADMIN) { return true; } return canEdit; }, [agentQuery.data?.id, user?.role, canEdit]); return (
{/* Create + Select Button */} {agent_id && (
)}
{agentQuery.isInitialLoading && } {!canEditAgent && !agentQuery.isInitialLoading && (

{localize('com_agents_not_available')}

{localize('com_agents_no_access')}

)} {canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && ( )} {canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && ( )} {canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && ( )} {canEditAgent && !agentQuery.isInitialLoading && ( )}
); }