feat(accounts): 账号列表显示 Antigravity scope 级别限流状态
- 后端 DTO 新增 scope_rate_limits 字段,从 extra 提取限流信息 - 前端状态列显示 scope 级限流徽章(Claude/Gemini/Image) - 清除速率限制时同时清除账号级和 scope 级限流(已有实现)
This commit is contained in:
@@ -164,6 +164,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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package dto
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
type ScopeRateLimitInfo struct {
|
||||||
|
ResetAt time.Time `json:"reset_at"`
|
||||||
|
RemainingSec int64 `json:"remaining_sec"`
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
@@ -97,6 +102,9 @@ type Account struct {
|
|||||||
RateLimitResetAt *time.Time `json:"rate_limit_reset_at"`
|
RateLimitResetAt *time.Time `json:"rate_limit_reset_at"`
|
||||||
OverloadUntil *time.Time `json:"overload_until"`
|
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"`
|
TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until"`
|
||||||
TempUnschedulableReason string `json:"temp_unschedulable_reason"`
|
TempUnschedulableReason string `json:"temp_unschedulable_reason"`
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Scope Rate Limit Indicators (Antigravity) -->
|
||||||
|
<template v-if="activeScopeRateLimits.length > 0">
|
||||||
|
<div v-for="item in activeScopeRateLimits" :key="item.scope" class="group relative">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center gap-1 rounded bg-orange-100 px-1.5 py-0.5 text-xs font-medium text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
|
>
|
||||||
|
<Icon name="exclamationTriangle" size="xs" :stroke-width="2" />
|
||||||
|
{{ formatScopeName(item.scope) }}
|
||||||
|
</span>
|
||||||
|
<!-- Tooltip -->
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 whitespace-nowrap rounded bg-gray-900 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
{{ t('admin.accounts.status.scopeRateLimitedUntil', { scope: formatScopeName(item.scope), time: formatTime(item.reset_at) }) }}
|
||||||
|
<div
|
||||||
|
class="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Overload Indicator (529) -->
|
<!-- Overload Indicator (529) -->
|
||||||
<div v-if="isOverloaded" class="group relative">
|
<div v-if="isOverloaded" class="group relative">
|
||||||
<span
|
<span
|
||||||
@@ -106,6 +127,25 @@ const isRateLimited = computed(() => {
|
|||||||
return new Date(props.account.rate_limit_reset_at) > new Date()
|
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<string, string> = {
|
||||||
|
claude: 'Claude',
|
||||||
|
gemini_text: 'Gemini',
|
||||||
|
gemini_image: 'Image'
|
||||||
|
}
|
||||||
|
return names[scope] || scope
|
||||||
|
}
|
||||||
|
|
||||||
// Computed: is overloaded (529)
|
// Computed: is overloaded (529)
|
||||||
const isOverloaded = computed(() => {
|
const isOverloaded = computed(() => {
|
||||||
if (!props.account.overload_until) return false
|
if (!props.account.overload_until) return false
|
||||||
|
|||||||
@@ -1081,6 +1081,7 @@ export default {
|
|||||||
limited: 'Limited',
|
limited: 'Limited',
|
||||||
tempUnschedulable: 'Temp Unschedulable',
|
tempUnschedulable: 'Temp Unschedulable',
|
||||||
rateLimitedUntil: 'Rate limited until {time}',
|
rateLimitedUntil: 'Rate limited until {time}',
|
||||||
|
scopeRateLimitedUntil: '{scope} rate limited until {time}',
|
||||||
overloadedUntil: 'Overloaded until {time}',
|
overloadedUntil: 'Overloaded until {time}',
|
||||||
viewTempUnschedDetails: 'View temp unschedulable details'
|
viewTempUnschedDetails: 'View temp unschedulable details'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1203,6 +1203,7 @@ export default {
|
|||||||
limited: '限流',
|
limited: '限流',
|
||||||
tempUnschedulable: '临时不可调度',
|
tempUnschedulable: '临时不可调度',
|
||||||
rateLimitedUntil: '限流中,重置时间:{time}',
|
rateLimitedUntil: '限流中,重置时间:{time}',
|
||||||
|
scopeRateLimitedUntil: '{scope} 限流中,重置时间:{time}',
|
||||||
overloadedUntil: '负载过重,重置时间:{time}',
|
overloadedUntil: '负载过重,重置时间:{time}',
|
||||||
viewTempUnschedDetails: '查看临时不可调度详情'
|
viewTempUnschedDetails: '查看临时不可调度详情'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -470,6 +470,9 @@ export interface Account {
|
|||||||
temp_unschedulable_until: string | null
|
temp_unschedulable_until: string | null
|
||||||
temp_unschedulable_reason: string | null
|
temp_unschedulable_reason: string | null
|
||||||
|
|
||||||
|
// Antigravity scope 级限流状态
|
||||||
|
scope_rate_limits?: Record<string, { reset_at: string; remaining_sec: number }>
|
||||||
|
|
||||||
// Session window fields (5-hour window)
|
// Session window fields (5-hour window)
|
||||||
session_window_start: string | null
|
session_window_start: string | null
|
||||||
session_window_end: string | null
|
session_window_end: string | null
|
||||||
|
|||||||
Reference in New Issue
Block a user