import { useState } from 'react'; import { CheckCircle2, XCircle, RefreshCw, ChevronDown, ChevronRight, Terminal, LogIn, Download, Cpu, } from 'lucide-react'; import type { SidecarConnectionDiagnostics, SidecarConnectionStatus, } from '@shared/contracts/sidecar'; interface CopilotStatusCardProps { connection?: SidecarConnectionDiagnostics; modelCount: number; isRefreshing: boolean; onRefresh: () => void; } interface StatusConfig { icon: React.ReactNode; label: string; accentClasses: string; dotClasses: string; actionIcon?: React.ReactNode; actionLabel?: string; } function getStatusConfig(status: SidecarConnectionStatus): StatusConfig { switch (status) { case 'ready': return { icon: , label: 'Connected to GitHub Copilot', accentClasses: 'text-emerald-400', dotClasses: 'bg-emerald-400', }; case 'copilot-cli-missing': return { icon: , label: 'Copilot CLI not found', accentClasses: 'text-amber-400', dotClasses: 'bg-amber-400', actionIcon: , actionLabel: 'Install the copilot CLI and ensure it is on your PATH', }; case 'copilot-auth-required': return { icon: , label: 'Sign-in required', accentClasses: 'text-blue-400', dotClasses: 'bg-blue-400', actionIcon: , actionLabel: 'Run copilot auth login in your terminal, then refresh', }; case 'copilot-error': return { icon: , label: 'Connection error', accentClasses: 'text-red-400', dotClasses: 'bg-red-400', }; } } function formatCheckedAt(iso: string): string { try { const date = new Date(iso); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMin = Math.floor(diffMs / 60_000); if (diffMin < 1) return 'just now'; if (diffMin < 60) return `${diffMin}m ago`; const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return `${diffHr}h ago`; return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } catch { return ''; } } function shortenPath(fullPath: string): string { const parts = fullPath.replace(/\\/g, '/').split('/'); if (parts.length <= 3) return fullPath; return `…/${parts.slice(-2).join('/')}`; } export function CopilotStatusCard({ connection, modelCount, isRefreshing, onRefresh, }: CopilotStatusCardProps) { const [showDetails, setShowDetails] = useState(false); if (!connection) { return (
Checking connection…
); } const config = getStatusConfig(connection.status); const isHealthy = connection.status === 'ready'; const hasDetail = connection.copilotCliPath; const checkedLabel = formatCheckedAt(connection.checkedAt); return (
{/* Status indicator */}
{config.label} {isHealthy && ( · {modelCount} model{modelCount === 1 ? '' : 's'} available )}
{checkedLabel && ( {checkedLabel} )}
{/* Action hint for non-ready states */} {!isHealthy && config.actionLabel && (
{config.actionIcon}

{config.actionLabel}

)} {/* CLI path (always visible when healthy, expandable otherwise) */} {isHealthy && hasDetail && (
{showDetails && (
{connection.copilotCliPath && (
CLI path

{connection.copilotCliPath}

)}
Last checked

{new Date(connection.checkedAt).toLocaleString()}

)}
)} {/* Error detail for non-ready states */} {!isHealthy && hasDetail && (
CLI path

{shortenPath(connection.copilotCliPath!)}

{connection.detail && (
Error detail

{connection.detail}

)}
)}
); }