import { useState } from 'react'; import { ChevronsUpDown, Star, Settings2, CircleDashed, } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; import { useNodes, type Node, type NodeMode } from '@/context/NodeContext'; import { formatTimeAgo } from '@/lib/relativeTime'; import { isValidVersion } from '@/lib/version'; interface NodeSwitcherProps { onManageNodes: () => void; /** One-line chip for the mobile Stacks masthead kicker (node name + chevron), * instead of the full bordered card. */ compact?: boolean; } function dotClass(status: Node['status']): string { if (status === 'online') { return 'bg-success shadow-[0_0_0_3px_color-mix(in_oklch,var(--success)_20%,transparent)]'; } if (status === 'offline') return 'bg-destructive'; return 'bg-muted-foreground/40'; } function typeLabel(type: Node['type'], mode: NodeMode | undefined): string { if (type === 'local') return 'Local'; if (mode === 'pilot_agent') return 'Agent'; return 'Remote'; } export function NodeSwitcher({ onManageNodes, compact = false }: NodeSwitcherProps) { const { nodes, activeNode, setActiveNode, nodeMeta, isLoading } = useNodes(); const [open, setOpen] = useState(false); const hasNodes = nodes.length > 0; const hasMultiple = nodes.length > 1; const kickerType = activeNode ? typeLabel(activeNode.type, activeNode.mode).toUpperCase() : hasNodes ? 'UNKNOWN' : 'LOADING'; const triggerContent = (
{activeNode ? ( ) : ( )}
Node · {kickerType}
{activeNode?.name ?? (isLoading ? '—' : 'No node')}
{hasMultiple ? ( ) : null}
); const compactTrigger = ( {activeNode?.name ?? (isLoading ? '…' : 'No node')} {hasMultiple ? : null} ); const trigger = compact ? compactTrigger : triggerContent; if (!hasMultiple) { return trigger; } return (
Connected {nodes.length} nodes
{nodes.map((node) => { const meta = nodeMeta.get(node.id); const isActive = activeNode?.id === node.id; const version = isValidVersion(meta?.version) ? meta.version : null; const typePart = typeLabel(node.type, node.mode).toUpperCase(); const metaParts: string[] = [typePart]; if (node.type === 'remote' && node.mode === 'pilot_agent') { metaParts.push( node.pilot_last_seen ? `SEEN ${formatTimeAgo(node.pilot_last_seen).toUpperCase()}` : 'WAITING', ); } if (version) metaParts.push(`v${version}`); return ( ); })}
); }