feat(claude-code): 支持通过 /api/oauth/usage 查询账号 5H/周额度并在号池展示

This commit is contained in:
dalamudx
2026-09-29 22:40:44 +08:00
parent 00a315e3bf
commit 8093899b5a
15 changed files with 653 additions and 33 deletions
@@ -1,4 +1,10 @@
export const legacyAdminEnglishMessages: Record<string, string> = {
'响应中未包含配额信息': 'The response contains no quota information',
'响应中未包含额度窗口': 'The response contains no quota windows',
'oauth/usage 请求执行失败': 'oauth/usage request failed',
'oauth/usage 返回 401,Token 可能已失效,请刷新 Token': 'oauth/usage returned 401. The token may have expired; please refresh it',
'oauth/usage 返回 403,该账号缺少 user:profile 权限(如 Setup Token),无法查询额度': 'oauth/usage returned 403. This account lacks the user:profile scope (e.g. a Setup Token), so quota cannot be queried',
'oauth/usage 被限流,请稍后重试': 'oauth/usage was rate limited; please retry later',
'配置额度、流量限制、会员权益和组合套餐': 'Configure credit, usage limits, membership benefits, and bundled plans',
'新建套餐': 'Create plan',
'套餐列表': 'Plans',
+11
View File
@@ -6,6 +6,11 @@ import { legacyGuideEnglishMessages } from './legacy-guide-messages'
export const messages = {
'zh-CN': {
'common.loading': '加载中...',
'poolQuota.claudeCode.window5h': '5H',
'poolQuota.claudeCode.weekly': '周',
'poolQuota.claudeCode.weeklySonnet': '周 Sonnet',
'poolQuota.claudeCode.weeklyFable': '周 Fable',
'poolQuota.claudeCode.unknownWindow': '额度',
'common.confirmRead': '确认已读',
'common.confirming': '确认中...',
'common.copy': '复制',
@@ -491,6 +496,11 @@ export const messages = {
},
'en-US': {
'common.loading': 'Loading...',
'poolQuota.claudeCode.window5h': '5H',
'poolQuota.claudeCode.weekly': 'Weekly',
'poolQuota.claudeCode.weeklySonnet': 'Weekly Sonnet',
'poolQuota.claudeCode.weeklyFable': 'Weekly Fable',
'poolQuota.claudeCode.unknownWindow': 'Quota',
'common.confirmRead': 'Confirm read',
'common.confirming': 'Confirming...',
'common.copy': 'Copy',
@@ -3066,6 +3076,7 @@ const legacyDynamicPatterns: Array<[RegExp, (match: RegExpMatchArray) => string]
[/^(1[0-2]|[1-9])月$/u, match => new Intl.DateTimeFormat('en-US', { month: 'short', timeZone: 'UTC' }).format(new Date(Date.UTC(2020, Number(match[1]) - 1, 1)))],
[/^总可用:(.+)$/u, match => `Total available: ${match[1]}`],
[/^余额:(.+)$/u, match => `Balance: ${match[1]}`],
[/^oauth\/usage 返回状态码 (\d+)$/u, match => `oauth/usage returned status ${match[1]}`],
[/^(.+) 秒$/u, match => `${translateLegacyText(match[1], 'en-US')}s`],
[/^(.+) 分钟$/u, match => `${translateLegacyText(match[1], 'en-US')} min`],
[/^(.+) 小时$/u, match => `${translateLegacyText(match[1], 'en-US')} h`],
@@ -1071,6 +1071,7 @@ import { useConfirm } from '@/composables/useConfirm'
import { useRouteQuery } from '@/composables/useRouteQuery'
import { useBatchSelection } from '@/composables/useBatchSelection'
import { useI18n } from '@/i18n'
import type { MessageKey } from '@/i18n/messages'
import { parseApiError } from '@/utils/errorParser'
import {
getPoolOverview,
@@ -1696,6 +1697,7 @@ watch(showAdaptiveHotPoolMetricsButton, (enabled) => {
const showAccountQuotaColumn = computed(() => {
return selectedProviderType.value === 'codex'
|| selectedProviderType.value === 'claude_code'
|| selectedProviderType.value === 'gemini_cli'
|| selectedProviderType.value === 'kiro'
|| selectedProviderType.value === 'windsurf'
@@ -2146,6 +2148,7 @@ function getPoolKeyAccountStatsMetrics(key: PoolKeyDetail): PoolStatsMetric[] {
const quotaRefreshSupported = computed(() => {
return selectedProviderType.value === 'codex'
|| selectedProviderType.value === 'claude_code'
|| selectedProviderType.value === 'kiro'
|| selectedProviderType.value === 'gemini_cli'
|| selectedProviderType.value === 'windsurf'
@@ -3902,6 +3905,33 @@ function buildQuotaProgressItemsFromSnapshot(key: PoolKeyDetail): QuotaProgressI
.filter((item): item is QuotaProgressItem => item != null)
}
if (providerType === 'claude_code') {
const quotaResetAtSeconds = getQuotaSnapshotResetAtSeconds(quota)
const quotaResetSeconds = getQuotaSnapshotResetSeconds(quota)
const windowPresentations: Record<string, { labelKey: MessageKey, sortOrder: number }> = {
'5h': { labelKey: 'poolQuota.claudeCode.window5h', sortOrder: 0 },
weekly: { labelKey: 'poolQuota.claudeCode.weekly', sortOrder: 1 },
weekly_sonnet: { labelKey: 'poolQuota.claudeCode.weeklySonnet', sortOrder: 2 },
weekly_fable: { labelKey: 'poolQuota.claudeCode.weeklyFable', sortOrder: 3 },
}
return (quota.windows ?? [])
.map((window): QuotaProgressItem | null => {
const remainingPercent = getQuotaWindowRemainingPercent(window)
if (remainingPercent == null) return null
const presentation = windowPresentations[String(window.code || '')]
return {
label: t(presentation?.labelKey ?? 'poolQuota.claudeCode.unknownWindow'),
sortOrder: presentation?.sortOrder ?? 9,
remainingPercent,
resetAtSeconds: normalizeUnixSeconds(window.reset_at ?? quotaResetAtSeconds ?? null),
resetSeconds: normalizeRemainingSeconds(window.reset_seconds ?? quotaResetSeconds ?? null),
updatedAtSeconds: getQuotaSnapshotUpdatedAtSeconds(quota),
allowDynamicReset: true,
}
})
.filter((item): item is QuotaProgressItem => item != null)
}
if (providerType === 'gemini_cli') {
const windows = getQuotaSnapshotWindowsByScope(quota, 'model')
if (windows.length === 0) return []