| import { Button } from '@/components/ui/button'; |
| import { SkeletonPulse } from '@/components/ui/skeleton'; |
| import { Spinner } from '@/components/ui/spinner'; |
| import { CheckCircle2, AlertCircle, RefreshCw } from 'lucide-react'; |
| import { cn } from '@/lib/utils'; |
| import type { CliStatus } from '../shared/types'; |
| import { CopilotIcon } from '@/components/ui/provider-icon'; |
| import type { CopilotAuthStatus } from '@automaker/types'; |
|
|
| |
| export type { CopilotAuthStatus }; |
|
|
| function getAuthMethodLabel(method: CopilotAuthStatus['method']): string { |
| switch (method) { |
| case 'oauth': |
| return 'GitHub OAuth'; |
| case 'cli': |
| return 'Copilot CLI'; |
| default: |
| return method || 'Unknown'; |
| } |
| } |
|
|
| interface CopilotCliStatusProps { |
| status: CliStatus | null; |
| authStatus?: CopilotAuthStatus | null; |
| isChecking: boolean; |
| onRefresh: () => void; |
| } |
|
|
| export function CopilotCliStatusSkeleton() { |
| return ( |
| <div |
| className={cn( |
| 'rounded-2xl overflow-hidden', |
| 'border border-border/50', |
| 'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl', |
| 'shadow-sm shadow-black/5' |
| )} |
| > |
| <div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent"> |
| <div className="flex items-center justify-between mb-2"> |
| <div className="flex items-center gap-3"> |
| <SkeletonPulse className="w-9 h-9 rounded-xl" /> |
| <SkeletonPulse className="h-6 w-36" /> |
| </div> |
| <SkeletonPulse className="w-9 h-9 rounded-lg" /> |
| </div> |
| <div className="ml-12"> |
| <SkeletonPulse className="h-4 w-80" /> |
| </div> |
| </div> |
| <div className="p-6 space-y-4"> |
| {/* Installation status skeleton */} |
| <div className="flex items-center gap-3 p-4 rounded-xl border border-border/30 bg-muted/10"> |
| <SkeletonPulse className="w-10 h-10 rounded-xl" /> |
| <div className="flex-1 space-y-2"> |
| <SkeletonPulse className="h-4 w-40" /> |
| <SkeletonPulse className="h-3 w-32" /> |
| <SkeletonPulse className="h-3 w-48" /> |
| </div> |
| </div> |
| {/* Auth status skeleton */} |
| <div className="flex items-center gap-3 p-4 rounded-xl border border-border/30 bg-muted/10"> |
| <SkeletonPulse className="w-10 h-10 rounded-xl" /> |
| <div className="flex-1 space-y-2"> |
| <SkeletonPulse className="h-4 w-28" /> |
| <SkeletonPulse className="h-3 w-36" /> |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| export function CopilotCliStatus({ |
| status, |
| authStatus, |
| isChecking, |
| onRefresh, |
| }: CopilotCliStatusProps) { |
| if (!status) return <CopilotCliStatusSkeleton />; |
|
|
| return ( |
| <div |
| className={cn( |
| 'rounded-2xl overflow-hidden', |
| 'border border-border/50', |
| 'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl', |
| 'shadow-sm shadow-black/5' |
| )} |
| > |
| <div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent"> |
| <div className="flex items-center justify-between mb-2"> |
| <div className="flex items-center gap-3"> |
| <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500/20 to-violet-600/10 flex items-center justify-center border border-violet-500/20"> |
| <CopilotIcon className="w-5 h-5 text-violet-500" /> |
| </div> |
| <h2 className="text-lg font-semibold text-foreground tracking-tight"> |
| GitHub Copilot CLI |
| </h2> |
| </div> |
| <Button |
| variant="ghost" |
| size="icon" |
| onClick={onRefresh} |
| disabled={isChecking} |
| data-testid="refresh-copilot-cli" |
| title="Refresh Copilot CLI detection" |
| aria-label="Refresh Copilot CLI detection" |
| className={cn( |
| 'h-9 w-9 rounded-lg', |
| 'hover:bg-accent/50 hover:scale-105', |
| 'transition-all duration-200' |
| )} |
| > |
| {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />} |
| </Button> |
| </div> |
| <p className="text-sm text-muted-foreground/80 ml-12"> |
| GitHub Copilot CLI provides access to GPT and Claude models via your Copilot subscription. |
| </p> |
| </div> |
| <div className="p-6 space-y-4"> |
| {status.success && status.status === 'installed' ? ( |
| <div className="space-y-3"> |
| <div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20"> |
| <div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0"> |
| <CheckCircle2 className="w-5 h-5 text-emerald-500" /> |
| </div> |
| <div className="flex-1 min-w-0"> |
| <p className="text-sm font-medium text-emerald-400">Copilot CLI Installed</p> |
| <div className="text-xs text-emerald-400/70 mt-1.5 space-y-0.5"> |
| {status.method && ( |
| <p> |
| Method: <span className="font-mono">{status.method}</span> |
| </p> |
| )} |
| {status.version && ( |
| <p> |
| Version: <span className="font-mono">{status.version}</span> |
| </p> |
| )} |
| {status.path && ( |
| <p className="truncate" title={status.path}> |
| Path: <span className="font-mono text-[10px]">{status.path}</span> |
| </p> |
| )} |
| </div> |
| </div> |
| </div> |
| |
| {/* Authentication Status */} |
| {authStatus?.authenticated ? ( |
| <div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20"> |
| <div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0"> |
| <CheckCircle2 className="w-5 h-5 text-emerald-500" /> |
| </div> |
| <div className="flex-1 min-w-0"> |
| <p className="text-sm font-medium text-emerald-400">Authenticated</p> |
| <div className="text-xs text-emerald-400/70 mt-1.5"> |
| {authStatus.method !== 'none' && ( |
| <p> |
| Method:{' '} |
| <span className="font-mono">{getAuthMethodLabel(authStatus.method)}</span> |
| </p> |
| )} |
| {authStatus.login && ( |
| <p> |
| User: <span className="font-mono">{authStatus.login}</span> |
| </p> |
| )} |
| </div> |
| </div> |
| </div> |
| ) : ( |
| <div className="flex items-start gap-3 p-4 rounded-xl bg-red-500/10 border border-red-500/20"> |
| <div className="w-10 h-10 rounded-xl bg-red-500/15 flex items-center justify-center border border-red-500/20 shrink-0 mt-0.5"> |
| <AlertCircle className="w-5 h-5 text-red-500" /> |
| </div> |
| <div className="flex-1"> |
| <p className="text-sm font-medium text-red-400">Authentication Required</p> |
| {authStatus?.error && ( |
| <p className="text-xs text-red-400/70 mt-1">{authStatus.error}</p> |
| )} |
| <p className="text-xs text-red-400/70 mt-2"> |
| Run <code className="font-mono bg-red-500/10 px-1 rounded">gh auth login</code>{' '} |
| in your terminal to authenticate with GitHub. |
| </p> |
| </div> |
| </div> |
| )} |
| |
| {status.recommendation && ( |
| <p className="text-xs text-muted-foreground/70 ml-1">{status.recommendation}</p> |
| )} |
| </div> |
| ) : ( |
| <div className="space-y-4"> |
| <div className="flex items-start gap-3 p-4 rounded-xl bg-amber-500/10 border border-amber-500/20"> |
| <div className="w-10 h-10 rounded-xl bg-amber-500/15 flex items-center justify-center border border-amber-500/20 shrink-0 mt-0.5"> |
| <AlertCircle className="w-5 h-5 text-amber-500" /> |
| </div> |
| <div className="flex-1"> |
| <p className="text-sm font-medium text-amber-400">Copilot CLI Not Detected</p> |
| <p className="text-xs text-amber-400/70 mt-1"> |
| {status.recommendation || |
| 'Install GitHub Copilot CLI to use models via your Copilot subscription.'} |
| </p> |
| </div> |
| </div> |
| {status.installCommands && ( |
| <div className="space-y-3"> |
| <p className="text-xs font-medium text-foreground/80">Installation Commands:</p> |
| <div className="space-y-2"> |
| {status.installCommands.npm && ( |
| <div className="p-3 rounded-xl bg-accent/30 border border-border/50"> |
| <p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider"> |
| npm |
| </p> |
| <code className="text-xs text-foreground/80 font-mono break-all"> |
| {status.installCommands.npm} |
| </code> |
| </div> |
| )} |
| </div> |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| } |
|
|