mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(platform): add daily-only vs daily+weekly reset option for rate limits
Collapse the reset button into the spending/usage display and add a dropdown to choose between "Reset daily only" (default) and "Reset daily + weekly". Backend accepts a new `reset_weekly` boolean parameter on the reset endpoint; when false only the daily Redis key is deleted.
This commit is contained in:
@@ -65,13 +65,17 @@ async def get_user_rate_limit(
|
||||
)
|
||||
async def reset_user_rate_limit(
|
||||
user_id: str = Body(embed=True),
|
||||
reset_weekly: bool = Body(False, embed=True),
|
||||
admin_user_id: str = Security(get_user_id),
|
||||
) -> UserRateLimitResponse:
|
||||
"""Reset a user's daily and weekly usage counters to zero. Admin-only."""
|
||||
logger.info(f"Admin {admin_user_id} resetting rate limit for user {user_id}")
|
||||
"""Reset a user's daily usage counter (and optionally weekly). Admin-only."""
|
||||
logger.info(
|
||||
f"Admin {admin_user_id} resetting rate limit for user {user_id} "
|
||||
f"(reset_weekly={reset_weekly})"
|
||||
)
|
||||
|
||||
try:
|
||||
await reset_user_usage(user_id)
|
||||
await reset_user_usage(user_id, reset_weekly=reset_weekly)
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to reset user usage: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to reset usage") from e
|
||||
|
||||
@@ -77,12 +77,52 @@ def test_get_rate_limit(
|
||||
)
|
||||
|
||||
|
||||
def test_reset_user_usage(
|
||||
def test_reset_user_usage_daily_only(
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
configured_snapshot: Snapshot,
|
||||
target_user_id: str,
|
||||
) -> None:
|
||||
"""Test resetting a user's usage counters to zero."""
|
||||
"""Test resetting only daily usage (default behaviour)."""
|
||||
mock_reset = mocker.patch(
|
||||
f"{_MOCK_MODULE}.reset_user_usage",
|
||||
new_callable=AsyncMock,
|
||||
)
|
||||
mocker.patch(
|
||||
f"{_MOCK_MODULE}.get_global_rate_limits",
|
||||
new_callable=AsyncMock,
|
||||
return_value=(2_500_000, 12_500_000),
|
||||
)
|
||||
mocker.patch(
|
||||
f"{_MOCK_MODULE}.get_usage_status",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_usage_status(daily_used=0, weekly_used=3_000_000),
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/admin/rate_limit/reset",
|
||||
json={"user_id": target_user_id},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["daily_tokens_used"] == 0
|
||||
# Weekly is untouched
|
||||
assert data["weekly_tokens_used"] == 3_000_000
|
||||
|
||||
mock_reset.assert_awaited_once_with(target_user_id, reset_weekly=False)
|
||||
|
||||
configured_snapshot.assert_match(
|
||||
json.dumps(data, indent=2, sort_keys=True) + "\n",
|
||||
"reset_user_usage_daily_only",
|
||||
)
|
||||
|
||||
|
||||
def test_reset_user_usage_daily_and_weekly(
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
configured_snapshot: Snapshot,
|
||||
target_user_id: str,
|
||||
) -> None:
|
||||
"""Test resetting both daily and weekly usage."""
|
||||
mock_reset = mocker.patch(
|
||||
f"{_MOCK_MODULE}.reset_user_usage",
|
||||
new_callable=AsyncMock,
|
||||
@@ -100,7 +140,7 @@ def test_reset_user_usage(
|
||||
|
||||
response = client.post(
|
||||
"/admin/rate_limit/reset",
|
||||
json={"user_id": target_user_id},
|
||||
json={"user_id": target_user_id, "reset_weekly": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -108,11 +148,11 @@ def test_reset_user_usage(
|
||||
assert data["daily_tokens_used"] == 0
|
||||
assert data["weekly_tokens_used"] == 0
|
||||
|
||||
mock_reset.assert_awaited_once_with(target_user_id)
|
||||
mock_reset.assert_awaited_once_with(target_user_id, reset_weekly=True)
|
||||
|
||||
configured_snapshot.assert_match(
|
||||
json.dumps(data, indent=2, sort_keys=True) + "\n",
|
||||
"reset_user_usage",
|
||||
"reset_user_usage_daily_and_weekly",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -269,18 +269,19 @@ async def get_global_rate_limits(
|
||||
return daily, weekly
|
||||
|
||||
|
||||
async def reset_user_usage(user_id: str) -> None:
|
||||
"""Reset a user's daily and weekly usage counters.
|
||||
async def reset_user_usage(user_id: str, *, reset_weekly: bool = False) -> None:
|
||||
"""Reset a user's usage counters.
|
||||
|
||||
Deletes the Redis keys for the current daily and weekly windows.
|
||||
Always deletes the daily Redis key. When *reset_weekly* is ``True``,
|
||||
the weekly key is deleted as well.
|
||||
"""
|
||||
now = datetime.now(UTC)
|
||||
keys_to_delete = [_daily_key(user_id, now=now)]
|
||||
if reset_weekly:
|
||||
keys_to_delete.append(_weekly_key(user_id, now=now))
|
||||
try:
|
||||
redis = await get_redis_async()
|
||||
await redis.delete(
|
||||
_daily_key(user_id, now=now),
|
||||
_weekly_key(user_id, now=now),
|
||||
)
|
||||
await redis.delete(*keys_to_delete)
|
||||
except (RedisError, ConnectionError, OSError):
|
||||
logger.warning("Redis unavailable for resetting user usage")
|
||||
raise
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"daily_token_limit": 2500000,
|
||||
"daily_tokens_used": 0,
|
||||
"user_id": "5e53486c-cf57-477e-ba2a-cb02dc828e1c",
|
||||
"weekly_token_limit": 12500000,
|
||||
"weekly_tokens_used": 3000000
|
||||
}
|
||||
@@ -39,38 +39,31 @@ function UsageBar({ used, limit }: { used: number; limit: number }) {
|
||||
|
||||
interface Props {
|
||||
data: UserRateLimitResponse;
|
||||
onReset: () => Promise<void>;
|
||||
onReset: (resetWeekly: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export function RateLimitDisplay({ data, onReset }: Props) {
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
const [resetWeekly, setResetWeekly] = useState(false);
|
||||
|
||||
async function handleReset() {
|
||||
setIsResetting(true);
|
||||
try {
|
||||
await onReset();
|
||||
await onReset(resetWeekly);
|
||||
} finally {
|
||||
setIsResetting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const nothingToReset = resetWeekly
|
||||
? data.daily_tokens_used === 0 && data.weekly_tokens_used === 0
|
||||
: data.daily_tokens_used === 0;
|
||||
|
||||
return (
|
||||
<div className="rounded-md border bg-white p-6">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">
|
||||
Rate Limits for {data.user_id}
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
disabled={
|
||||
isResetting ||
|
||||
(data.daily_tokens_used === 0 && data.weekly_tokens_used === 0)
|
||||
}
|
||||
>
|
||||
{isResetting ? "Resetting..." : "Reset Usage to Zero"}
|
||||
</Button>
|
||||
</div>
|
||||
<h2 className="mb-4 text-lg font-semibold">
|
||||
Rate Limits for {data.user_id}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
@@ -88,6 +81,25 @@ export function RateLimitDisplay({ data, onReset }: Props) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex items-center gap-3 border-t pt-4">
|
||||
<select
|
||||
value={resetWeekly ? "both" : "daily"}
|
||||
onChange={(e) => setResetWeekly(e.target.value === "both")}
|
||||
className="rounded-md border px-3 py-1.5 text-sm"
|
||||
disabled={isResetting}
|
||||
>
|
||||
<option value="daily">Reset daily only</option>
|
||||
<option value="both">Reset daily + weekly</option>
|
||||
</select>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
disabled={isResetting || nothingToReset}
|
||||
>
|
||||
{isResetting ? "Resetting..." : "Reset Usage"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,12 +44,13 @@ export function RateLimitManager() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReset() {
|
||||
async function handleReset(resetWeekly: boolean) {
|
||||
if (!rateLimitData) return;
|
||||
|
||||
try {
|
||||
const response = await postV2ResetUserRateLimitUsage({
|
||||
user_id: rateLimitData.user_id,
|
||||
reset_weekly: resetWeekly,
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to reset usage");
|
||||
@@ -57,7 +58,9 @@ export function RateLimitManager() {
|
||||
setRateLimitData(response.data);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "User rate limit usage reset to zero.",
|
||||
description: resetWeekly
|
||||
? "Daily and weekly usage reset to zero."
|
||||
: "Daily usage reset to zero.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error resetting rate limit:", error);
|
||||
|
||||
@@ -1448,7 +1448,7 @@
|
||||
"post": {
|
||||
"tags": ["v2", "admin", "copilot", "admin"],
|
||||
"summary": "Reset User Rate Limit Usage",
|
||||
"description": "Reset a user's daily and weekly usage counters to zero. Admin-only.",
|
||||
"description": "Reset a user's daily usage counter (and optionally weekly). Admin-only.",
|
||||
"operationId": "postV2Reset user rate limit usage",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -8238,7 +8238,14 @@
|
||||
"title": "Body_postV2Execute a preset"
|
||||
},
|
||||
"Body_postV2Reset_user_rate_limit_usage": {
|
||||
"properties": { "user_id": { "type": "string", "title": "User Id" } },
|
||||
"properties": {
|
||||
"user_id": { "type": "string", "title": "User Id" },
|
||||
"reset_weekly": {
|
||||
"type": "boolean",
|
||||
"title": "Reset Weekly",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["user_id"],
|
||||
"title": "Body_postV2Reset user rate limit usage"
|
||||
|
||||
Reference in New Issue
Block a user