import React from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Loader2, Github, LogOut, Copy, Check, ExternalLink, Plus, X, User, } from "lucide-react"; import { useCopilotAuth } from "./hooks/useCopilotAuth"; import { copyText } from "@/lib/clipboard"; import type { GitHubAccount } from "@/lib/api"; interface CopilotAuthSectionProps { className?: string; /** 当前选中的 GitHub 账号 ID */ selectedAccountId?: string | null; /** 账号选择回调 */ onAccountSelect?: (accountId: string | null) => void; } /** * Copilot OAuth 认证区块 * * 显示 GitHub Copilot 的认证状态,支持多账号管理和选择。 */ export const CopilotAuthSection: React.FC = ({ className, selectedAccountId, onAccountSelect, }) => { const { t } = useTranslation(); const [copied, setCopied] = React.useState(false); const [deploymentType, setDeploymentType] = React.useState< "github.com" | "enterprise" >("github.com"); const [enterpriseDomain, setEnterpriseDomain] = React.useState(""); // 根据部署类型计算实际的 GitHub 域名 const effectiveGithubDomain = deploymentType === "enterprise" && enterpriseDomain.trim() ? enterpriseDomain .trim() .replace(/^https?:\/\//, "") .replace(/\/$/, "") : undefined; const { accounts, defaultAccountId, migrationError, hasAnyAccount, pollingState, deviceCode, error, isPolling, isAddingAccount, isRemovingAccount, isSettingDefaultAccount, addAccount, removeAccount, setDefaultAccount, cancelAuth, logout, } = useCopilotAuth(effectiveGithubDomain); // 复制用户码 const copyUserCode = async () => { if (deviceCode?.user_code) { await copyText(deviceCode.user_code); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; // 处理账号选择 const handleAccountSelect = (value: string) => { onAccountSelect?.(value === "none" ? null : value); }; // 处理移除账号 const handleRemoveAccount = (accountId: string, e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); removeAccount(accountId); // 如果移除的是当前选中的账号,清除选择 if (selectedAccountId === accountId) { onAccountSelect?.(null); } }; // 渲染账号头像 const renderAvatar = (account: GitHubAccount) => { return ; }; return (
{/* 认证状态标题 */}
{hasAnyAccount ? t("copilot.accountCount", { count: accounts.length, defaultValue: `${accounts.length} 个账号`, }) : t("copilot.notAuthenticated", "未认证")}
{/* GitHub 部署类型选择 */}
{deploymentType === "enterprise" && ( setEnterpriseDomain(e.target.value)} /> )}
{migrationError && (

{t("copilot.migrationFailed", { error: migrationError, defaultValue: `旧认证数据迁移失败:${migrationError}`, })}

)} {/* 账号选择器(有账号时显示) */} {hasAnyAccount && onAccountSelect && (
)} {/* 已登录账号列表 */} {hasAnyAccount && (
{accounts.map((account) => (
{renderAvatar(account)} {account.login} {defaultAccountId === account.id && ( {t("copilot.defaultAccount", "默认")} )} {account.github_domain && account.github_domain !== "github.com" && ( {account.github_domain} )} {selectedAccountId === account.id && ( {t("copilot.selected", "已选中")} )}
{defaultAccountId !== account.id && ( )}
))}
)} {/* 未认证状态 - 登录按钮 */} {!hasAnyAccount && pollingState === "idle" && ( )} {/* 已有账号 - 添加更多账号按钮 */} {hasAnyAccount && pollingState === "idle" && ( )} {/* 轮询中状态 */} {isPolling && deviceCode && (
{t("copilot.waitingForAuth", "等待授权中...")}
{/* 用户码 */}

{t("copilot.enterCode", "在浏览器中输入以下代码:")}

{deviceCode.user_code}
{/* 验证链接 */}
{deviceCode.verification_uri}
{/* 取消按钮 */}
)} {/* 错误状态 */} {pollingState === "error" && error && (

{error}

)} {/* 注销所有账号按钮 */} {hasAnyAccount && accounts.length > 1 && ( )}
); }; const CopilotAccountAvatar: React.FC<{ account: GitHubAccount }> = ({ account, }) => { const [failed, setFailed] = React.useState(false); if (!account.avatar_url || failed) { return ; } return ( {account.login} setFailed(true)} /> ); }; export default CopilotAuthSection;