Skip to content

Commit 4419e2c

Browse files
✨ feat: Agent Panel UI Enhancements (danny-avila#7800)
* feat: add MCP Panel to Agent Builder - Add MCP server panel and configuration UI - Implement MCP input forms and tool lists - Add MCP icon and metadata support - Integrate MCP with agent configuration - Add localization support for MCP features - Refactor components for better reusability - Update types and add MCP-related mutations - Fix small issues with Actions and AgentSelect - Refactor AgentPanelSwitch and related components to use new AgentPanelContext to reduce prop drilling * chore: import order * chore: clean up import statements and unused var in ActionsPanel component * refactor: AgentPanelContext with actions query, remove unnecessary `actions` state - Added actions query using `useGetActionsQuery` to fetch actions based on the current agent ID. - Removed now unused `setActions` state and related logic from `AgentPanelContext` and `AgentPanelSwitch` components. - Updated `AgentPanelContextType` to reflect the removal of `setActions`. * chore: re-order import statements in AgentConfig component * chore: re-order import statements in ModelPanel component * chore: update ModelPanel props to consolidated props to avoid passing unnecessary props * chore: update import statements in Providers index file to include ToastProvider and AgentPanelContext exports * chore: clean up import statements in VersionPanel component * refactor: streamline AgentConfig and AgentPanel components - Consolidated props in AgentConfig to only include necessary fields. - Updated AgentPanel to remove unused state and props, enhancing clarity and maintainability. - Reorganized import statements for better structure and readability. * refactor: replace default agent form values with utility function - Updated AgentsProvider, AgentPanel, AgentSelect, and DeleteButton components to use getDefaultAgentFormValues utility function instead of directly importing defaultAgentFormValues. - Enhanced the initialization of agent forms by incorporating localStorage values for model and provider in the new utility function. * chore: comment out rendering MCPSection --------- Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com>
1 parent 5f2d1c5 commit 4419e2c

27 files changed

Lines changed: 1026 additions & 135 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { createContext, useContext, useState } from 'react';
2+
import { Action, MCP, EModelEndpoint } from 'librechat-data-provider';
3+
import type { AgentPanelContextType } from '~/common';
4+
import { useGetActionsQuery } from '~/data-provider';
5+
import { Panel } from '~/common';
6+
7+
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
8+
9+
export function useAgentPanelContext() {
10+
const context = useContext(AgentPanelContext);
11+
if (context === undefined) {
12+
throw new Error('useAgentPanelContext must be used within an AgentPanelProvider');
13+
}
14+
return context;
15+
}
16+
17+
/** Houses relevant state for the Agent Form Panels (formerly 'commonProps') */
18+
export function AgentPanelProvider({ children }: { children: React.ReactNode }) {
19+
const [mcp, setMcp] = useState<MCP | undefined>(undefined);
20+
const [mcps, setMcps] = useState<MCP[] | undefined>(undefined);
21+
const [action, setAction] = useState<Action | undefined>(undefined);
22+
const [activePanel, setActivePanel] = useState<Panel>(Panel.builder);
23+
const [agent_id, setCurrentAgentId] = useState<string | undefined>(undefined);
24+
25+
const { data: actions } = useGetActionsQuery(EModelEndpoint.agents, {
26+
enabled: !!agent_id,
27+
});
28+
29+
const value = {
30+
action,
31+
setAction,
32+
mcp,
33+
setMcp,
34+
mcps,
35+
setMcps,
36+
activePanel,
37+
setActivePanel,
38+
setCurrentAgentId,
39+
agent_id,
40+
/** Query data for actions */
41+
actions,
42+
};
43+
44+
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;
45+
}

client/src/Providers/AgentsContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useForm, FormProvider } from 'react-hook-form';
22
import { createContext, useContext } from 'react';
3-
import { defaultAgentFormValues } from 'librechat-data-provider';
43
import type { UseFormReturn } from 'react-hook-form';
54
import type { AgentForm } from '~/common';
5+
import { getDefaultAgentFormValues } from '~/utils';
66

77
type AgentsContextType = UseFormReturn<AgentForm>;
88

