From 6e8eff9bb9cfb1ecd78846c2560d5c4c3b969025 Mon Sep 17 00:00:00 2001 From: liuxiongfeng Date: Tue, 3 Feb 2026 12:06:45 +0800 Subject: [PATCH] =?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 }) }} + + +