import React, { useState, useMemo, useCallback, useEffect } from 'react'; import { EModelEndpoint } from 'librechat-data-provider'; import { X, Waypoints, PlusCircle, ChevronDown } from 'lucide-react'; import { Label, Input, Textarea, HoverCard, CircleHelpIcon, HoverCardPortal, ControlCombobox, HoverCardContent, HoverCardTrigger, } from '@librechat/client'; import type { TMessage, GraphEdge } from 'librechat-data-provider'; import type { ControllerRenderProps } from 'react-hook-form'; import type { AgentForm, OptionWithIcon } from '~/common'; import MessageIcon from '~/components/Share/MessageIcon'; import { useAgentsMapContext } from '~/Providers'; import { useLocalize } from '~/hooks'; import { ESide } from '~/common'; interface AgentHandoffsProps { field: ControllerRenderProps; currentAgentId: string; } /** TODO: make configurable */ const MAX_HANDOFFS = 10; const AgentHandoffs: React.FC = ({ field, currentAgentId }) => { const localize = useLocalize(); const [newAgentId, setNewAgentId] = useState(''); const [expandedIndices, setExpandedIndices] = useState>(new Set()); const agentsMap = useAgentsMapContext(); const edgesValue = field.value; const edges = useMemo(() => edgesValue || [], [edgesValue]); const agents = useMemo(() => (agentsMap ? Object.values(agentsMap) : []), [agentsMap]); const selectableAgents = useMemo( () => agents .filter((agent) => agent?.id !== currentAgentId) .map( (agent) => ({ label: agent?.name || '', value: agent?.id || '', icon: ( ), }) as OptionWithIcon, ), [agents, currentAgentId], ); const getAgentDetails = useCallback((id: string) => agentsMap?.[id], [agentsMap]); useEffect(() => { if (newAgentId && edges.length < MAX_HANDOFFS) { const newEdge: GraphEdge = { from: currentAgentId, to: newAgentId, edgeType: 'handoff', }; field.onChange([...edges, newEdge]); setNewAgentId(''); } }, [newAgentId, edges, field, currentAgentId]); const removeHandoffAt = (index: number) => { field.onChange(edges.filter((_, i) => i !== index)); // Also remove from expanded set setExpandedIndices((prev) => { const newSet = new Set(prev); newSet.delete(index); return newSet; }); }; const updateHandoffAt = (index: number, agentId: string) => { const updated = [...edges]; updated[index] = { ...updated[index], to: agentId }; field.onChange(updated); }; const updateHandoffDetailsAt = (index: number, updates: Partial) => { const updated = [...edges]; updated[index] = { ...updated[index], ...updates }; field.onChange(updated); }; const toggleExpanded = (index: number) => { setExpandedIndices((prev) => { const newSet = new Set(prev); if (newSet.has(index)) { newSet.delete(index); } else { newSet.add(index); } return newSet; }); }; const getTargetAgentId = (to: string | string[]): string => { return Array.isArray(to) ? to[0] : to; }; return (
{localize('com_ui_beta')}
{edges.length} / {MAX_HANDOFFS}
{edges.map((edge, idx) => { const targetAgentId = getTargetAgentId(edge.to); const isExpanded = expandedIndices.has(idx); return (
updateHandoffAt(idx, id)} selectPlaceholder={localize('com_ui_agent_var', { 0: localize('com_ui_select'), })} searchPlaceholder={localize('com_ui_agent_var', { 0: localize('com_ui_search'), })} items={selectableAgents} displayValue={getAgentDetails(targetAgentId)?.name ?? ''} SelectIcon={ } className="flex-1 border-border-heavy" containerClassName="px-0" />
{isExpanded && (
updateHandoffDetailsAt(idx, { description: e.target.value }) } className="mt-1 h-8 text-sm" />