@@ -20,7 +20,7 @@ export function useAgentsContext() {
2020

2121
export default function AgentsProvider({ children }) {
2222
const methods = useForm<AgentForm>({
23-
defaultValues: defaultAgentFormValues,
23+
defaultValues: getDefaultAgentFormValues(),
2424
});
2525

2626
return <FormProvider {...methods}>{children}</FormProvider>;

client/src/Providers/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
export { default as ToastProvider } from './ToastContext';
21
export { default as AssistantsProvider } from './AssistantsContext';
32
export { default as AgentsProvider } from './AgentsContext';
3+
export { default as ToastProvider } from './ToastContext';
4+
export * from './AgentPanelContext';
45
export * from './ChatContext';
56
export * from './ShareContext';
67
export * from './ToastContext';

client/src/common/mcp.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
AuthorizationTypeEnum,
3+
AuthTypeEnum,
4+
TokenExchangeMethodEnum,
5+
} from 'librechat-data-provider';
6+
import { MCPForm } from '~/common/types';
7+
8+
export const defaultMCPFormValues: MCPForm = {
9+
type: AuthTypeEnum.None,
10+
saved_auth_fields: false,
11+
api_key: '',
12+
authorization_type: AuthorizationTypeEnum.Basic,
13+
custom_auth_header: '',
14+
oauth_client_id: '',
15+
oauth_client_secret: '',
16+
authorization_url: '',
17+
client_url: '',
18+
scope: '',
19+
token_exchange_method: TokenExchangeMethodEnum.DefaultPost,
20+
name: '',
21+
description: '',
22+
url: '',
23+
tools: [],
24+
icon: '',
25+
trust: false,
26+
};

client/src/common/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export enum Panel {
143143
actions = 'actions',
144144
model = 'model',
145145
version = 'version',
146+
mcp = 'mcp',
146147
}
147148

148149
export type FileSetter =
@@ -166,6 +167,15 @@ export type ActionAuthForm = {
166167
token_exchange_method: t.TokenExchangeMethodEnum;
167168
};
168169

170+
export type MCPForm = ActionAuthForm & {
171+
name?: string;
172+
description?: string;
173+
url?: string;
174+
tools?: string[];
175+
icon?: string;
176+
trust?: boolean;
177+
};
178+
169179
export type ActionWithNullableMetadata = Omit<t.Action, 'metadata'> & {
170180
metadata: t.ActionMetadata | null;
171181
};
@@ -188,16 +198,33 @@ export type AgentPanelProps = {
188198
index?: number;
189199
agent_id?: string;
190200
activePanel?: string;
201+
mcp?: t.MCP;
202+
mcps?: t.MCP[];
191203
action?: t.Action;
192204
actions?: t.Action[];
193205
createMutation: UseMutationResult<t.Agent, Error, t.AgentCreateParams>;
194206
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
207+
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
195208
setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>;
196209
endpointsConfig?: t.TEndpointsConfig;
197210
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
198211
agentsConfig?: t.TAgentsEndpoint | null;
199212
};
200213

214+
export type AgentPanelContextType = {
215+
action?: t.Action;
216+
actions?: t.Action[];
217+
setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>;
218+
mcp?: t.MCP;
219+
mcps?: t.MCP[];
220+
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
221+
setMcps: React.Dispatch<React.SetStateAction<t.MCP[] | undefined>>;
222+
activePanel?: string;
223+
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
224+
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
225+
agent_id?: string;
226+
};
227+
201228
export type AgentModelPanelProps = {
202229
agent_id?: string;
203230
providers: Option[];

client/src/components/SidePanel/Agents/ActionsPanel.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
import { useEffect } from 'react';
2+
import { ChevronLeft } from 'lucide-react';
23
import { useForm, FormProvider } from 'react-hook-form';
34
import {
45
AuthTypeEnum,
56
AuthorizationTypeEnum,
67
TokenExchangeMethodEnum,
78
} from 'librechat-data-provider';
8-
import { ChevronLeft } from 'lucide-react';
9-
import type { AgentPanelProps, ActionAuthForm } from '~/common';
109
import ActionsAuth from '~/components/SidePanel/Builder/ActionsAuth';
10+
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
1111
import { OGDialog, OGDialogTrigger, Label } from '~/components/ui';
1212
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
1313
import { useDeleteAgentAction } from '~/data-provider';
14+
import type { ActionAuthForm } from '~/common';
1415
import useLocalize from '~/hooks/useLocalize';
1516
import { useToastContext } from '~/Providers';
1617
import { TrashIcon } from '~/components/svg';
1718
import ActionsInput from './ActionsInput';
1819
import { Panel } from '~/common';
1920

20-
export default function ActionsPanel({
21-
// activePanel,
22-
action,
23-
setAction,
24-
agent_id,
25-
setActivePanel,
26-
}: AgentPanelProps) {
21+
export default function ActionsPanel() {
2722
const localize = useLocalize();
2823
const { showToast } = useToastContext();
24+
const { setActivePanel, action, setAction, agent_id } = useAgentPanelContext();
2925
const deleteAgentAction = useDeleteAgentAction({
3026
onSuccess: () => {
3127
showToast({
@@ -62,7 +58,7 @@ export default function ActionsPanel({
6258
},
6359
});
6460

65-
const { reset, watch } = methods;
61+
const { reset } = methods;
6662

6763
useEffect(() => {
6864
if (action?.metadata.auth) {

client/src/components/SidePanel/Agents/AgentConfig.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useState, useMemo, useCallback } from 'react';
21
import { useQueryClient } from '@tanstack/react-query';
2+
import React, { useState, useMemo, useCallback } from 'react';
33
import { Controller, useWatch, useFormContext } from 'react-hook-form';
44
import { QueryKeys, EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
55
import type { TPlugin } from 'librechat-data-provider';
6-
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
76
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
8-
import { useToastContext, useFileMapContext } from '~/Providers';
7+
import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers';
8+
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
99
import Action from '~/components/SidePanel/Builder/Action';
1010
import { ToolSelectDialog } from '~/components/Tools';
1111
import { icons } from '~/hooks/Endpoint/Icons';
@@ -15,6 +15,7 @@ import AgentAvatar from './AgentAvatar';
1515
import FileContext from './FileContext';
1616
import SearchForm from './Search/Form';
1717
import { useLocalize } from '~/hooks';
18+
import MCPSection from './MCPSection';
1819
import FileSearch from './FileSearch';
1920
import Artifacts from './Artifacts';
2021
import AgentTool from './AgentTool';
@@ -29,23 +30,19 @@ const inputClass = cn(
2930
);
3031

3132
export default function AgentConfig({
32-
setAction,
33-
actions = [],
3433
agentsConfig,
3534
createMutation,
36-
setActivePanel,
3735
endpointsConfig,
38-
}: AgentPanelProps) {
36+
}: Pick<AgentPanelProps, 'agentsConfig' | 'createMutation' | 'endpointsConfig'>) {
37+
const localize = useLocalize();
3938
const fileMap = useFileMapContext();
4039
const queryClient = useQueryClient();
41-
42-
const allTools = queryClient.getQueryData<TPlugin[]>([QueryKeys.tools]) ?? [];
4340
const { showToast } = useToastContext();
44-
const localize = useLocalize();
45-
41+
const methods = useFormContext<AgentForm>();
4642
const [showToolDialog, setShowToolDialog] = useState(false);
43+
const { actions, setAction, setActivePanel } = useAgentPanelContext();
4744

48-
const methods = useFormContext<AgentForm>();
45+
const allTools = queryClient.getQueryData<TPlugin[]>([QueryKeys.tools]) ?? [];
4946

5047
const { control } = methods;
5148
const provider = useWatch({ control, name: 'provider' });
@@ -299,7 +296,7 @@ export default function AgentConfig({
299296
agent_id={agent_id}
300297
/>
301298
))}
302-
{actions
299+
{(actions ?? [])
303300
.filter((action) => action.agent_id === agent_id)
304301
.map((action, i) => (
305302
<Action
@@ -340,6 +337,8 @@ export default function AgentConfig({
340337
</div>
341338
</div>
342339
</div>
340+
{/* MCP Section */}
341+
{/* <MCPSection /> */}
343342
</div>
344343
<ToolSelectDialog
345344
isOpen={showToolDialog}

client/src/components/SidePanel/Agents/AgentPanel.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@ import {
77
Constants,
88
SystemRoles,
99
EModelEndpoint,
10+
TAgentsEndpoint,
11+
TEndpointsConfig,
1012
isAssistantsEndpoint,
11-
defaultAgentFormValues,
1213
} from 'librechat-data-provider';
13-
import type { AgentForm, AgentPanelProps, StringOption } from '~/common';
14+
import type { AgentForm, StringOption } from '~/common';
1415
import {
1516
useCreateAgentMutation,
1617
useUpdateAgentMutation,
1718
useGetAgentByIdQuery,
1819
} from '~/data-provider';
20+
import { createProviderOption, getDefaultAgentFormValues } from '~/utils';
1921
import { useSelectAgent, useLocalize, useAuthContext } from '~/hooks';
22+
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
2023
import AgentPanelSkeleton from './AgentPanelSkeleton';
21-
import { createProviderOption } from '~/utils';
22-
import { useToastContext } from '~/Providers';
2324
import AdvancedPanel from './Advanced/AdvancedPanel';
25+
import { useToastContext } from '~/Providers';
2426
import AgentConfig from './AgentConfig';
2527
import AgentSelect from './AgentSelect';
2628
import AgentFooter from './AgentFooter';
@@ -29,18 +31,21 @@ import ModelPanel from './ModelPanel';
2931
import { Panel } from '~/common';
3032

3133
export default function AgentPanel({
32-
setAction,
33-
activePanel,
34-
actions = [],
35-
setActivePanel,
36-
agent_id: current_agent_id,
37-
setCurrentAgentId,
3834
agentsConfig,
3935
endpointsConfig,
40-
}: AgentPanelProps) {
36+
}: {
37+
agentsConfig: TAgentsEndpoint | null;
38+
endpointsConfig: TEndpointsConfig;
39+
}) {
4140
const localize = useLocalize();
4241
const { user } = useAuthContext();
4342
const { showToast } = useToastContext();
43+
const {
44+
activePanel,
45+
setActivePanel,
46+
setCurrentAgentId,
47+
agent_id: current_agent_id,
48+
} = useAgentPanelContext();
4449

4550
const { onSelect: onSelectAgent } = useSelectAgent();
4651

@@ -51,7 +56,7 @@ export default function AgentPanel({
5156

5257
const models = useMemo(() => modelsQuery.data ?? {}, [modelsQuery.data]);
5358
const methods = useForm<AgentForm>({
54-
defaultValues: defaultAgentFormValues,
59+
defaultValues: getDefaultAgentFormValues(),
5560
});
5661

5762
const { control, handleSubmit, reset } = methods;
@@ -277,7 +282,7 @@ export default function AgentPanel({
277282
variant="outline"
278283
className="w-full justify-center"
279284
onClick={() => {
280-
reset(defaultAgentFormValues);
285+
reset(getDefaultAgentFormValues());
281286
setCurrentAgentId(undefined);
282287
}}
283288
disabled={agentQuery.isInitialLoading}
@@ -315,22 +320,13 @@ export default function AgentPanel({
315320
</div>
316321
)}
317322
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && (
318-
<ModelPanel
319-
setActivePanel={setActivePanel}
320-
agent_id={agent_id}
321-
providers={providers}
322-
models={models}
323-
/>
323+
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
324324
)}
325325
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
326326
<AgentConfig
327-
actions={actions}
328-
setAction={setAction}
329327
createMutation={create}
330328
agentsConfig={agentsConfig}
331-
setActivePanel={setActivePanel}
332329
endpointsConfig={endpointsConfig}
333-
setCurrentAgentId={setCurrentAgentId}
334330
/>
335331
)}
336332
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (

0 commit comments

Comments
 (0)