From 0f3991efc3509871491ffe66d6bf42047aa612e9 Mon Sep 17 00:00:00 2001 From: SaladDay Date: Wed, 1 Jul 2026 17:07:04 +0000 Subject: [PATCH] feat(codex-oauth): show per-account usage in Auth Center MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each ChatGPT (Codex OAuth) account under Settings → 认证 now displays its own subscription usage — reset countdowns and per-window progress bars — directly in the account list, instead of usage only being visible on the active provider card. - Add useCodexOauthQuotaByAccountId(accountId) and refactor useCodexOauthQuota to delegate to it (shared query key → cache reuse) - Add CodexOauthAccountQuota, a thin per-account wrapper that reuses the existing SubscriptionQuotaView expanded layout (same look and 5-state handling as provider cards), with a light spinner on first load - Render it under each account row in CodexOAuthSection; fetch once when the Auth Center opens, manual refresh available (no polling) Copilot is intentionally left out — same as before, this is Codex-only. --- src/components/CodexOauthAccountQuota.tsx | 53 ++++++++++++++ .../providers/forms/CodexOAuthSection.tsx | 72 ++++++++++--------- src/lib/query/subscription.ts | 29 +++++--- 3 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 src/components/CodexOauthAccountQuota.tsx diff --git a/src/components/CodexOauthAccountQuota.tsx b/src/components/CodexOauthAccountQuota.tsx new file mode 100644 index 000000000..c2c3db1df --- /dev/null +++ b/src/components/CodexOauthAccountQuota.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Loader2 } from "lucide-react"; +import { useCodexOauthQuotaByAccountId } from "@/lib/query/subscription"; +import { SubscriptionQuotaView } from "@/components/SubscriptionQuotaFooter"; + +interface CodexOauthAccountQuotaProps { + /** cc-switch 自管的 ChatGPT 账号 ID */ + accountId: string; +} + +/** + * 设置 → 认证中心里,单个 ChatGPT (Codex OAuth) 账号的用量展示。 + * + * 直接按 accountId 查询 cc-switch 自管 OAuth token 的订阅额度,复用 + * `SubscriptionQuotaView` 的展开布局(进度条 + 重置倒计时 + 刷新按钮), + * 因此与供应商卡片里的额度展示保持完全一致的观感与状态处理。 + * + * 面板打开时拉取一次,不轮询;用户可点卡片内的刷新按钮手动更新。 + */ +const CodexOauthAccountQuota: React.FC = ({ + accountId, +}) => { + const { + data: quota, + isFetching: loading, + refetch, + } = useCodexOauthQuotaByAccountId(accountId, { + enabled: true, + autoQuery: false, + }); + + // 首次加载占位:SubscriptionQuotaView 在 quota 未就绪时返回 null, + // 这里给一个语言无关的 spinner,避免开面板后出现空白跳变。 + if (loading && !quota) { + return ( +
+ +
+ ); + } + + return ( + + ); +}; + +export default CodexOauthAccountQuota; diff --git a/src/components/providers/forms/CodexOAuthSection.tsx b/src/components/providers/forms/CodexOAuthSection.tsx index 603939ad6..df8b3ce67 100644 --- a/src/components/providers/forms/CodexOAuthSection.tsx +++ b/src/components/providers/forms/CodexOAuthSection.tsx @@ -24,6 +24,7 @@ import { } from "lucide-react"; import { useCodexOauth } from "./hooks/useCodexOauth"; import { copyText } from "@/lib/clipboard"; +import CodexOauthAccountQuota from "@/components/CodexOauthAccountQuota"; interface CodexOAuthSectionProps { className?: string; @@ -178,47 +179,50 @@ export const CodexOAuthSection: React.FC = ({ {accounts.map((account) => (
-
- - {account.login} - {defaultAccountId === account.id && ( - - {t("codexOauth.defaultAccount", "默认")} - - )} - {selectedAccountId === account.id && ( - - {t("codexOauth.selected", "已选中")} - - )} -
-
- {defaultAccountId !== account.id && ( +
+
+ + {account.login} + {defaultAccountId === account.id && ( + + {t("codexOauth.defaultAccount", "默认")} + + )} + {selectedAccountId === account.id && ( + + {t("codexOauth.selected", "已选中")} + + )} +
+
+ {defaultAccountId !== account.id && ( + + )} - )} - +
+
))}
diff --git a/src/lib/query/subscription.ts b/src/lib/query/subscription.ts index 7da5a7485..47e5ef3a7 100644 --- a/src/lib/query/subscription.ts +++ b/src/lib/query/subscription.ts @@ -45,20 +45,18 @@ export interface UseCodexOauthQuotaOptions { } /** - * Codex OAuth (ChatGPT Plus/Pro 反代) 订阅额度查询 hook + * Codex OAuth 订阅额度查询 hook(按账号 ID) * - * 与 `useSubscriptionQuota` 平行:数据走 cc-switch 自管的 OAuth token, - * 而不是 Codex CLI 的 ~/.codex/auth.json。 - * - * Query key 包含 accountId,多张卡片绑定到同一账号时会自动去重共享请求。 + * 直接以 cc-switch 自管的 ChatGPT 账号 ID 查询额度,供认证中心里逐个账号 + * 展示用量时复用。Query key 与 `useCodexOauthQuota` 一致,绑定到同一账号的 + * 供应商卡片与账号列表会自动去重共享同一份请求缓存。 * accountId 为 null 时使用 "default" 占位,让后端 fallback 到默认账号。 */ -export function useCodexOauthQuota( - meta: ProviderMeta | undefined, +export function useCodexOauthQuotaByAccountId( + accountId: string | null, options: UseCodexOauthQuotaOptions = {}, ) { const { enabled = true, autoQuery = false } = options; - const accountId = resolveManagedAccountId(meta, PROVIDER_TYPES.CODEX_OAUTH); return useQuery({ queryKey: ["codex_oauth", "quota", accountId ?? "default"], queryFn: () => subscriptionApi.getCodexOauthQuota(accountId), @@ -70,3 +68,18 @@ export function useCodexOauthQuota( retry: 1, }); } + +/** + * Codex OAuth (ChatGPT Plus/Pro 反代) 订阅额度查询 hook + * + * 与 `useSubscriptionQuota` 平行:数据走 cc-switch 自管的 OAuth token, + * 而不是 Codex CLI 的 ~/.codex/auth.json。账号 ID 从供应商 meta 的 + * authBinding 中解析,再委托给 `useCodexOauthQuotaByAccountId`。 + */ +export function useCodexOauthQuota( + meta: ProviderMeta | undefined, + options: UseCodexOauthQuotaOptions = {}, +) { + const accountId = resolveManagedAccountId(meta, PROVIDER_TYPES.CODEX_OAUTH); + return useCodexOauthQuotaByAccountId(accountId, options); +}