Skip to content

Commit 9f6bef2

Browse files
dustinhealygithub-advanced-security[bot]danny-avila
authored
✨ feat: Enhance Agent Panel with Tool Grouping (danny-avila#7951)
* ✨ feat: Enhance Agent Panel with Tool Grouping * 🧰 feat: Added support for grouping tools in the Agent Panel, allowing for better organization and management of related tools. * 💡 feat: Added hovercards for tools belonging to a group which display their tool descriptions when their help icon is hovered over. * 🧹 chore: Updated the AgentPanelContext to include grouped tools and their metadata. * 🔨 refactor: Refactored AgentConfig and AgentTool components to utilize the new tool structure and enhance rendering logic. * 🔍 feat: Improved the ToolSelectDialog to filter and display tools based on user input, including searching for tools within a group, and limits viewport height to prevent overflowing vertically on smaller screens. This update enhances the overall functionality and usability of the Agent Panel, making it easier for users to interact with tools. * Potential fix for code scanning alert no. 6217: Disallow unused variables Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix: Agent tool type mismatches * fix: accessibility issues and mcp tool overflow issue * fix: enhance keyboard accessibility and prevent event propagation in AgentTool * chore: WIP types * chore: address comments and fix accordian collapse bug --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Danny Avila <[email protected]>
1 parent 4c68074 commit 9f6bef2

File tree

13 files changed

+600
-193
lines changed

13 files changed

+600
-193
lines changed

client/src/Providers/AgentPanelContext.tsx

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { createContext, useContext, useState } from 'react';
2-
import { Action, MCP, EModelEndpoint } from 'librechat-data-provider';
2+
import { Constants, EModelEndpoint } from 'librechat-data-provider';
3+
import type { TPlugin, AgentToolType, Action, MCP } from 'librechat-data-provider';
34
import type { AgentPanelContextType } from '~/common';
4-
import { useGetActionsQuery } from '~/data-provider';
5+
import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider';
6+
import { useLocalize } from '~/hooks';
57
import { Panel } from '~/common';
68

79
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
@@ -16,6 +18,7 @@ export function useAgentPanelContext() {
1618

1719
/** Houses relevant state for the Agent Form Panels (formerly 'commonProps') */
1820
export function AgentPanelProvider({ children }: { children: React.ReactNode }) {
21+
const localize = useLocalize();
1922
const [mcp, setMcp] = useState<MCP | undefined>(undefined);
2023
const [mcps, setMcps] = useState<MCP[] | undefined>(undefined);
2124
const [action, setAction] = useState<Action | undefined>(undefined);
@@ -26,6 +29,53 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
2629
enabled: !!agent_id,
2730
});
2831

32+
const { data: pluginTools } = useAvailableToolsQuery(EModelEndpoint.agents, {
33+
enabled: !!agent_id,
34+
});
35+
36+
const tools =
37+
pluginTools?.map((tool) => ({
38+
tool_id: tool.pluginKey,
39+
metadata: tool as TPlugin,
40+
agent_id: agent_id || '',
41+
})) || [];
42+
43+
const groupedTools =
44+
tools?.reduce(
45+
(acc, tool) => {
46+
if (tool.tool_id.includes(Constants.mcp_delimiter)) {
47+
const [_toolName, serverName] = tool.tool_id.split(Constants.mcp_delimiter);
48+
const groupKey = `${serverName.toLowerCase()}`;
49+
if (!acc[groupKey]) {
50+
acc[groupKey] = {
51+
tool_id: groupKey,
52+
metadata: {
53+
name: `${serverName}`,
54+
pluginKey: groupKey,
55+
description: `${localize('com_ui_tool_collection_prefix')} ${serverName}`,
56+
icon: tool.metadata.icon || '',
57+
} as TPlugin,
58+
agent_id: agent_id || '',
59+
tools: [],
60+
};
61+
}
62+
acc[groupKey].tools?.push({
63+
tool_id: tool.tool_id,
64+
metadata: tool.metadata,
65+
agent_id: agent_id || '',
66+
});
67+
} else {
68+
acc[tool.tool_id] = {
69+
tool_id: tool.tool_id,
70+
metadata: tool.metadata,
71+
agent_id: agent_id || '',
72+
};
73+
}
74+
return acc;
75+
},
76+
{} as Record<string, AgentToolType & { tools?: AgentToolType[] }>,
77+
) || {};
78+
2979
const value = {
3080
action,
3181
setAction,
@@ -37,8 +87,10 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
3787
setActivePanel,
3888
setCurrentAgentId,
3989
agent_id,
40-
/** Query data for actions */
90+
groupedTools,
91+
/** Query data for actions and tools */
4192
actions,
93+
tools,
4294
};
4395

4496
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;

client/src/common/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ export type AgentPanelContextType = {
219219
mcps?: t.MCP[];
220220
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
221221
setMcps: React.Dispatch<React.SetStateAction<t.MCP[] | undefined>>;
222+
groupedTools: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>;
223+
tools: t.AgentToolType[];
222224
activePanel?: string;
223225
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
224226
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;

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

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { useQueryClient } from '@tanstack/react-query';
21
import React, { useState, useMemo, useCallback } from 'react';
32
import { Controller, useWatch, useFormContext } from 'react-hook-form';
4-
import { QueryKeys, EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
5-
import type { TPlugin } from 'librechat-data-provider';
3+
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
4+
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
65
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
76
import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers';
8-
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
97
import Action from '~/components/SidePanel/Builder/Action';
108
import { ToolSelectDialog } from '~/components/Tools';
119
import { icons } from '~/hooks/Endpoint/Icons';
@@ -15,7 +13,6 @@ import AgentAvatar from './AgentAvatar';
1513
import FileContext from './FileContext';
1614
import SearchForm from './Search/Form';
1715
import { useLocalize } from '~/hooks';
18-
import MCPSection from './MCPSection';
1916
import FileSearch from './FileSearch';
2017
import Artifacts from './Artifacts';
2118
import AgentTool from './AgentTool';
@@ -36,13 +33,10 @@ export default function AgentConfig({
3633
}: Pick<AgentPanelProps, 'agentsConfig' | 'createMutation' | 'endpointsConfig'>) {
3734
const localize = useLocalize();
3835
const fileMap = useFileMapContext();
39-
const queryClient = useQueryClient();
4036
const { showToast } = useToastContext();
4137
const methods = useFormContext<AgentForm>();
4238
const [showToolDialog, setShowToolDialog] = useState(false);
43-
const { actions, setAction, setActivePanel } = useAgentPanelContext();
44-
45-
const allTools = queryClient.getQueryData<TPlugin[]>([QueryKeys.tools]) ?? [];
39+
const { actions, setAction, groupedTools: allTools, setActivePanel } = useAgentPanelContext();
4640

4741
const { control } = methods;
4842
const provider = useWatch({ control, name: 'provider' });
@@ -169,6 +163,20 @@ export default function AgentConfig({
169163
Icon = icons[iconKey];
170164
}
171165

166+
// Determine what to show
167+
const selectedToolIds = tools ?? [];
168+
const visibleToolIds = new Set(selectedToolIds);
169+
170+
// Check what group parent tools should be shown if any subtool is present
171+
Object.entries(allTools).forEach(([toolId, toolObj]) => {
172+
if (toolObj.tools?.length) {
173+
// if any subtool of this group is selected, ensure group parent tool rendered
174+
if (toolObj.tools.some((st) => selectedToolIds.includes(st.tool_id))) {
175+
visibleToolIds.add(toolId);
176+
}
177+
}
178+
});
179+
172180
return (
173181
<>
174182
<div className="h-auto bg-white px-4 pt-3 dark:bg-transparent">
@@ -287,28 +295,37 @@ export default function AgentConfig({
287295
${toolsEnabled === true && actionsEnabled === true ? ' + ' : ''}
288296
${actionsEnabled === true ? localize('com_assistants_actions') : ''}`}
289297
</label>
290-
<div className="space-y-2">
291-
{tools?.map((func, i) => (
292-
<AgentTool
293-
key={`${func}-${i}-${agent_id}`}
294-
tool={func}
295-
allTools={allTools}
296-
agent_id={agent_id}
297-
/>
298-
))}
299-
{(actions ?? [])
300-
.filter((action) => action.agent_id === agent_id)
301-
.map((action, i) => (
302-
<Action
303-
key={i}
304-
action={action}
305-
onClick={() => {
306-
setAction(action);
307-
setActivePanel(Panel.actions);
308-
}}
309-
/>
310-
))}
311-
<div className="flex space-x-2">
298+
<div>
299+
<div className="mb-1">
300+
{/* // Render all visible IDs (including groups with subtools selected) */}
301+
{[...visibleToolIds].map((toolId, i) => {
302+
const tool = allTools[toolId];
303+
if (!tool) return null;
304+
return (
305+
<AgentTool
306+
key={`${toolId}-${i}-${agent_id}`}
307+
tool={toolId}
308+
allTools={allTools}
309+
agent_id={agent_id}
310+
/>
311+
);
312+
})}
313+
</div>
314+
<div className="flex flex-col gap-1">
315+
{(actions ?? [])
316+
.filter((action) => action.agent_id === agent_id)
317+
.map((action, i) => (
318+
<Action
319+
key={i}
320+
action={action}
321+
onClick={() => {
322+
setAction(action);
323+
setActivePanel(Panel.actions);
324+
}}
325+
/>
326+
))}
327+
</div>
328+
<div className="mt-2 flex space-x-2">
312329
{(toolsEnabled ?? false) && (
313330
<button
314331
type="button"
@@ -343,7 +360,6 @@ export default function AgentConfig({
343360
<ToolSelectDialog
344361
isOpen={showToolDialog}
345362
setIsOpen={setShowToolDialog}
346-
toolsFormKey="tools"
347363
endpoint={EModelEndpoint.agents}
348364
/>
349365
</>

0 commit comments

Comments
 (0)