From 56949a58bc04dcae83a8b5fc8b9bf11b9ecdb16d Mon Sep 17 00:00:00 2001 From: liuxiongfeng Date: Tue, 3 Feb 2026 11:31:35 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(antigravity):=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=BC=80=E5=90=AF=E6=8C=89=E9=85=8D=E9=A2=9D=E5=9F=9F=E9=99=90?= =?UTF-8?q?=E6=B5=81=EF=BC=8C=E9=81=BF=E5=85=8D=E6=95=B4=E4=B8=AA=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E8=A2=AB=E9=94=81=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 GATEWAY_ANTIGRAVITY_429_SCOPE_LIMIT 的默认值从关闭改为开启。 当 Gemini 模型触发 429 限流时,只会限制对应的配额域(gemini_text), 而 Claude 和 gemini_image 仍可继续使用,提高账号利用率。 --- backend/internal/service/antigravity_gateway_service.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go index 3b847bcb..7fd8ef5d 100644 --- a/backend/internal/service/antigravity_gateway_service.go +++ b/backend/internal/service/antigravity_gateway_service.go @@ -1530,7 +1530,11 @@ func sleepAntigravityBackoffWithContext(ctx context.Context, attempt int) bool { func antigravityUseScopeRateLimit() bool { v := strings.ToLower(strings.TrimSpace(os.Getenv(antigravityScopeRateLimitEnv))) - return v == "1" || v == "true" || v == "yes" || v == "on" + // 默认开启按配额域限流,只有明确设置为禁用值时才关闭 + if v == "0" || v == "false" || v == "no" || v == "off" { + return false + } + return true } func (s *AntigravityGatewayService) handleUpstreamError(ctx context.Context, prefix string, account *Account, statusCode int, headers http.Header, body []byte, quotaScope AntigravityQuotaScope) { From f5884d16085ae684d854a1a9bc4a6c1c908a583e Mon Sep 17 00:00:00 2001 From: liuxiongfeng Date: Tue, 3 Feb 2026 12:06:05 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20jsonb=5Fset=20=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=97=A0=E6=B3=95=E5=88=9B=E5=BB=BA=E5=A4=9A?= =?UTF-8?q?=E5=B1=82=20key=20=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL jsonb_set 在 create_if_missing=true 时无法一次性创建多层嵌套路径。 例如设置 {antigravity_quota_scopes,gemini_image} 时,如果 antigravity_quota_scopes 不存在, jsonb_set 不会自动创建外层 key,导致更新静默失败(affected=1 但数据未变)。 修复方案:嵌套两次 jsonb_set,先确保外层 key 存在,再设置内层值。 同时在设置限流时更新 last_used_at,使刚触发 429 的账号调度优先级降低。 Cherry-picked from slovx2/sub2api: 4b57e80e --- backend/internal/repository/account_repo.go | 29 ++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index c11c079b..e4e837e2 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -809,12 +809,21 @@ func (r *accountRepository) SetAntigravityQuotaScopeLimit(ctx context.Context, i return err } - path := "{antigravity_quota_scopes," + string(scope) + "}" + scopeKey := string(scope) client := clientFromContext(ctx, r.client) result, err := client.ExecContext( ctx, - "UPDATE accounts SET extra = jsonb_set(COALESCE(extra, '{}'::jsonb), $1::text[], $2::jsonb, true), updated_at = NOW() WHERE id = $3 AND deleted_at IS NULL", - path, + `UPDATE accounts SET + extra = jsonb_set( + jsonb_set(COALESCE(extra, '{}'::jsonb), '{antigravity_quota_scopes}'::text[], COALESCE(extra->'antigravity_quota_scopes', '{}'::jsonb), true), + ARRAY['antigravity_quota_scopes', $1]::text[], + $2::jsonb, + true + ), + updated_at = NOW(), + last_used_at = NOW() + WHERE id = $3 AND deleted_at IS NULL`, + scopeKey, raw, id, ) @@ -829,6 +838,7 @@ func (r *accountRepository) SetAntigravityQuotaScopeLimit(ctx context.Context, i if affected == 0 { return service.ErrAccountNotFound } + if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventAccountChanged, &id, nil, nil); err != nil { log.Printf("[SchedulerOutbox] enqueue quota scope failed: account=%d err=%v", id, err) } @@ -849,12 +859,19 @@ func (r *accountRepository) SetModelRateLimit(ctx context.Context, id int64, sco return err } - path := "{model_rate_limits," + scope + "}" client := clientFromContext(ctx, r.client) result, err := client.ExecContext( ctx, - "UPDATE accounts SET extra = jsonb_set(COALESCE(extra, '{}'::jsonb), $1::text[], $2::jsonb, true), updated_at = NOW() WHERE id = $3 AND deleted_at IS NULL", - path, + `UPDATE accounts SET + extra = jsonb_set( + jsonb_set(COALESCE(extra, '{}'::jsonb), '{model_rate_limits}'::text[], COALESCE(extra->'model_rate_limits', '{}'::jsonb), true), + ARRAY['model_rate_limits', $1]::text[], + $2::jsonb, + true + ), + updated_at = NOW() + WHERE id = $3 AND deleted_at IS NULL`, + scope, raw, id, ) From 6e8eff9bb9cfb1ecd78846c2560d5c4c3b969025 Mon Sep 17 00:00:00 2001 From: liuxiongfeng Date: Tue, 3 Feb 2026 12:06:45 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(ops):=20=E8=BF=90=E7=BB=B4=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=B1=95=E7=A4=BA=20Antigravity=20=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=20scope=20=E7=BA=A7=E5=88=AB=E9=99=90=E6=B5=81=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在运维监控的并发/排队卡片中,为 Antigravity 平台账号显示各 scope (claude/gemini_text/gemini_image) 的限流数量统计,便于管理员了解 哪些 scope 正在被限流。 Cherry-picked from slovx2/sub2api: 08d6dc52 --- .../service/antigravity_quota_scope.go | 27 +++++++++++++ .../service/ops_account_availability.go | 21 ++++++++++ .../internal/service/ops_realtime_models.go | 39 ++++++++++--------- frontend/src/api/admin/ops.ts | 3 ++ frontend/src/i18n/locales/en.ts | 1 + frontend/src/i18n/locales/zh.ts | 1 + .../ops/components/OpsConcurrencyCard.vue | 24 ++++++++++++ 7 files changed, 98 insertions(+), 18 deletions(-) diff --git a/backend/internal/service/antigravity_quota_scope.go b/backend/internal/service/antigravity_quota_scope.go index a3b2ec66..34cd9a4c 100644 --- a/backend/internal/service/antigravity_quota_scope.go +++ b/backend/internal/service/antigravity_quota_scope.go @@ -89,3 +89,30 @@ func (a *Account) antigravityQuotaScopeResetAt(scope AntigravityQuotaScope) *tim } return &resetAt } + +var antigravityAllScopes = []AntigravityQuotaScope{ + AntigravityQuotaScopeClaude, + AntigravityQuotaScopeGeminiText, + AntigravityQuotaScopeGeminiImage, +} + +func (a *Account) GetAntigravityScopeRateLimits() map[string]int64 { + if a == nil || a.Platform != PlatformAntigravity { + return nil + } + now := time.Now() + result := make(map[string]int64) + for _, scope := range antigravityAllScopes { + resetAt := a.antigravityQuotaScopeResetAt(scope) + if resetAt != nil && now.Before(*resetAt) { + remainingSec := int64(time.Until(*resetAt).Seconds()) + if remainingSec > 0 { + result[string(scope)] = remainingSec + } + } + } + if len(result) == 0 { + return nil + } + return result +} diff --git a/backend/internal/service/ops_account_availability.go b/backend/internal/service/ops_account_availability.go index da66ec4d..9be06c15 100644 --- a/backend/internal/service/ops_account_availability.go +++ b/backend/internal/service/ops_account_availability.go @@ -67,6 +67,8 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi isAvailable := acc.Status == StatusActive && acc.Schedulable && !isRateLimited && !isOverloaded && !isTempUnsched + scopeRateLimits := acc.GetAntigravityScopeRateLimits() + if acc.Platform != "" { if _, ok := platform[acc.Platform]; !ok { platform[acc.Platform] = &PlatformAvailability{ @@ -84,6 +86,14 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi if hasError { p.ErrorCount++ } + if len(scopeRateLimits) > 0 { + if p.ScopeRateLimitCount == nil { + p.ScopeRateLimitCount = make(map[string]int64) + } + for scope := range scopeRateLimits { + p.ScopeRateLimitCount[scope]++ + } + } } for _, grp := range acc.Groups { @@ -108,6 +118,14 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi if hasError { g.ErrorCount++ } + if len(scopeRateLimits) > 0 { + if g.ScopeRateLimitCount == nil { + g.ScopeRateLimitCount = make(map[string]int64) + } + for scope := range scopeRateLimits { + g.ScopeRateLimitCount[scope]++ + } + } } displayGroupID := int64(0) @@ -140,6 +158,9 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi item.RateLimitRemainingSec = &remainingSec } } + if len(scopeRateLimits) > 0 { + item.ScopeRateLimits = scopeRateLimits + } if isOverloaded && acc.OverloadUntil != nil { item.OverloadUntil = acc.OverloadUntil remainingSec := int64(time.Until(*acc.OverloadUntil).Seconds()) diff --git a/backend/internal/service/ops_realtime_models.go b/backend/internal/service/ops_realtime_models.go index f7514a24..c7e5715b 100644 --- a/backend/internal/service/ops_realtime_models.go +++ b/backend/internal/service/ops_realtime_models.go @@ -39,22 +39,24 @@ type AccountConcurrencyInfo struct { // PlatformAvailability aggregates account availability by platform. type PlatformAvailability struct { - Platform string `json:"platform"` - TotalAccounts int64 `json:"total_accounts"` - AvailableCount int64 `json:"available_count"` - RateLimitCount int64 `json:"rate_limit_count"` - ErrorCount int64 `json:"error_count"` + Platform string `json:"platform"` + TotalAccounts int64 `json:"total_accounts"` + AvailableCount int64 `json:"available_count"` + RateLimitCount int64 `json:"rate_limit_count"` + ScopeRateLimitCount map[string]int64 `json:"scope_rate_limit_count,omitempty"` + ErrorCount int64 `json:"error_count"` } // GroupAvailability aggregates account availability by group. type GroupAvailability struct { - GroupID int64 `json:"group_id"` - GroupName string `json:"group_name"` - Platform string `json:"platform"` - TotalAccounts int64 `json:"total_accounts"` - AvailableCount int64 `json:"available_count"` - RateLimitCount int64 `json:"rate_limit_count"` - ErrorCount int64 `json:"error_count"` + GroupID int64 `json:"group_id"` + GroupName string `json:"group_name"` + Platform string `json:"platform"` + TotalAccounts int64 `json:"total_accounts"` + AvailableCount int64 `json:"available_count"` + RateLimitCount int64 `json:"rate_limit_count"` + ScopeRateLimitCount map[string]int64 `json:"scope_rate_limit_count,omitempty"` + ErrorCount int64 `json:"error_count"` } // AccountAvailability represents current availability for a single account. @@ -72,10 +74,11 @@ type AccountAvailability struct { IsOverloaded bool `json:"is_overloaded"` HasError bool `json:"has_error"` - RateLimitResetAt *time.Time `json:"rate_limit_reset_at"` - RateLimitRemainingSec *int64 `json:"rate_limit_remaining_sec"` - OverloadUntil *time.Time `json:"overload_until"` - OverloadRemainingSec *int64 `json:"overload_remaining_sec"` - ErrorMessage string `json:"error_message"` - TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"` + RateLimitResetAt *time.Time `json:"rate_limit_reset_at"` + RateLimitRemainingSec *int64 `json:"rate_limit_remaining_sec"` + ScopeRateLimits map[string]int64 `json:"scope_rate_limits,omitempty"` + OverloadUntil *time.Time `json:"overload_until"` + OverloadRemainingSec *int64 `json:"overload_remaining_sec"` + ErrorMessage string `json:"error_message"` + TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"` } diff --git a/frontend/src/api/admin/ops.ts b/frontend/src/api/admin/ops.ts index 9e0444b1..bf2c246c 100644 --- a/frontend/src/api/admin/ops.ts +++ b/frontend/src/api/admin/ops.ts @@ -353,6 +353,7 @@ export interface PlatformAvailability { total_accounts: number available_count: number rate_limit_count: number + scope_rate_limit_count?: Record error_count: number } @@ -363,6 +364,7 @@ export interface GroupAvailability { total_accounts: number available_count: number rate_limit_count: number + scope_rate_limit_count?: Record error_count: number } @@ -377,6 +379,7 @@ export interface AccountAvailability { is_rate_limited: boolean rate_limit_reset_at?: string rate_limit_remaining_sec?: number + scope_rate_limits?: Record is_overloaded: boolean overload_until?: string overload_remaining_sec?: number diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 7c4df36b..d0b38605 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -2829,6 +2829,7 @@ export default { empty: 'No data', queued: 'Queue {count}', rateLimited: 'Rate-limited {count}', + scopeRateLimitedTooltip: '{scope} rate-limited ({count} accounts)', errorAccounts: 'Errors {count}', loadFailed: 'Failed to load concurrency data' }, diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index ba1c775f..a1067689 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -2982,6 +2982,7 @@ export default { empty: '暂无数据', queued: '队列 {count}', rateLimited: '限流 {count}', + scopeRateLimitedTooltip: '{scope} 限流中 ({count} 个账号)', errorAccounts: '异常 {count}', loadFailed: '加载并发数据失败' }, diff --git a/frontend/src/views/admin/ops/components/OpsConcurrencyCard.vue b/frontend/src/views/admin/ops/components/OpsConcurrencyCard.vue index acb0de1b..9c1ae1c1 100644 --- a/frontend/src/views/admin/ops/components/OpsConcurrencyCard.vue +++ b/frontend/src/views/admin/ops/components/OpsConcurrencyCard.vue @@ -49,6 +49,7 @@ interface SummaryRow { total_accounts: number available_accounts: number rate_limited_accounts: number + scope_rate_limit_count?: Record error_accounts: number // 并发统计 total_concurrency: number @@ -102,6 +103,7 @@ const platformRows = computed((): SummaryRow[] => { total_accounts: totalAccounts, available_accounts: availableAccounts, rate_limited_accounts: safeNumber(avail.rate_limit_count), + scope_rate_limit_count: avail.scope_rate_limit_count, error_accounts: safeNumber(avail.error_count), total_concurrency: totalConcurrency, used_concurrency: usedConcurrency, @@ -141,6 +143,7 @@ const groupRows = computed((): SummaryRow[] => { total_accounts: totalAccounts, available_accounts: availableAccounts, rate_limited_accounts: safeNumber(avail.rate_limit_count), + scope_rate_limit_count: avail.scope_rate_limit_count, error_accounts: safeNumber(avail.error_count), total_concurrency: totalConcurrency, used_concurrency: usedConcurrency, @@ -269,6 +272,15 @@ function formatDuration(seconds: number): string { return `${hours}h` } +function formatScopeName(scope: string): string { + const names: Record = { + claude: 'Claude', + gemini_text: 'Gemini', + gemini_image: 'Image' + } + return names[scope] || scope +} + watch( () => realtimeEnabled.value, async (enabled) => { @@ -387,6 +399,18 @@ watch( {{ t('admin.ops.concurrency.rateLimited', { count: row.rate_limited_accounts }) }} + + + Date: Tue, 3 Feb 2026 12:08:13 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(accounts):=20=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA=20Antigravity=20scope=20?= =?UTF-8?q?=E7=BA=A7=E5=88=AB=E9=99=90=E6=B5=81=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 DTO 新增 scope_rate_limits 字段,从 extra 提取限流信息 - 前端状态列显示 scope 级限流徽章(Claude/Gemini/Image) - 清除速率限制时同时清除账号级和 scope 级限流(已有实现) Cherry-picked from slovx2/sub2api: 66f49b67 --- backend/internal/handler/dto/mappers.go | 11 +++ backend/internal/handler/dto/types.go | 8 ++ .../account/AccountStatusIndicator.vue | 78 +++++++++++++++++++ frontend/src/i18n/locales/en.ts | 1 + frontend/src/i18n/locales/zh.ts | 1 + frontend/src/types/index.ts | 3 + 6 files changed, 102 insertions(+) diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 886a5535..5663e299 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -204,6 +204,17 @@ func AccountFromServiceShallow(a *service.Account) *Account { } } + if scopeLimits := a.GetAntigravityScopeRateLimits(); len(scopeLimits) > 0 { + out.ScopeRateLimits = make(map[string]ScopeRateLimitInfo, len(scopeLimits)) + now := time.Now() + for scope, remainingSec := range scopeLimits { + out.ScopeRateLimits[scope] = ScopeRateLimitInfo{ + ResetAt: now.Add(time.Duration(remainingSec) * time.Second), + RemainingSec: remainingSec, + } + } + } + return out } diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index 4cfaef5f..0a58391c 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -2,6 +2,11 @@ package dto import "time" +type ScopeRateLimitInfo struct { + ResetAt time.Time `json:"reset_at"` + RemainingSec int64 `json:"remaining_sec"` +} + type User struct { ID int64 `json:"id"` Email string `json:"email"` @@ -108,6 +113,9 @@ type Account struct { RateLimitResetAt *time.Time `json:"rate_limit_reset_at"` OverloadUntil *time.Time `json:"overload_until"` + // Antigravity scope 级限流状态(从 extra 提取) + ScopeRateLimits map[string]ScopeRateLimitInfo `json:"scope_rate_limits,omitempty"` + TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until"` TempUnschedulableReason string `json:"temp_unschedulable_reason"` diff --git a/frontend/src/components/account/AccountStatusIndicator.vue b/frontend/src/components/account/AccountStatusIndicator.vue index 02c962f1..6bb19b87 100644 --- a/frontend/src/components/account/AccountStatusIndicator.vue +++ b/frontend/src/components/account/AccountStatusIndicator.vue @@ -56,6 +56,65 @@ > + + +
+ + + 429 + + +
+ {{ t('admin.accounts.status.rateLimitedUntil', { time: formatTime(account.rate_limit_reset_at) }) }} +
+
+
+ + + + + +
+ + + 529 + + +
+ {{ t('admin.accounts.status.overloadedUntil', { time: formatTime(account.overload_until) }) }} +
+
+
@@ -81,6 +140,25 @@ const isRateLimited = computed(() => { return new Date(props.account.rate_limit_reset_at) > new Date() }) +// Computed: active scope rate limits (Antigravity) +const activeScopeRateLimits = computed(() => { + const scopeLimits = props.account.scope_rate_limits + if (!scopeLimits) return [] + const now = new Date() + return Object.entries(scopeLimits) + .filter(([, info]) => new Date(info.reset_at) > now) + .map(([scope, info]) => ({ scope, reset_at: info.reset_at })) +}) + +const formatScopeName = (scope: string): string => { + const names: Record = { + claude: 'Claude', + gemini_text: 'Gemini', + gemini_image: 'Image' + } + return names[scope] || scope +} + // Computed: is overloaded (529) const isOverloaded = computed(() => { if (!props.account.overload_until) return false diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index d0b38605..beb0bdf3 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1183,6 +1183,7 @@ export default { overloaded: 'Overloaded', tempUnschedulable: 'Temp Unschedulable', rateLimitedUntil: 'Rate limited until {time}', + scopeRateLimitedUntil: '{scope} rate limited until {time}', overloadedUntil: 'Overloaded until {time}', viewTempUnschedDetails: 'View temp unschedulable details' }, diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index a1067689..328ad1bc 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -1305,6 +1305,7 @@ export default { overloaded: '过载中', tempUnschedulable: '临时不可调度', rateLimitedUntil: '限流中,重置时间:{time}', + scopeRateLimitedUntil: '{scope} 限流中,重置时间:{time}', overloadedUntil: '负载过重,重置时间:{time}', viewTempUnschedDetails: '查看临时不可调度详情' }, diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 7c6cbf52..0d904ab5 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -559,6 +559,9 @@ export interface Account { temp_unschedulable_until: string | null temp_unschedulable_reason: string | null + // Antigravity scope 级限流状态 + scope_rate_limits?: Record + // Session window fields (5-hour window) session_window_start: string | null session_window_end: string | null From d3c1d77a35f147d75ee02e28b07c1db1a1998951 Mon Sep 17 00:00:00 2001 From: liuxiongfeng Date: Tue, 3 Feb 2026 12:56:06 +0800 Subject: [PATCH 5/5] fix(frontend): import missing formatTime function in AccountStatusIndicator --- frontend/src/components/account/AccountStatusIndicator.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/account/AccountStatusIndicator.vue b/frontend/src/components/account/AccountStatusIndicator.vue index 6bb19b87..8e525fa3 100644 --- a/frontend/src/components/account/AccountStatusIndicator.vue +++ b/frontend/src/components/account/AccountStatusIndicator.vue @@ -122,7 +122,7 @@ import { computed } from 'vue' import { useI18n } from 'vue-i18n' import type { Account } from '@/types' -import { formatCountdownWithSuffix } from '@/utils/format' +import { formatCountdownWithSuffix, formatTime } from '@/utils/format' const { t } = useI18n()