Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
f3f19d35
Commit
f3f19d35
authored
Mar 16, 2026
by
SilentFlower
Committed by
erio
Mar 16, 2026
Browse files
feat: enhance Antigravity account overages handling and improve UI credit display
parent
ced90e1d
Changes
4
Show whitespace changes
Inline
Side-by-side
backend/internal/service/admin_service.go
View file @
f3f19d35
...
...
@@ -1538,6 +1538,13 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U
}
}
account
.
Extra
=
input
.
Extra
if
account
.
Platform
==
PlatformAntigravity
&&
wasOveragesEnabled
&&
!
account
.
IsOveragesEnabled
()
{
delete
(
account
.
Extra
,
antigravityCreditsOveragesKey
)
}
if
account
.
Platform
==
PlatformAntigravity
&&
!
wasOveragesEnabled
&&
account
.
IsOveragesEnabled
()
{
delete
(
account
.
Extra
,
modelRateLimitsKey
)
delete
(
account
.
Extra
,
antigravityCreditsOveragesKey
)
}
// 校验并预计算固定时间重置的下次重置时间
if
err
:=
ValidateQuotaResetConfig
(
account
.
Extra
);
err
!=
nil
{
return
nil
,
err
...
...
@@ -1623,9 +1630,6 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U
if
account
.
Platform
==
PlatformAntigravity
{
if
!
account
.
IsOveragesEnabled
()
&&
wasOveragesEnabled
{
clearCreditsExhausted
(
account
.
ID
)
if
err
:=
clearAntigravityCreditsOveragesState
(
ctx
,
s
.
accountRepo
,
account
.
ID
);
err
!=
nil
{
return
nil
,
err
}
}
if
account
.
IsOveragesEnabled
()
&&
!
wasOveragesEnabled
{
clearCreditsExhausted
(
account
.
ID
)
...
...
backend/internal/service/admin_service_overages_test.go
0 → 100644
View file @
f3f19d35
//go:build unit
package
service
import
(
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type
updateAccountOveragesRepoStub
struct
{
mockAccountRepoForGemini
account
*
Account
updateCalls
int
}
func
(
r
*
updateAccountOveragesRepoStub
)
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
{
return
r
.
account
,
nil
}
func
(
r
*
updateAccountOveragesRepoStub
)
Update
(
ctx
context
.
Context
,
account
*
Account
)
error
{
r
.
updateCalls
++
r
.
account
=
account
return
nil
}
func
TestUpdateAccount_DisableOveragesClearsRuntimeStateBeforePersist
(
t
*
testing
.
T
)
{
accountID
:=
int64
(
101
)
repo
:=
&
updateAccountOveragesRepoStub
{
account
:
&
Account
{
ID
:
accountID
,
Platform
:
PlatformAntigravity
,
Type
:
AccountTypeOAuth
,
Status
:
StatusActive
,
Extra
:
map
[
string
]
any
{
"allow_overages"
:
true
,
"mixed_scheduling"
:
true
,
antigravityCreditsOveragesKey
:
map
[
string
]
any
{
"claude-sonnet-4-5"
:
map
[
string
]
any
{
"activated_at"
:
"2026-03-15T00:00:00Z"
,
"active_until"
:
"2099-03-15T00:00:00Z"
,
},
},
},
},
}
setCreditsExhausted
(
accountID
,
time
.
Now
()
.
Add
(
time
.
Minute
))
t
.
Cleanup
(
func
()
{
clearCreditsExhausted
(
accountID
)
})
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
updated
,
err
:=
svc
.
UpdateAccount
(
context
.
Background
(),
accountID
,
&
UpdateAccountInput
{
Extra
:
map
[
string
]
any
{
"mixed_scheduling"
:
true
,
},
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
updated
)
require
.
Equal
(
t
,
1
,
repo
.
updateCalls
)
require
.
False
(
t
,
updated
.
IsOveragesEnabled
())
require
.
False
(
t
,
isCreditsExhausted
(
accountID
))
_
,
exists
:=
repo
.
account
.
Extra
[
antigravityCreditsOveragesKey
]
require
.
False
(
t
,
exists
,
"关闭 overages 时应在持久化前移除运行态"
)
}
func
TestUpdateAccount_EnableOveragesClearsModelRateLimitsBeforePersist
(
t
*
testing
.
T
)
{
accountID
:=
int64
(
102
)
repo
:=
&
updateAccountOveragesRepoStub
{
account
:
&
Account
{
ID
:
accountID
,
Platform
:
PlatformAntigravity
,
Type
:
AccountTypeOAuth
,
Status
:
StatusActive
,
Extra
:
map
[
string
]
any
{
"mixed_scheduling"
:
true
,
modelRateLimitsKey
:
map
[
string
]
any
{
"claude-sonnet-4-5"
:
map
[
string
]
any
{
"rate_limited_at"
:
"2026-03-15T00:00:00Z"
,
"rate_limit_reset_at"
:
"2099-03-15T00:00:00Z"
,
},
},
},
},
}
setCreditsExhausted
(
accountID
,
time
.
Now
()
.
Add
(
time
.
Minute
))
t
.
Cleanup
(
func
()
{
clearCreditsExhausted
(
accountID
)
})
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
updated
,
err
:=
svc
.
UpdateAccount
(
context
.
Background
(),
accountID
,
&
UpdateAccountInput
{
Extra
:
map
[
string
]
any
{
"mixed_scheduling"
:
true
,
"allow_overages"
:
true
,
},
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
updated
)
require
.
Equal
(
t
,
1
,
repo
.
updateCalls
)
require
.
True
(
t
,
updated
.
IsOveragesEnabled
())
require
.
False
(
t
,
isCreditsExhausted
(
accountID
))
_
,
exists
:=
repo
.
account
.
Extra
[
modelRateLimitsKey
]
require
.
False
(
t
,
exists
,
"开启 overages 时应在持久化前清掉旧模型限流"
)
}
frontend/src/components/account/AccountUsageCell.vue
View file @
f3f19d35
...
...
@@ -290,30 +290,34 @@
color=
"amber"
/>
<div
v-if=
"antigravityAICreditsDisplay.length > 0"
class=
"mt-1
space-y-0.5 text-[10px] text-gray-500 dark:text-gray-400
"
>
<div
v-if=
"antigravityAICreditsDisplay.length > 0"
class=
"mt-1
flex flex-wrap gap-1
"
>
<div
v-for=
"credit in antigravityAICreditsDisplay"
:key=
"credit.creditType"
class=
"inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[10px] font-medium text-amber-700 dark:border-amber-800/50 dark:bg-amber-950/40 dark:text-amber-300"
>
{{
t
(
'
admin.accounts.aiCreditsBalance
'
)
}}
:
{{
credit
.
creditType
}}
{{
credit
.
amount
}}
<span
v-if=
"credit.minimumBalance !== null"
>
(min
{{
credit
.
minimumBalance
}}
)
<span>
⚡
</span>
<span>
{{
t
(
'
admin.accounts.aiCreditsBalance
'
)
}}
</span>
<span>
{{
credit
.
label
}}
</span>
<span
class=
"font-mono"
>
{{
credit
.
amount
}}
</span>
<span
v-if=
"credit.minimumBalance !== null"
class=
"text-[9px] opacity-75"
>
min
{{
credit
.
minimumBalance
}}
</span>
</div>
</div>
</div>
<div
v-else-if=
"antigravityAICreditsDisplay.length > 0"
class=
"
space-y-0.5 text-[10px] text-gray-500 dark:text-gray-400
"
>
<div
v-else-if=
"antigravityAICreditsDisplay.length > 0"
class=
"
flex flex-wrap gap-1
"
>
<div
v-for=
"credit in antigravityAICreditsDisplay"
:key=
"credit.creditType"
class=
"inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[10px] font-medium text-amber-700 dark:border-amber-800/50 dark:bg-amber-950/40 dark:text-amber-300"
>
{{
t
(
'
admin.accounts.aiCreditsBalance
'
)
}}
:
{{
credit
.
creditType
}}
{{
credit
.
amount
}}
<span
v-if=
"credit.minimumBalance !== null"
>
(min
{{
credit
.
minimumBalance
}}
)
<span>
⚡
</span>
<span>
{{
t
(
'
admin.accounts.aiCreditsBalance
'
)
}}
</span>
<span>
{{
credit
.
label
}}
</span>
<span
class=
"font-mono"
>
{{
credit
.
amount
}}
</span>
<span
v-if=
"credit.minimumBalance !== null"
class=
"text-[9px] opacity-75"
>
min
{{
credit
.
minimumBalance
}}
</span>
</div>
</div>
...
...
@@ -615,6 +619,7 @@ const antigravityAICreditsDisplay = computed(() => {
.
filter
((
credit
)
=>
(
credit
.
amount
??
0
)
>
0
)
.
map
((
credit
)
=>
({
creditType
:
credit
.
credit_type
||
'
UNKNOWN
'
,
label
:
formatAICreditTypeLabel
(
credit
.
credit_type
||
'
UNKNOWN
'
),
amount
:
Number
(
credit
.
amount
??
0
).
toFixed
(
0
),
minimumBalance
:
typeof
credit
.
minimum_balance
===
'
number
'
?
Number
(
credit
.
minimum_balance
).
toFixed
(
0
)
...
...
@@ -622,6 +627,11 @@ const antigravityAICreditsDisplay = computed(() => {
}))
})
function
formatAICreditTypeLabel
(
creditType
:
string
):
string
{
if
(
creditType
===
'
GOOGLE_ONE_AI
'
)
return
'
Google One AI
'
return
creditType
}
// Antigravity 账户类型(从 load_code_assist 响应中提取)
const
antigravityTier
=
computed
(()
=>
{
const
extra
=
props
.
account
.
extra
as
Record
<
string
,
unknown
>
|
undefined
...
...
frontend/src/components/account/__tests__/AccountUsageCell.spec.ts
View file @
f3f19d35
...
...
@@ -133,9 +133,9 @@ describe('AccountUsageCell', () => {
await
flushPromises
()
expect
(
wrapper
.
text
()).
toContain
(
'
admin.accounts.aiCreditsBalance
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
G
OOGLE_ONE_
AI
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
G
oogle One
AI
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
25
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
(
min 5
)
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
min 5
'
)
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment