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 { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Loader2, LogOut, Copy, Check, ExternalLink, Plus, X, Sparkles, User, } from "lucide-react"; import { useCodexOauth } from "./hooks/useCodexOauth"; import { copyText } from "@/lib/clipboard"; interface CodexOAuthSectionProps { className?: string; /** 当前选中的 ChatGPT 账号 ID */ selectedAccountId?: string | null; /** 账号选择回调 */ onAccountSelect?: (accountId: string | null) => void; /** 是否开启 Codex FAST mode */ fastModeEnabled?: boolean; /** FAST mode 切换回调 */ onFastModeChange?: (enabled: boolean) => void; } /** * Codex OAuth 认证区块 * * 通过 OpenAI Device Code 流程登录 ChatGPT Plus/Pro 账号, * 用于将 Claude Code 请求反代到 Codex 后端 API。 */ export const CodexOAuthSection: React.FC = ({ className, selectedAccountId, onAccountSelect, fastModeEnabled = false, onFastModeChange, }) => { const { t } = useTranslation(); const [copied, setCopied] = React.useState(false); const { accounts, defaultAccountId, hasAnyAccount, pollingState, deviceCode, error, isPolling, isAddingAccount, isRemovingAccount, isSettingDefaultAccount, addAccount, removeAccount, setDefaultAccount, cancelAuth, logout, } = useCodexOauth(); 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); } }; return (
{/* 认证状态标题 */}
{hasAnyAccount ? t("codexOauth.accountCount", { count: accounts.length, defaultValue: `${accounts.length} 个账号`, }) : t("codexOauth.notAuthenticated", "未认证")}
{/* 账号选择器 */} {hasAnyAccount && onAccountSelect && (
)} {onFastModeChange && (

{t("codexOauth.fastModeDescription", { defaultValue: 'Send service_tier="priority" for lower latency. Turn it off if the ChatGPT Codex backend rejects the parameter.', })}

)} {/* 已登录账号列表 */} {hasAnyAccount && (
{accounts.map((account) => (
{account.login} {defaultAccountId === account.id && ( {t("codexOauth.defaultAccount", "默认")} )} {selectedAccountId === account.id && ( {t("codexOauth.selected", "已选中")} )}
{defaultAccountId !== account.id && ( )}
))}
)} {/* 未认证 - 登录按钮 */} {!hasAnyAccount && pollingState === "idle" && ( )} {/* 已有账号 - 添加更多按钮 */} {hasAnyAccount && pollingState === "idle" && ( )} {/* 轮询中状态 */} {isPolling && deviceCode && (
{t("codexOauth.waitingForAuth", "等待授权中...")}

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

{deviceCode.user_code}
{deviceCode.verification_uri}
)} {/* 错误状态 */} {pollingState === "error" && error && (

{error}

)} {/* 注销所有账号 */} {hasAnyAccount && accounts.length > 1 && ( )}
); }; export default CodexOAuthSection;