import { AgentAvatar, type AvatarAgent } from "@/components/AgentAvatar"; import { useEffect, useMemo, useState, type ComponentProps, type ReactNode } from "react"; import { ChevronRight } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; export interface AgentMultiSelectOption extends AvatarAgent { id: string; name: string; title?: string | null; icon?: string | null; } export function AgentSelect({ agents, value, onChange, placeholder = "Select agent…", emptyMessage = "No agents yet.", disabled = false, triggerClassName, id, }: { agents: AgentMultiSelectOption[]; value: string; onChange: (agentId: string) => void; placeholder?: string; emptyMessage?: string; disabled?: boolean; triggerClassName?: string; id?: string; }) { const [open, setOpen] = useState(false); const [filter, setFilter] = useState(""); const selectedAgent = agents.find((agent) => agent.id === value); const normalizedFilter = filter.trim().toLowerCase(); const filteredAgents = useMemo( () => agents .filter((agent) => `${agent.name} ${agent.title ?? ""}`.toLowerCase().includes(normalizedFilter)) .sort((a, b) => a.name.localeCompare(b.name)), [agents, normalizedFilter], ); return ( { setOpen(nextOpen); if (!nextOpen) setFilter(""); }} > {selectedAgent && } {selectedAgent?.name ?? placeholder} setFilter(event.target.value)} placeholder="Filter agents" className="h-8" autoFocus /> {agents.length === 0 ? ( {emptyMessage} ) : ( {filteredAgents.map((agent) => ( { onChange(agent.id); setOpen(false); }} > {agent.name} {agent.title ? {agent.title} : null} ))} {filteredAgents.length === 0 ? ( No matches. ) : null} )} ); } export function AgentMultiSelect({ agents, selectedAgentIds, onChange, onSave, loading = false, disabled = false, pending = false, getDescription, isAgentDisabled, renderNameSuffix, triggerLabel, triggerIcon, triggerVariant = "outline", triggerSize = "default", triggerFullWidth = true, triggerClassName, contentAlign = "start", headerContent, emptyMessage = "No agents yet.", showSelectionPreview = true, onOpenChange, }: { agents: AgentMultiSelectOption[]; selectedAgentIds: Set; onChange?: (next: Set) => void; onSave?: (next: Set) => void; loading?: boolean; disabled?: boolean; pending?: boolean; getDescription?: (agent: AgentMultiSelectOption) => string | null | undefined; isAgentDisabled?: (agent: AgentMultiSelectOption) => boolean; renderNameSuffix?: (agent: AgentMultiSelectOption) => ReactNode; triggerLabel?: string; triggerIcon?: ReactNode; triggerVariant?: ComponentProps["variant"]; triggerSize?: ComponentProps["size"]; triggerFullWidth?: boolean; triggerClassName?: string; contentAlign?: ComponentProps["align"]; headerContent?: ReactNode; emptyMessage?: string; showSelectionPreview?: boolean; onOpenChange?: (open: boolean) => void; }) { const [open, setOpen] = useState(false); const [filter, setFilter] = useState(""); const [draftAgentIds, setDraftAgentIds] = useState>(new Set(selectedAgentIds)); const staged = Boolean(onSave); const workingAgentIds = staged ? draftAgentIds : selectedAgentIds; useEffect(() => { if (open && staged) setDraftAgentIds(new Set(selectedAgentIds)); }, [open, selectedAgentIds, staged]); const normalizedFilter = filter.trim().toLowerCase(); const filteredAgents = useMemo( () => agents .filter((agent) => { const description = getDescription?.(agent) ?? agent.title ?? ""; return `${agent.name} ${description}`.toLowerCase().includes(normalizedFilter); }) .sort((a, b) => { const aSelected = workingAgentIds.has(a.id); const bSelected = workingAgentIds.has(b.id); if (aSelected !== bSelected) return aSelected ? -1 : 1; return a.name.localeCompare(b.name); }), [agents, getDescription, normalizedFilter, workingAgentIds], ); const selectedCount = selectedAgentIds.size; const selectedAgents = agents.filter((agent) => selectedAgentIds.has(agent.id)); function setSelection(next: Set) { if (staged) setDraftAgentIds(next); else onChange?.(next); } return ( { setOpen(nextOpen); onOpenChange?.(nextOpen); if (!nextOpen) setFilter(""); }} > {triggerIcon} {triggerLabel ?? (selectedCount === 0 ? "Select agents" : `${selectedCount} ${selectedCount === 1 ? "agent" : "agents"} selected`)} setFilter(event.target.value)} placeholder="Filter agents" className="h-8" autoFocus /> {headerContent} {loading ? ( ) : agents.length === 0 ? ( {emptyMessage} ) : ( {filteredAgents.map((agent) => { const description = getDescription?.(agent) ?? agent.title; const optionDisabled = isAgentDisabled?.(agent) ?? false; return ( { const next = new Set(workingAgentIds); if (checked) next.add(agent.id); else next.delete(agent.id); setSelection(next); }} /> {agent.name} {renderNameSuffix?.(agent)} {description ? {description} : null} ); })} {filteredAgents.length === 0 ? ( No matches. ) : null} )} {workingAgentIds.size === 0 ? "No agents selected" : `${workingAgentIds.size} selected`} {staged ? ( setOpen(false)} disabled={pending}> Cancel ) : null} { if (staged) onSave?.(draftAgentIds); setOpen(false); }} disabled={pending} > {staged ? (pending ? "Saving…" : "Save") : "Done"} {showSelectionPreview && selectedAgents.length > 0 ? ( {selectedAgents.slice(0, 3).map((agent) => ( {agent.name} ))} {selectedAgents.length > 3 ? ( and {selectedAgents.length - 3} more ) : null} ) : null} ); }
and {selectedAgents.length - 3} more