diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 08fc6f5fd02..709a4352cd1 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -65,6 +65,7 @@ "src/components/playground/chat_ui/SessionManagement.tsx", "src/components/playground/chat_ui/SearchResultsDisplay.tsx", "src/components/team/member_permissions.tsx", + "src/components/agent_management/AgentSelector.tsx", "src/components/UsagePage/components/EntityUsage/SpendByProvider.tsx", "src/components/view_logs/ToolsSection/FormattedToolView.tsx", "src/components/Settings/LoggingAndAlerts/LoggingCallbacks/LoggingCallbacksTable.tsx", diff --git a/ui/litellm-dashboard/src/components/agent_management/AgentSelector.tsx b/ui/litellm-dashboard/src/components/agent_management/AgentSelector.tsx index 4debf284732..0d0fad8a07b 100644 --- a/ui/litellm-dashboard/src/components/agent_management/AgentSelector.tsx +++ b/ui/litellm-dashboard/src/components/agent_management/AgentSelector.tsx @@ -1,23 +1,27 @@ -import React, { useEffect, useState } from "react"; -import { Select } from "antd"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { X } from "lucide-react"; +import React, { useEffect, useMemo, useState } from "react"; import { getAgentsList } from "../networking"; interface Agent { agent_id: string; agent_name: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any agent_config?: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any agent_card_params?: Record; } interface AgentSelectorProps { - onChange: (selected: { - agents: string[]; - accessGroups: string[]; - }) => void; - value?: { - agents: string[]; - accessGroups: string[]; - }; + onChange: (selected: { agents: string[]; accessGroups: string[] }) => void; + value?: { agents: string[]; accessGroups: string[] }; className?: string; accessToken: string; placeholder?: string; @@ -34,21 +38,22 @@ const AgentSelector: React.FC = ({ }) => { const [agents, setAgents] = useState([]); const [accessGroups, setAccessGroups] = useState([]); - const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); useEffect(() => { const fetchData = async () => { if (!accessToken) return; - setLoading(true); try { const response = await getAgentsList(accessToken); - let agentsList = response?.agents || []; + const agentsList = response?.agents || []; setAgents(agentsList); - - // Extract unique access groups from agents + const groups = new Set(); agentsList.forEach((agent: Agent) => { - const agentAccessGroups = (agent as any).agent_access_groups; + const agentAccessGroups = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (agent as any).agent_access_groups as string[] | undefined; if (agentAccessGroups && Array.isArray(agentAccessGroups)) { agentAccessGroups.forEach((g: string) => groups.add(g)); } @@ -56,93 +61,154 @@ const AgentSelector: React.FC = ({ setAccessGroups(Array.from(groups)); } catch (error) { console.error("Error fetching agents:", error); - } finally { - setLoading(false); } }; fetchData(); }, [accessToken]); - // Combine options, access groups first - const options = [ - ...accessGroups.map((group) => ({ - label: group, - value: `group:${group}`, - isAccessGroup: true, - searchText: `${group} Access Group`, - })), - ...agents.map((agent) => ({ - label: `${agent.agent_name || agent.agent_id}`, - value: agent.agent_id, - isAccessGroup: false, - searchText: `${agent.agent_name || agent.agent_id} ${agent.agent_id} Agent`, - })), - ]; + const options = useMemo( + () => [ + ...accessGroups.map((group) => ({ + label: group, + value: `group:${group}`, + isAccessGroup: true, + searchText: `${group} Access Group`, + })), + ...agents.map((agent) => ({ + label: `${agent.agent_name || agent.agent_id}`, + value: agent.agent_id, + isAccessGroup: false, + searchText: `${agent.agent_name || agent.agent_id} ${agent.agent_id} Agent`, + })), + ], + [accessGroups, agents], + ); - // Flatten value for Select - const selectedValues = [ - ...(value?.agents || []), - ...(value?.accessGroups || []).map((g) => `group:${g}`), - ]; + const selectedValues = useMemo( + () => [ + ...(value?.agents || []), + ...(value?.accessGroups || []).map((g) => `group:${g}`), + ], + [value], + ); - // Handle selection - const handleChange = (selected: string[]) => { - const agentsSelected = selected.filter((v) => !v.startsWith("group:")); - const accessGroupsSelected = selected + const filteredOptions = useMemo( + () => + options + .filter((o) => !selectedValues.includes(o.value)) + .filter((o) => + query + ? o.searchText.toLowerCase().includes(query.toLowerCase()) + : true, + ), + [options, selectedValues, query], + ); + + const labelFor = (v: string) => + options.find((o) => o.value === v)?.label ?? v; + const isGroupFor = (v: string) => + options.find((o) => o.value === v)?.isAccessGroup ?? false; + + const applyChange = (next: string[]) => { + const agentsSelected = next.filter((v) => !v.startsWith("group:")); + const accessGroupsSelected = next .filter((v) => v.startsWith("group:")) .map((v) => v.replace("group:", "")); onChange({ agents: agentsSelected, accessGroups: accessGroupsSelected }); }; return ( -
- setQuery(e.target.value)} + className="h-8 mb-2" + /> +
+ {filteredOptions.length === 0 ? ( +
+ No matches
- - ))} - -
+ ) : ( + filteredOptions.map((opt) => ( + + )) + )} +
+ + ); }; export default AgentSelector; -