diff --git a/frontend/src/api/admin/claudeResetCredits.ts b/frontend/src/api/admin/claudeResetCredits.ts new file mode 100644 index 0000000000..68edb5afb9 --- /dev/null +++ b/frontend/src/api/admin/claudeResetCredits.ts @@ -0,0 +1,28 @@ +import { apiClient } from '../client' + +export interface ClaudeResetCredit { + selection_token: string + label: string + resets_left: number + starts_at?: string + expires_at?: string + clears: string[] + percent_used: Record + blocking: string[] + use_requires_limit: boolean + redeemable: boolean +} + +export interface ClaudeResetCredits { + eligible: boolean + available_count: number + credits: ClaudeResetCredit[] + cooldown_until?: string + weekly_resets_at?: string + fetched_at: string +} + +export async function getClaudeResetCredits(id: number): Promise { + const { data } = await apiClient.get(`/admin/accounts/${id}/claude/reset-credits`) + return data +} diff --git a/frontend/src/components/account/AccountUsageCell.vue b/frontend/src/components/account/AccountUsageCell.vue index 36e708e08a..8c57bafbf8 100644 --- a/frontend/src/components/account/AccountUsageCell.vue +++ b/frontend/src/components/account/AccountUsageCell.vue @@ -114,6 +114,7 @@
-
+ @@ -677,6 +678,7 @@ import { enqueueUsageRequest } from '@/utils/usageLoadQueue' import { formatCompactNumber } from '@/utils/format' import UsageProgressBar from './UsageProgressBar.vue' import AccountQuotaInfo from './AccountQuotaInfo.vue' +import ClaudeResetCreditsCell from './ClaudeResetCreditsCell.vue' import OpenAIQuotaResetCell from './OpenAIQuotaResetCell.vue' import GrokQuotaProbeCell from './GrokQuotaProbeCell.vue' import CNProviderQuotaCell from './CNProviderQuotaCell.vue' diff --git a/frontend/src/components/account/ClaudeResetCreditsCell.vue b/frontend/src/components/account/ClaudeResetCreditsCell.vue new file mode 100644 index 0000000000..f462b8e720 --- /dev/null +++ b/frontend/src/components/account/ClaudeResetCreditsCell.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/account/__tests__/ClaudeResetCreditsCell.spec.ts b/frontend/src/components/account/__tests__/ClaudeResetCreditsCell.spec.ts new file mode 100644 index 0000000000..4bdbe52f20 --- /dev/null +++ b/frontend/src/components/account/__tests__/ClaudeResetCreditsCell.spec.ts @@ -0,0 +1,32 @@ +import { mount, flushPromises } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import ClaudeResetCreditsCell from '../ClaudeResetCreditsCell.vue' +import type { Account } from '@/types' +const getCredits = vi.hoisted(() => vi.fn()) +vi.mock('@/api/admin/claudeResetCredits', () => ({ getClaudeResetCredits: getCredits })) +vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) })) +const account = { id: 1, platform: 'anthropic', type: 'oauth' } as Account +const snapshot = { eligible: true, available_count: 1, credits: [], fetched_at: '2026-09-25T00:00:00Z' } +describe('Claude reset credit status', () => { + beforeEach(() => getCredits.mockReset()) + it('queries only on explicit request and renders fetched data', async () => { + getCredits.mockResolvedValue(snapshot) + const wrapper = mount(ClaudeResetCreditsCell, { props: { account } }) + expect(getCredits).not.toHaveBeenCalled() + await wrapper.get('button').trigger('click') + await flushPromises() + expect(getCredits).toHaveBeenCalledWith(1) + expect(wrapper.text()).toContain('claudeResetCredits.count') + }) + it('hides setup tokens and discards responses after account changes', async () => { + let resolve!: (value: typeof snapshot) => void + getCredits.mockReturnValue(new Promise(r => { resolve = r })) + const wrapper = mount(ClaudeResetCreditsCell, { props: { account } }) + await wrapper.get('button').trigger('click') + await wrapper.setProps({ account: { ...account, id: 2, type: 'setup-token' } }) + resolve(snapshot) + await flushPromises() + expect(wrapper.find('button').exists()).toBe(false) + expect(wrapper.text()).not.toContain('claudeResetCredits.count') + }) +}) diff --git a/frontend/src/i18n/locales/en/admin/accounts.ts b/frontend/src/i18n/locales/en/admin/accounts.ts index 40fd7b6f0b..9819f0544b 100644 --- a/frontend/src/i18n/locales/en/admin/accounts.ts +++ b/frontend/src/i18n/locales/en/admin/accounts.ts @@ -925,7 +925,8 @@ export default { autoPause5hDisabled: 'Disable 5h auto-pause', autoPause7dDisabled: 'Disable 7d auto-pause', autoPauseDisabledHint: 'When enabled, this account is never auto-paused (even if a global default threshold is configured).', - autoResetCredit: { + claudeResetCredits: {"query": "Claude reset credits", "error": "Could not refresh reset credits. No reset was performed.", "count": "Available resets: {count}", "ineligible": "This account is not eligible.", "cooldown": "Cooldown until {time}", "weekly": "Weekly window resets at {time}", "expires": "Expires at {time}", "clears": "Clears: {windows}", "fetched": "Checked at {time}"}, + autoResetCredit: { title: 'Automatically use reset credits', hint: 'Uses the earliest-expiring available credit only when actual usage reaches a threshold. Off by default; the account remains paused if no credit is available or reset fails.', threshold5h: '5h auto-reset threshold (%)', diff --git a/frontend/src/i18n/locales/zh/admin/accounts.ts b/frontend/src/i18n/locales/zh/admin/accounts.ts index bc374be293..a4c4cf571a 100644 --- a/frontend/src/i18n/locales/zh/admin/accounts.ts +++ b/frontend/src/i18n/locales/zh/admin/accounts.ts @@ -1026,7 +1026,8 @@ export default { autoPause5hDisabled: '禁用 5h 自动暂停', autoPause7dDisabled: '禁用 7d 自动暂停', autoPauseDisabledHint: '开启后该账号永不进入自动暂停(即使全局默认阈值已配置)。', - autoResetCredit: { + claudeResetCredits: {"query": "Claude 重置额度", "error": "无法刷新重置额度,未执行重置。", "count": "可用重置次数:{count}", "ineligible": "此账号不符合使用条件。", "cooldown": "冷却截止:{time}", "weekly": "每周窗口重置时间:{time}", "expires": "到期时间:{time}", "clears": "清除窗口:{windows}", "fetched": "查询时间:{time}"}, + autoResetCredit: { title: '自动使用重置卡', hint: '仅在实际用量达到阈值时使用最早到期的可用卡;默认关闭。无卡或失败时账号保持暂停。', threshold5h: '5h 自动用卡阈值(%)',