feat: Codex account_name 透传并优化池列表 OAuth 标识与额度重置倒计时展示

(cherry picked from commit 1b9176fc4a)
This commit is contained in:
kayphoon
2026-03-18 20:58:33 +08:00
committed by fawney19
parent 8d8cddcef6
commit 6e55968487
16 changed files with 888 additions and 251 deletions

View File

@@ -104,6 +104,7 @@ export interface PoolKeyDetail {
oauth_plan_type?: string | null
oauth_account_id?: string | null
oauth_account_user_id?: string | null
oauth_account_name?: string | null
oauth_organizations?: OAuthOrganizationInfo[] | null
quota_updated_at?: number | null
health_score?: number

View File

@@ -288,6 +288,7 @@ export interface EndpointAPIKey {
oauth_plan_type?: string | null // Codex 订阅类型: plus/free/team/enterprise
oauth_account_id?: string | null // Codex ChatGPT 账号 ID
oauth_account_user_id?: string | null // Codex ChatGPT account-user 联合 ID
oauth_account_name?: string | null
oauth_organizations?: OAuthOrganizationInfo[] | null // OAuth 关联组织/工作区摘要
oauth_invalid_at?: number | null // OAuth Token 失效时间Unix 时间戳)
oauth_invalid_reason?: string | null // OAuth Token 失效原因

View File

@@ -1,5 +1,10 @@
import type { OAuthOrganizationInfo } from '@/api/endpoints/types/provider'
type OAuthIdentityDisplayValue = {
oauth_account_name?: string | null
oauth_organizations?: OAuthOrganizationInfo[] | null
} | null | undefined
function formatOAuthIdentityShort(
value: string | null | undefined,
head = 8,
@@ -11,24 +16,44 @@ function formatOAuthIdentityShort(
return `${normalized.slice(0, head)}...${normalized.slice(-tail)}`
}
function getPrimaryOAuthOrganizationId(
value: { oauth_organizations?: OAuthOrganizationInfo[] | null } | null | undefined,
): string | null {
const organizations = Array.isArray(value?.oauth_organizations) ? value.oauth_organizations : []
const defaultOrg = organizations.find(
(org) => org?.is_default && typeof org?.id === 'string' && org.id.trim(),
)
if (defaultOrg?.id) return defaultOrg.id.trim()
const firstWithId = organizations.find(
(org) => typeof org?.id === 'string' && org.id.trim(),
)
return firstWithId?.id?.trim() || null
function getPrimaryOAuthOrganization(
value: OAuthIdentityDisplayValue,
): { id: string; title: string } | null {
const organizations: OAuthOrganizationInfo[] = Array.isArray(value?.oauth_organizations)
? value.oauth_organizations
: []
let firstWithId: OAuthOrganizationInfo | null = null
for (let index = 0; index < organizations.length; index += 1) {
const org = organizations[index]
if (typeof org?.id !== 'string' || !org.id.trim()) continue
if (!firstWithId) firstWithId = org
if (org.is_default) {
firstWithId = org
break
}
}
if (!firstWithId?.id) return null
return {
id: firstWithId.id.trim(),
title: typeof firstWithId.title === 'string' ? firstWithId.title.trim() : '',
}
}
export function getOAuthOrgBadge(
value: { oauth_organizations?: OAuthOrganizationInfo[] | null } | null | undefined,
value: OAuthIdentityDisplayValue,
): { id: string; label: string } | null {
const id = getPrimaryOAuthOrganizationId(value)
if (!id) return null
return { id, label: formatOAuthIdentityShort(id) }
const org = getPrimaryOAuthOrganization(value)
if (!org) return null
const accountName = typeof value?.oauth_account_name === 'string'
? value.oauth_account_name.trim()
: ''
return {
id: org.id,
label: accountName || org.title || formatOAuthIdentityShort(org.id),
}
}

View File

@@ -516,7 +516,7 @@
:key="`${key.key_id}-quota-${idx}`"
class="w-full"
>
<div class="min-h-4 grid grid-cols-[20px_minmax(0,1fr)_42px] items-center gap-1 text-[10px] leading-tight">
<div class="min-h-4 grid grid-cols-[20px_minmax(0,1fr)_80px] items-center gap-1 text-[10px] leading-tight">
<span
class="text-muted-foreground whitespace-nowrap text-right tabular-nums"
:title="getQuotaProgressTooltip(item)"
@@ -530,12 +530,21 @@
:style="{ width: `${item.remainingPercent}%` }"
/>
</div>
<span
class="tabular-nums text-right whitespace-nowrap"
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
>
{{ item.remainingPercent.toFixed(1) }}%
</span>
<div class="flex flex-col items-end justify-center gap-0.5 text-right leading-tight">
<span
class="tabular-nums whitespace-nowrap"
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
>
{{ item.remainingPercent.toFixed(1) }}%
</span>
<span
v-if="getQuotaProgressCountdownText(item)"
class="text-[9px] text-muted-foreground whitespace-nowrap"
:title="getQuotaProgressTooltip(item)"
>
{{ getQuotaProgressCountdownText(item) }}
</span>
</div>
</div>
</div>
</div>
@@ -971,7 +980,7 @@
:key="`${key.key_id}-quota-mobile-${idx}`"
class="w-full"
>
<div class="grid grid-cols-[20px_minmax(0,1fr)_42px] items-center gap-1 text-[10px] leading-tight">
<div class="grid grid-cols-[20px_minmax(0,1fr)_80px] items-center gap-1 text-[10px] leading-tight">
<span
class="text-muted-foreground whitespace-nowrap text-right tabular-nums"
:title="getQuotaProgressTooltip(item)"
@@ -985,12 +994,21 @@
:style="{ width: `${item.remainingPercent}%` }"
/>
</div>
<span
class="tabular-nums text-right whitespace-nowrap"
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
>
{{ item.remainingPercent.toFixed(1) }}%
</span>
<div class="flex flex-col items-end justify-center gap-0.5 text-right leading-tight">
<span
class="tabular-nums whitespace-nowrap"
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
>
{{ item.remainingPercent.toFixed(1) }}%
</span>
<span
v-if="getQuotaProgressCountdownText(item)"
class="text-[9px] text-muted-foreground whitespace-nowrap"
:title="getQuotaProgressTooltip(item)"
>
{{ getQuotaProgressCountdownText(item) }}
</span>
</div>
</div>
</div>
</div>
@@ -1743,6 +1761,7 @@ function toEndpointApiKey(key: PoolKeyDetail): EndpointAPIKey {
oauth_plan_type: key.oauth_plan_type ?? null,
oauth_account_id: key.oauth_account_id ?? null,
oauth_account_user_id: key.oauth_account_user_id ?? null,
oauth_account_name: key.oauth_account_name ?? null,
oauth_organizations: key.oauth_organizations ?? [],
oauth_invalid_at: key.oauth_invalid_at ?? null,
oauth_invalid_reason: key.oauth_invalid_reason ?? null,