agent-picker-list.tsx
1 'use client' 2 3 import { AgentAvatar } from '@/components/agents/agent-avatar' 4 import { CheckIcon } from '@/components/shared/check-icon' 5 import type { Agent } from '@/types' 6 7 interface Props { 8 agents: Agent[] 9 /** Currently selected agent ID(s). String for single-select, string[] for multi-select. */ 10 selected: string | string[] 11 /** Called when an agent is clicked. In multi mode, caller should toggle; in single mode, set. */ 12 onSelect: (agentId: string) => void 13 /** Show a "None" option at the top for optional single-select */ 14 noneOption?: { label: string; onSelect: () => void } 15 /** Show delegation-capable badge */ 16 showDelegationBadge?: boolean 17 /** Max height of the scrollable list */ 18 maxHeight?: number 19 } 20 21 export function AgentPickerList({ 22 agents, 23 selected, 24 onSelect, 25 noneOption, 26 showDelegationBadge, 27 maxHeight = 220, 28 }: Props) { 29 const isSelected = (id: string) => 30 Array.isArray(selected) ? selected.includes(id) : selected === id 31 const noneSelected = Array.isArray(selected) ? selected.length === 0 : !selected 32 33 if (agents.length === 0 && !noneOption) { 34 return <p className="text-[13px] text-text-3">No agents configured.</p> 35 } 36 37 return ( 38 <div 39 className="flex flex-col gap-1 rounded-[14px] border border-white/[0.06] bg-surface p-1.5 overflow-y-auto" 40 style={{ maxHeight }} 41 > 42 {noneOption && ( 43 <button 44 onClick={noneOption.onSelect} 45 className={`relative flex items-center gap-3 px-3 py-2.5 rounded-[10px] cursor-pointer transition-all w-full text-left border-none 46 ${noneSelected ? 'bg-accent-soft' : 'bg-transparent hover:bg-white/[0.03]'}`} 47 style={{ fontFamily: 'inherit' }} 48 > 49 {noneSelected && ( 50 <div className="absolute left-0 top-2 bottom-2 w-[2.5px] rounded-full bg-accent-bright" /> 51 )} 52 <div className="w-[28px] h-[28px] rounded-full bg-white/[0.06] flex items-center justify-center shrink-0"> 53 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className={noneSelected ? 'text-accent-bright' : 'text-text-3'}> 54 <circle cx="12" cy="12" r="10" /><line x1="8" y1="12" x2="16" y2="12" /> 55 </svg> 56 </div> 57 <span className={`text-[13px] font-600 flex-1 ${noneSelected ? 'text-accent-bright' : 'text-text-2'}`}> 58 {noneOption.label} 59 </span> 60 </button> 61 )} 62 {agents.map((a) => { 63 const active = isSelected(a.id) 64 return ( 65 <button 66 key={a.id} 67 onClick={() => onSelect(a.id)} 68 className={`relative flex items-center gap-3 px-3 py-2.5 rounded-[10px] cursor-pointer transition-all w-full text-left border-none 69 ${active ? 'bg-accent-soft' : 'bg-transparent hover:bg-white/[0.03]'}`} 70 style={{ fontFamily: 'inherit' }} 71 > 72 {active && ( 73 <div className="absolute left-0 top-2 bottom-2 w-[2.5px] rounded-full bg-accent-bright" /> 74 )} 75 <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={28} /> 76 <span className={`text-[13px] font-600 flex-1 truncate ${active ? 'text-accent-bright' : 'text-text-2'}`}> 77 {a.name} 78 </span> 79 {showDelegationBadge && a.delegationEnabled && ( 80 <span className="text-[10px] text-text-3/60 flex items-center gap-0.5"> 81 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg> 82 </span> 83 )} 84 {active && <CheckIcon className="text-accent-bright shrink-0" />} 85 </button> 86 ) 87 })} 88 </div> 89 ) 90 }