import { useEffect, useState } from 'react'; import { CheckCircle2, XCircle, RefreshCw, ChevronDown, ChevronRight, Terminal, LogIn, Download, Cpu, ArrowUpCircle, User, Building2, BarChart3, Loader2, } from 'lucide-react'; import { CliInstallGuide } from '@renderer/components/settings/CliInstallGuide'; import type { SidecarConnectionDiagnostics, SidecarConnectionStatus, SidecarCopilotCliVersionStatus, QuotaSnapshot, } from '@shared/contracts/sidecar'; interface CopilotStatusCardProps { connection?: SidecarConnectionDiagnostics; modelCount: number; isRefreshing: boolean; onRefresh: () => void; onGetQuota?: () => Promise>; } 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-[var(--color-status-success)]', dotClasses: 'bg-[var(--color-status-success)]', }; case 'copilot-cli-missing': return { icon: , label: 'Copilot CLI not found', accentClasses: 'text-[var(--color-status-warning)]', dotClasses: 'bg-[var(--color-status-warning)]', 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-[var(--color-status-info)]', dotClasses: 'bg-[var(--color-status-info)]', actionIcon: , actionLabel: 'Run copilot auth login in your terminal, then refresh', }; case 'copilot-error': return { icon: , label: 'Connection error', accentClasses: 'text-[var(--color-status-error)]', dotClasses: 'bg-[var(--color-status-error)]', }; } } 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('/')}`; } function VersionBadge({ status, installedVersion }: { status: SidecarCopilotCliVersionStatus; installedVersion?: string }) { const versionLabel = installedVersion ? `v${installedVersion}` : undefined; switch (status) { case 'latest': return ( {versionLabel ?? 'Up to date'} ); case 'outdated': return ( Update available ); case 'unknown': return ( {versionLabel ?? 'Version unknown'} ); } } const quotaTypeLabels: Record = { premium_interactions: 'Premium Requests', chat: 'Chat', completions: 'Completions', }; function formatQuotaTypeLabel(key: string): string { return quotaTypeLabels[key] ?? key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); } function formatResetDate(iso: string): string { try { const date = new Date(iso); const now = new Date(); const diffMs = date.getTime() - now.getTime(); const diffDays = Math.ceil(diffMs / 86_400_000); if (diffDays <= 0) return 'Today'; if (diffDays === 1) return 'Tomorrow'; if (diffDays <= 30) return `In ${diffDays} day${diffDays === 1 ? '' : 's'}`; return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } catch { return iso; } } function QuotaSection({ onGetQuota, }: { onGetQuota: () => Promise>; }) { const [quotaData, setQuotaData] = useState>(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); useEffect(() => { let cancelled = false; setLoading(true); setError(undefined); void onGetQuota() .then((data) => { if (!cancelled) setQuotaData(data); }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : String(err)); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [onGetQuota]); if (loading) { return (
Loading quota…
); } if (error) { return (
Could not load quota
); } if (!quotaData || Object.keys(quotaData).length === 0) { return (
No quota data available
); } return (
{Object.entries(quotaData).map(([key, snapshot]) => { const usedPct = snapshot.entitlementRequests > 0 ? (snapshot.usedRequests / snapshot.entitlementRequests) * 100 : 0; const barColor = usedPct > 90 ? 'bg-[var(--color-status-error)]' : usedPct > 70 ? 'bg-[var(--color-status-warning)]' : 'bg-[var(--color-accent)]/60'; return (
{formatQuotaTypeLabel(key)} {Math.round(snapshot.remainingPercentage)}% remaining
{Math.round(snapshot.usedRequests)} of {Math.round(snapshot.entitlementRequests)} used {snapshot.overage > 0 && ( <> · {Math.round(snapshot.overage)} overage )} {snapshot.resetDate && ( <> · Resets {formatResetDate(snapshot.resetDate)} )}
); })}
); } function AccountSection({ connection }: { connection: SidecarConnectionDiagnostics }) { const { account } = connection; if (!account) return null; const hasLogin = !!account.login; const hasOrgs = account.organizations && account.organizations.length > 0; if (!hasLogin && !account.statusMessage) return null; const MAX_VISIBLE_ORGS = 5; const visibleOrgs = hasOrgs ? account.organizations!.slice(0, MAX_VISIBLE_ORGS) : []; const remainingOrgs = hasOrgs ? account.organizations!.length - MAX_VISIBLE_ORGS : 0; return (
{/* Identity row */}
{hasLogin ? (
{account.login} {account.host && ( · {account.host} )}
) : ( {account.statusMessage} )}
{/* Organizations */} {hasOrgs && (
{visibleOrgs.map((org) => ( {org} ))} {remainingOrgs > 0 && ( +{remainingOrgs} more )}
)}
); } export function CopilotStatusCard({ connection, modelCount, isRefreshing, onRefresh, onGetQuota, }: 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); const hasVersionInfo = !!connection.copilotCliVersion; const hasAccountInfo = !!connection.account; return (
{/* Status indicator */}
{config.label} {isHealthy && ( · {modelCount} model{modelCount === 1 ? '' : 's'} available )}
{hasVersionInfo && ( )} {checkedLabel && ( {checkedLabel} )}
{/* Account info (when healthy) */} {isHealthy && hasAccountInfo && ( )} {/* Usage & Quota (when healthy and callback provided) */} {isHealthy && onGetQuota && (
Usage & Quota
)} {/* Installation guide for missing CLI */} {connection.status === 'copilot-cli-missing' && (
)} {/* Action hint for other non-ready states */} {!isHealthy && connection.status !== 'copilot-cli-missing' && config.actionLabel && (
{config.actionIcon}

{config.actionLabel}

)} {/* Expandable details (healthy state) */} {isHealthy && hasDetail && (
{showDetails && (
{connection.copilotCliPath && (
CLI path

{connection.copilotCliPath}

)} {hasVersionInfo && connection.copilotCliVersion!.status === 'outdated' && connection.copilotCliVersion!.latestVersion && (
Latest version

{connection.copilotCliVersion!.latestVersion}

)}
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}

)}
)}
); }