feat: OpenAI OAuth账号显示Codex使用量
从响应头提取x-codex-*使用量信息并保存到账号Extra字段, 前端账号列表展示5h/7d窗口的使用进度条。
This commit is contained in:
@@ -68,7 +68,31 @@
|
||||
<SetupTokenTimeWindow :account="account" />
|
||||
</template>
|
||||
|
||||
<!-- OpenAI accounts: no usage window API, show dash -->
|
||||
<!-- OpenAI OAuth accounts: show Codex usage from extra field -->
|
||||
<template v-else-if="account.platform === 'openai' && account.type === 'oauth'">
|
||||
<div v-if="hasCodexUsage" class="space-y-1">
|
||||
<!-- 5h Window (Secondary) -->
|
||||
<UsageProgressBar
|
||||
v-if="codexSecondaryUsedPercent !== null"
|
||||
label="5h"
|
||||
:utilization="codexSecondaryUsedPercent"
|
||||
:resets-at="codexSecondaryResetAt"
|
||||
color="indigo"
|
||||
/>
|
||||
|
||||
<!-- Weekly Window (Primary) -->
|
||||
<UsageProgressBar
|
||||
v-if="codexPrimaryUsedPercent !== null"
|
||||
label="7d"
|
||||
:utilization="codexPrimaryUsedPercent"
|
||||
:resets-at="codexPrimaryResetAt"
|
||||
color="emerald"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="text-xs text-gray-400">-</div>
|
||||
</template>
|
||||
|
||||
<!-- Other accounts: no usage window -->
|
||||
<template v-else>
|
||||
<div class="text-xs text-gray-400">-</div>
|
||||
</template>
|
||||
@@ -100,9 +124,44 @@ const showUsageWindows = computed(() =>
|
||||
props.account.type === 'oauth' || props.account.type === 'setup-token'
|
||||
)
|
||||
|
||||
// OpenAI Codex usage computed properties
|
||||
const hasCodexUsage = computed(() => {
|
||||
const extra = props.account.extra
|
||||
return extra && (
|
||||
extra.codex_primary_used_percent !== undefined ||
|
||||
extra.codex_secondary_used_percent !== undefined
|
||||
)
|
||||
})
|
||||
|
||||
const codexPrimaryUsedPercent = computed(() => {
|
||||
const extra = props.account.extra
|
||||
if (!extra || extra.codex_primary_used_percent === undefined) return null
|
||||
return extra.codex_primary_used_percent
|
||||
})
|
||||
|
||||
const codexSecondaryUsedPercent = computed(() => {
|
||||
const extra = props.account.extra
|
||||
if (!extra || extra.codex_secondary_used_percent === undefined) return null
|
||||
return extra.codex_secondary_used_percent
|
||||
})
|
||||
|
||||
const codexPrimaryResetAt = computed(() => {
|
||||
const extra = props.account.extra
|
||||
if (!extra || extra.codex_primary_reset_after_seconds === undefined) return null
|
||||
const resetTime = new Date(Date.now() + extra.codex_primary_reset_after_seconds * 1000)
|
||||
return resetTime.toISOString()
|
||||
})
|
||||
|
||||
const codexSecondaryResetAt = computed(() => {
|
||||
const extra = props.account.extra
|
||||
if (!extra || extra.codex_secondary_reset_after_seconds === undefined) return null
|
||||
const resetTime = new Date(Date.now() + extra.codex_secondary_reset_after_seconds * 1000)
|
||||
return resetTime.toISOString()
|
||||
})
|
||||
|
||||
const loadUsage = async () => {
|
||||
// Only fetch usage for Anthropic OAuth accounts
|
||||
// OpenAI doesn't have a usage window API - usage is updated from response headers during forwarding
|
||||
// OpenAI usage comes from account.extra field (updated during forwarding)
|
||||
if (props.account.platform !== 'anthropic' || props.account.type !== 'oauth') return
|
||||
|
||||
loading.value = true
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-if="tooltipOpen"
|
||||
class="absolute right-0 mt-2 w-80 bg-white dark:bg-dark-800 rounded-xl shadow-xl border border-gray-200 dark:border-dark-700 z-50"
|
||||
class="absolute right-0 mt-2 w-[340px] bg-white dark:bg-dark-800 rounded-xl shadow-xl border border-gray-200 dark:border-dark-700 z-50 overflow-hidden"
|
||||
>
|
||||
<div class="p-3 border-b border-gray-100 dark:border-dark-700">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
@@ -62,43 +62,43 @@
|
||||
<!-- Progress bars -->
|
||||
<div class="space-y-1.5">
|
||||
<div v-if="subscription.group?.daily_limit_usd" class="flex items-center gap-2">
|
||||
<span class="text-[10px] text-gray-500 w-8">{{ t('subscriptionProgress.daily') }}</span>
|
||||
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<span class="text-[10px] text-gray-500 w-8 flex-shrink-0">{{ t('subscriptionProgress.daily') }}</span>
|
||||
<div class="flex-1 min-w-0 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all"
|
||||
:class="getProgressBarClass(subscription.daily_usage_usd, subscription.group?.daily_limit_usd)"
|
||||
:style="{ width: getProgressWidth(subscription.daily_usage_usd, subscription.group?.daily_limit_usd) }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-[10px] text-gray-500 w-16 text-right">
|
||||
<span class="text-[10px] text-gray-500 w-24 text-right flex-shrink-0">
|
||||
{{ formatUsage(subscription.daily_usage_usd, subscription.group?.daily_limit_usd) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="subscription.group?.weekly_limit_usd" class="flex items-center gap-2">
|
||||
<span class="text-[10px] text-gray-500 w-8">{{ t('subscriptionProgress.weekly') }}</span>
|
||||
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<span class="text-[10px] text-gray-500 w-8 flex-shrink-0">{{ t('subscriptionProgress.weekly') }}</span>
|
||||
<div class="flex-1 min-w-0 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all"
|
||||
:class="getProgressBarClass(subscription.weekly_usage_usd, subscription.group?.weekly_limit_usd)"
|
||||
:style="{ width: getProgressWidth(subscription.weekly_usage_usd, subscription.group?.weekly_limit_usd) }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-[10px] text-gray-500 w-16 text-right">
|
||||
<span class="text-[10px] text-gray-500 w-24 text-right flex-shrink-0">
|
||||
{{ formatUsage(subscription.weekly_usage_usd, subscription.group?.weekly_limit_usd) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="subscription.group?.monthly_limit_usd" class="flex items-center gap-2">
|
||||
<span class="text-[10px] text-gray-500 w-8">{{ t('subscriptionProgress.monthly') }}</span>
|
||||
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<span class="text-[10px] text-gray-500 w-8 flex-shrink-0">{{ t('subscriptionProgress.monthly') }}</span>
|
||||
<div class="flex-1 min-w-0 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all"
|
||||
:class="getProgressBarClass(subscription.monthly_usage_usd, subscription.group?.monthly_limit_usd)"
|
||||
:style="{ width: getProgressWidth(subscription.monthly_usage_usd, subscription.group?.monthly_limit_usd) }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-[10px] text-gray-500 w-16 text-right">
|
||||
<span class="text-[10px] text-gray-500 w-24 text-right flex-shrink-0">
|
||||
{{ formatUsage(subscription.monthly_usage_usd, subscription.group?.monthly_limit_usd) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -316,6 +316,7 @@ export interface Account {
|
||||
platform: AccountPlatform;
|
||||
type: AccountType;
|
||||
credentials?: Record<string, unknown>;
|
||||
extra?: CodexUsageSnapshot & Record<string, unknown>; // Extra fields including Codex usage
|
||||
proxy_id: number | null;
|
||||
concurrency: number;
|
||||
priority: number;
|
||||
@@ -361,6 +362,18 @@ export interface AccountUsageInfo {
|
||||
seven_day_sonnet: UsageProgress | null;
|
||||
}
|
||||
|
||||
// OpenAI Codex usage snapshot (from response headers)
|
||||
export interface CodexUsageSnapshot {
|
||||
codex_primary_used_percent?: number; // Weekly limit usage percentage
|
||||
codex_primary_reset_after_seconds?: number; // Seconds until weekly reset
|
||||
codex_primary_window_minutes?: number; // Weekly window in minutes
|
||||
codex_secondary_used_percent?: number; // 5h limit usage percentage
|
||||
codex_secondary_reset_after_seconds?: number; // Seconds until 5h reset
|
||||
codex_secondary_window_minutes?: number; // 5h window in minutes
|
||||
codex_primary_over_secondary_percent?: number; // Overflow ratio
|
||||
codex_usage_updated_at?: string; // Last update timestamp
|
||||
}
|
||||
|
||||
export interface CreateAccountRequest {
|
||||
name: string;
|
||||
platform: AccountPlatform;
|
||||
|
||||
Reference in New Issue
Block a user