mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat: 增强 OAuth 标识展示与 Codex 调试日志
- 优化 OAuth badge 支持 account_id/account_user_id,tooltip 展示完整身份信息 - 重构池管理额度进度条布局,倒计时独立展示并增加样式区分 - Codex 插件增加 token 解析快照日志,便于排查 OAuth 透传问题 Closes #245 Co-Authored-By: kayphoon <109347466+Kayphoon@users.noreply.github.com>
This commit is contained in:
@@ -125,7 +125,7 @@
|
||||
v-if="getOAuthOrgBadge(key)"
|
||||
variant="secondary"
|
||||
class="text-[10px] px-1 py-0 h-4 shrink-0"
|
||||
:title="getOAuthOrgBadge(key)?.id"
|
||||
:title="getOAuthOrgBadge(key)?.title"
|
||||
>{{ getOAuthOrgBadge(key)?.label }}</Badge>
|
||||
<Badge
|
||||
v-if="isBannedKey(key)"
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
v-if="getOAuthOrgBadge(key)"
|
||||
variant="secondary"
|
||||
class="text-[10px] px-1.5 py-0 shrink-0"
|
||||
:title="getOAuthOrgBadge(key)?.id"
|
||||
:title="getOAuthOrgBadge(key)?.title"
|
||||
>
|
||||
{{ getOAuthOrgBadge(key)?.label }}
|
||||
</Badge>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { OAuthOrganizationInfo } from '@/api/endpoints/types/provider'
|
||||
|
||||
type OAuthIdentityDisplayValue = {
|
||||
oauth_account_id?: string | null
|
||||
oauth_account_name?: string | null
|
||||
oauth_account_user_id?: string | null
|
||||
oauth_organizations?: OAuthOrganizationInfo[] | null
|
||||
} | null | undefined
|
||||
|
||||
@@ -16,6 +18,14 @@ function formatOAuthIdentityShort(
|
||||
return `${normalized.slice(0, head)}...${normalized.slice(-tail)}`
|
||||
}
|
||||
|
||||
function readStr(raw: unknown): string {
|
||||
return typeof raw === 'string' ? raw.trim() : ''
|
||||
}
|
||||
|
||||
function formatOAuthAccountBadge(accountId: string): string {
|
||||
return accountId.slice(0, 8)
|
||||
}
|
||||
|
||||
function getPrimaryOAuthOrganization(
|
||||
value: OAuthIdentityDisplayValue,
|
||||
): { id: string; title: string } | null {
|
||||
@@ -44,16 +54,30 @@ function getPrimaryOAuthOrganization(
|
||||
|
||||
export function getOAuthOrgBadge(
|
||||
value: OAuthIdentityDisplayValue,
|
||||
): { id: string; label: string } | null {
|
||||
): { id: string; label: string; title: string } | null {
|
||||
const org = getPrimaryOAuthOrganization(value)
|
||||
if (!org) return null
|
||||
|
||||
const accountName = typeof value?.oauth_account_name === 'string'
|
||||
? value.oauth_account_name.trim()
|
||||
: ''
|
||||
const accountId = readStr(value?.oauth_account_id)
|
||||
const accountName = readStr(value?.oauth_account_name)
|
||||
const accountUserId = readStr(value?.oauth_account_user_id)
|
||||
|
||||
const badgeId = accountId || org?.id || ''
|
||||
const label = accountName
|
||||
|| (accountId ? formatOAuthAccountBadge(accountId) : '')
|
||||
|| (org?.id ? `org:${formatOAuthIdentityShort(org.id, 6, 4)}` : '')
|
||||
if (!badgeId || !label) return null
|
||||
|
||||
const titleParts = [
|
||||
accountName ? `name: ${accountName}` : '',
|
||||
accountId ? `account_id: ${accountId}` : '',
|
||||
accountUserId ? `account_user_id: ${accountUserId}` : '',
|
||||
org?.id ? `org_id: ${org.id}` : '',
|
||||
org?.title ? `org_title: ${org.title}` : '',
|
||||
].filter(Boolean)
|
||||
|
||||
return {
|
||||
id: org.id,
|
||||
label: accountName || org.title || formatOAuthIdentityShort(org.id),
|
||||
id: badgeId,
|
||||
label,
|
||||
title: titleParts.join(' | '),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@
|
||||
v-if="getOAuthOrgBadge(key)"
|
||||
variant="secondary"
|
||||
class="text-[9px] px-1 py-0 h-4 shrink-0"
|
||||
:title="getOAuthOrgBadge(key)?.id"
|
||||
:title="getOAuthOrgBadge(key)?.title"
|
||||
>
|
||||
{{ getOAuthOrgBadge(key)?.label }}
|
||||
</Badge>
|
||||
@@ -509,42 +509,43 @@
|
||||
>
|
||||
<div
|
||||
v-if="quotaProgressMap[key.key_id]?.length"
|
||||
class="space-y-1 max-w-[220px]"
|
||||
class="max-w-[208px] space-y-2"
|
||||
>
|
||||
<div
|
||||
v-for="(item, idx) in quotaProgressMap[key.key_id].slice(0, 2)"
|
||||
:key="`${key.key_id}-quota-${idx}`"
|
||||
class="w-full"
|
||||
class="space-y-0.5"
|
||||
>
|
||||
<div class="min-h-4 grid grid-cols-[20px_minmax(0,1fr)_80px] items-center gap-1 text-[10px] leading-tight">
|
||||
<div class="flex items-center justify-between gap-2 min-w-0">
|
||||
<span
|
||||
class="text-muted-foreground whitespace-nowrap text-right tabular-nums"
|
||||
class="shrink-0 text-[10px] leading-none text-muted-foreground tabular-nums whitespace-nowrap"
|
||||
:title="getQuotaProgressTooltip(item)"
|
||||
>
|
||||
{{ getQuotaProgressLabel(item.label) }}
|
||||
</span>
|
||||
<div class="relative flex-1 h-1.5 bg-border rounded-full overflow-hidden">
|
||||
<span
|
||||
v-if="getQuotaProgressDisplayText(item)"
|
||||
class="min-w-0 truncate text-[9px] leading-none tabular-nums text-right"
|
||||
:class="getQuotaProgressDisplayClass(item)"
|
||||
:title="getQuotaProgressTooltip(item)"
|
||||
>
|
||||
{{ getQuotaProgressDisplayText(item) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_50px] items-center gap-2">
|
||||
<div class="relative h-1.5 rounded-full bg-border/80 overflow-hidden">
|
||||
<div
|
||||
class="absolute left-0 top-0 h-full transition-all duration-300"
|
||||
class="absolute left-0 top-0 h-full rounded-full transition-all duration-300"
|
||||
:class="getQuotaRemainingBarColorByRemaining(item.remainingPercent)"
|
||||
:style="{ width: `${item.remainingPercent}%` }"
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<span
|
||||
class="text-[11px] font-medium leading-none tabular-nums whitespace-nowrap text-right"
|
||||
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
|
||||
>
|
||||
{{ item.remainingPercent.toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -815,7 +816,7 @@
|
||||
v-if="getOAuthOrgBadge(key)"
|
||||
variant="secondary"
|
||||
class="text-[9px] px-1 py-0 h-4 shrink-0"
|
||||
:title="getOAuthOrgBadge(key)?.id"
|
||||
:title="getOAuthOrgBadge(key)?.title"
|
||||
>
|
||||
{{ getOAuthOrgBadge(key)?.label }}
|
||||
</Badge>
|
||||
@@ -973,42 +974,43 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="quotaProgressMap[key.key_id]?.length"
|
||||
class="space-y-1"
|
||||
class="space-y-2"
|
||||
>
|
||||
<div
|
||||
v-for="(item, idx) in quotaProgressMap[key.key_id]"
|
||||
:key="`${key.key_id}-quota-mobile-${idx}`"
|
||||
class="w-full"
|
||||
class="space-y-0.5"
|
||||
>
|
||||
<div class="grid grid-cols-[20px_minmax(0,1fr)_80px] items-center gap-1 text-[10px] leading-tight">
|
||||
<div class="flex items-center justify-between gap-2 min-w-0">
|
||||
<span
|
||||
class="text-muted-foreground whitespace-nowrap text-right tabular-nums"
|
||||
class="shrink-0 text-[10px] leading-none text-muted-foreground tabular-nums whitespace-nowrap"
|
||||
:title="getQuotaProgressTooltip(item)"
|
||||
>
|
||||
{{ getQuotaProgressLabel(item.label) }}
|
||||
</span>
|
||||
<div class="relative flex-1 h-1.5 bg-border rounded-full overflow-hidden">
|
||||
<span
|
||||
v-if="getQuotaProgressDisplayText(item)"
|
||||
class="min-w-0 truncate text-[9px] leading-none tabular-nums text-right"
|
||||
:class="getQuotaProgressDisplayClass(item)"
|
||||
:title="getQuotaProgressTooltip(item)"
|
||||
>
|
||||
{{ getQuotaProgressDisplayText(item) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_50px] items-center gap-2">
|
||||
<div class="relative h-1.5 rounded-full bg-border/80 overflow-hidden">
|
||||
<div
|
||||
class="absolute left-0 top-0 h-full transition-all duration-300"
|
||||
class="absolute left-0 top-0 h-full rounded-full transition-all duration-300"
|
||||
:class="getQuotaRemainingBarColorByRemaining(item.remainingPercent)"
|
||||
:style="{ width: `${item.remainingPercent}%` }"
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<span
|
||||
class="text-[11px] font-medium leading-none tabular-nums whitespace-nowrap text-right"
|
||||
:class="getQuotaRemainingClassByRemaining(item.remainingPercent)"
|
||||
>
|
||||
{{ item.remainingPercent.toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2456,6 +2458,32 @@ function getQuotaProgressCountdownText(item: QuotaProgressItem): string {
|
||||
return status.isExpired ? status.text : `${status.text} 后重置`
|
||||
}
|
||||
|
||||
function formatCompactQuotaCountdownText(text: string): string {
|
||||
const normalized = text.trim()
|
||||
const dayMatch = normalized.match(/^(\d+)天\s+(.+?)(?:\s+后重置)?$/)
|
||||
if (dayMatch) {
|
||||
return `${dayMatch[1]}天 ${dayMatch[2]}`
|
||||
}
|
||||
return normalized.replace(/\s+后重置$/, '')
|
||||
}
|
||||
|
||||
function getQuotaProgressDisplayText(item: QuotaProgressItem): string {
|
||||
const countdownText = getQuotaProgressCountdownText(item)
|
||||
if (countdownText) return formatCompactQuotaCountdownText(countdownText)
|
||||
// remainingPercent >= 100 时 getCodexResetCountdown 返回 null,倒计时为空,
|
||||
// 但 UI 仍需展示"满额未消耗"的占位文本
|
||||
if ((item.label === '5H' || item.label === '周') && item.remainingPercent >= 100) return '0天 00:00:00'
|
||||
return item.detail?.trim() || ''
|
||||
}
|
||||
|
||||
function getQuotaProgressDisplayClass(item: QuotaProgressItem): string {
|
||||
const status = getQuotaProgressCountdown(item)
|
||||
if (!status || status.isExpired) return 'text-muted-foreground/70'
|
||||
if (status.isCritical) return 'text-destructive font-medium animate-pulse'
|
||||
if (status.isUrgent) return 'text-amber-500 dark:text-amber-400'
|
||||
return 'text-muted-foreground/70'
|
||||
}
|
||||
|
||||
function getQuotaProgressTooltip(item: QuotaProgressItem): string {
|
||||
if ((item.label === '5H' || item.label === '周') && item.remainingPercent >= 100) {
|
||||
return ''
|
||||
|
||||
Reference in New Issue
Block a user