/** * AgentSelector - Dropdown for switching between AI agents * * Dark, grouped agent menu with local SVG branding for built-in, * discovered, and external agents. */ import { ChevronDown, RefreshCw, Plus, Settings } from 'lucide-react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { cn } from '../../lib/utils'; import { useI18n } from '../../application/i18n/I18nProvider'; import { getExternalAgentSdkBackend, isSettingsManagedDiscoveredAgent, matchesManagedAgentConfig, } from '../../infrastructure/ai/managedAgents'; import type { AgentInfo, ExternalAgentConfig, DiscoveredAgent } from '../../infrastructure/ai/types'; import AgentIconBadge from './AgentIconBadge'; import { Dropdown, DropdownContent, DropdownTrigger, } from '../ui/dropdown'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; interface AgentSelectorProps { currentAgentId: string; externalAgents: ExternalAgentConfig[]; discoveredAgents?: DiscoveredAgent[]; isDiscovering?: boolean; onSelectAgent: (agentId: string) => void; onEnableDiscoveredAgent?: (agent: DiscoveredAgent) => void; onRediscover?: () => void; onManageAgents?: () => void; parked?: boolean; disabled?: boolean; } const BUILTIN_AGENTS: AgentInfo[] = [ { id: 'catty', name: 'Catty Agent', type: 'builtin', description: 'Built-in terminal assistant', available: true, }, ]; const SectionLabel: React.FC<{ children: React.ReactNode; action?: React.ReactNode }> = ({ children, action }) => (
{children} {action}
); const AgentMenuRow: React.FC<{ agent: AgentInfo; isActive?: boolean; subtitle?: string; onClick: () => void; }> = ({ agent, isActive, subtitle, onClick }) => { return ( ); }; const DiscoveredAgentRow: React.FC<{ agent: DiscoveredAgent; onEnable: () => void; }> = ({ agent, onEnable }) => { const { t } = useI18n(); const agentLike: AgentInfo = { id: `discovered_${agent.command}`, name: agent.name, type: 'external', icon: agent.icon, command: agent.command, available: true, }; return (
{agent.name} {agent.version || agent.path}
{t('ai.chat.enableAgent', { name: agent.name })}
); }; const AgentSelector: React.FC = ({ currentAgentId, externalAgents, discoveredAgents = [], isDiscovering = false, onSelectAgent, onEnableDiscoveredAgent, onRediscover, onManageAgents, parked = false, disabled = false, }) => { const { t } = useI18n(); const [open, setOpen] = useState(false); useEffect(() => { if (parked || disabled) setOpen(false); }, [disabled, parked]); const enabledExternalAgents = useMemo( () => externalAgents .filter((agent) => agent.enabled && Boolean(getExternalAgentSdkBackend(agent))) .map( (agent): AgentInfo => ({ id: agent.id, name: agent.name, type: 'external', icon: agent.icon, command: agent.command, args: agent.args, available: true, }), ), [externalAgents], ); // Discovered agents not yet added to external agents const unconfiguredDiscovered = useMemo( () => discoveredAgents.filter( (da) => { if (isSettingsManagedDiscoveredAgent(da)) { return !externalAgents.some((ea) => matchesManagedAgentConfig(ea, da.command)); } return !externalAgents.some((ea) => ea.command === da.command || ea.command === da.path); }, ), [discoveredAgents, externalAgents], ); const allAgents = useMemo( () => [...BUILTIN_AGENTS, ...enabledExternalAgents], [enabledExternalAgents], ); const currentAgent = useMemo( () => allAgents.find((agent) => agent.id === currentAgentId) ?? BUILTIN_AGENTS[0], [allAgents, currentAgentId], ); const handleSelect = useCallback( (agentId: string) => { onSelectAgent(agentId); setOpen(false); }, [onSelectAgent], ); const handleEnableDiscovered = useCallback( (agent: DiscoveredAgent) => { onEnableDiscoveredAgent?.(agent); // After enabling, auto-select it const agentId = `discovered_${agent.command}`; onSelectAgent(agentId); setOpen(false); }, [onEnableDiscoveredAgent, onSelectAgent], ); const handleManageAgents = useCallback(() => { setOpen(false); onManageAgents?.(); }, [onManageAgents]); return ( {BUILTIN_AGENTS.map((agent) => ( handleSelect(agent.id)} /> ))} {enabledExternalAgents.length > 0 && ( <>
{t('ai.chat.agents')} {enabledExternalAgents.map((agent) => ( handleSelect(agent.id)} /> ))} )} {unconfiguredDiscovered.length > 0 && ( <>
{t('ai.chat.rescan')} ) } > {t('ai.chat.detectedOnMachine')} {unconfiguredDiscovered.map((agent) => ( handleEnableDiscovered(agent)} /> ))} )}
); }; export default React.memo(AgentSelector);