total quota fix#1124
Conversation
📝 WalkthroughWalkthrough为配额功能新增“总成本/总配额”维度:添加多语言本地化条目、后端汇总总成本的数据字段,并在提供商与密钥的配额 UI 与类型声明中引入可读/可编辑的总 USD 限额(limitTotalUsd / costTotal)。 Changes
Estimated code review effort🎯 3 (中等) | ⏱️ ~25 分钟 Possibly related PRs
Suggested reviewers
漫游指南此PR为总成本/总配额功能添加了多语言本地化字符串、扩展了提供商配额数据类型以包含 更改内容
代码审查工作量评估🎯 3 (中等) | ⏱️ ~25 分钟 可能相关的PR
建议审查者
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| min="0" | ||
| placeholder={t("limitTotalUsd.placeholder")} | ||
| value={limitTotal} | ||
| onChange={(e) => setLimitTotal(e.target.value)} | ||
| className="h-9" | ||
| /> | ||
| {currentQuota?.costTotal.limit && ( | ||
| <p className="text-xs text-muted-foreground"> | ||
| {t("limitTotalUsd.current", { | ||
| currency: currencySymbol, | ||
| current: Number(currentQuota.costTotal.current).toFixed(4), | ||
| limit: Number(currentQuota.costTotal.limit).toFixed(2), | ||
| })} | ||
| </p> |
There was a problem hiding this comment.
Current-usage tooltip hidden when limit is exactly
0
The condition currentQuota?.costTotal.limit && … is falsy when limit is 0. Because an admin can legitimately set a limitTotalUsd of 0 to hard-block a key, the tooltip won't appear in that case even though there is an active limit.
| min="0" | |
| placeholder={t("limitTotalUsd.placeholder")} | |
| value={limitTotal} | |
| onChange={(e) => setLimitTotal(e.target.value)} | |
| className="h-9" | |
| /> | |
| {currentQuota?.costTotal.limit && ( | |
| <p className="text-xs text-muted-foreground"> | |
| {t("limitTotalUsd.current", { | |
| currency: currencySymbol, | |
| current: Number(currentQuota.costTotal.current).toFixed(4), | |
| limit: Number(currentQuota.costTotal.limit).toFixed(2), | |
| })} | |
| </p> | |
| {currentQuota?.costTotal.limit !== null && currentQuota?.costTotal.limit !== undefined && ( |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx
Line: 351-364
Comment:
**Current-usage tooltip hidden when limit is exactly `0`**
The condition `currentQuota?.costTotal.limit && …` is falsy when `limit` is `0`. Because an admin can legitimately set a `limitTotalUsd` of `0` to hard-block a key, the tooltip won't appear in that case even though there is an active limit.
```suggestion
{currentQuota?.costTotal.limit !== null && currentQuota?.costTotal.limit !== undefined && (
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6a0151b4c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), |
There was a problem hiding this comment.
Respect provider resetAt when summing total quota usage
getProviderLimitUsage now computes total usage with sumProviderTotalCost(providerId) but does not pass provider.totalCostResetAt. After an admin uses resetProviderTotalUsage, enforcement paths use that reset marker (RateLimitService.checkTotalCostLimit(..., { resetAt })), while this UI/API path keeps counting historical ledger rows, so the displayed total quota usage is wrong and can show providers as still over limit immediately after a reset.
Useful? React with 👍 / 👎.
|
|
||
| {/* 总限额 */} | ||
| <TableCell> | ||
| {hasKeyQuota && key.quota && key.quota.costTotal.limit !== null ? ( |
There was a problem hiding this comment.
Render total key quota without requiring other quota types
The new total-quota column is gated by hasKeyQuota, but hasKeyQuotaSet does not include costTotal, so keys that only configure limitTotalUsd are treated as “inherited” and this cell renders - instead of the configured total limit and quick-edit control. This makes the new total quota effectively invisible for total-only configurations.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[HIGH] [ERROR-NO-USER-FEEDBACK] Current-usage tooltip hidden when total limit is exactly 0
Why this is a problem: The condition currentQuota?.costTotal.limit && ... is falsy when limit is 0. Because an admin can legitimately set a limitTotalUsd of 0 to hard-block a key, the tooltip won't appear in that case even though there is an active limit.
Suggested fix:
There was a problem hiding this comment.
Code Review Summary
This PR adds total cost limit (limitTotalUsd) support to provider and key quotas. While the implementation is mostly solid, I've identified one critical issue and one high priority bug that must be addressed before merging.
PR Size: S
- Lines changed: 173
- Files changed: 12
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 1 | 1 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 1 | 0 | 0 |
| Types | 1 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Critical Issues (Must Fix)
1. [TYPE-WEAK-INVARIANT] costTotal missing from shared KeyQuota type in src/lib/utils/quota-helpers.ts
Why this is a problem: The PR adds costTotal to every component-level KeyQuota interface, but the canonical KeyQuota type in quota-helpers.ts is not updated. As a result:
hasKeyQuotaSet()returnsfalsefor keys with only total quota setgetMaxUsageRate()ignores total cost - warning/exceeded states are never triggeredisWarning()andisExceeded()don't consider total quota- Filter tabs ("key-quota", "warning", "exceeded") in
keys-quota-manager.tsxmisclassify keys - A key whose only limit is
limitTotalUsdwill be treated as having no quota at all, and the per-key status badge will show "正常" even after the total budget is exhausted
Suggested fix:
// Update KeyQuota type
export type KeyQuota = {
cost5h: { current: number; limit: number | null };
costDaily: { current: number; limit: number | null };
costWeekly: { current: number; limit: number | null };
costMonthly: { current: number; limit: number | null };
costTotal: { current: number; limit: number | null }; // ADD THIS
concurrentSessions: { current: number; limit: number };
} | null;
// Update hasKeyQuotaSet()
return \!\!(
quota.cost5h.limit ||
quota.costDaily.limit ||
quota.costWeekly.limit ||
quota.costMonthly.limit ||
quota.costTotal?.limit || // ADD THIS
(quota.concurrentSessions.limit && quota.concurrentSessions.limit > 0)
);
// Update getMaxUsageRate()
if (quota.costTotal?.limit) { // ADD THIS
rates.push(getUsageRate(quota.costTotal.current, quota.costTotal.limit));
}High Priority Issues (Should Fix)
2. [ERROR-NO-USER-FEEDBACK] Current-usage tooltip hidden when limit is exactly 0 in edit-key-quota-dialog.tsx:356-366
Why this is a problem: The condition currentQuota?.costTotal.limit && ... is falsy when limit is 0. Because an admin can legitimately set a limitTotalUsd of 0 to hard-block a key, the tooltip won't appear in that case even though there is an active limit.
Suggested fix:
// Change from:
{currentQuota?.costTotal.limit && (
<p className="text-xs text-muted-foreground">...</p>
)}
// To:
{currentQuota?.costTotal.limit \!== null && currentQuota?.costTotal.limit \!== undefined && (
<p className="text-xs text-muted-foreground">...</p>
)}3. [LOGIC-BUG] handleClearQuota does not clear limitTotalUsd in edit-key-quota-dialog.tsx:118-145
Why this is a problem: The "Clear All Quotas" button resets all quotas except limitTotalUsd. This leaves the total quota active when the user intended to clear all limits.
Suggested fix: Add limitTotalUsd: null, to the handleClearQuota call at line 127-130.
Medium Priority (Consider Fixing)
4. [STANDARD-VIOLATION] Missing test coverage for new quota functionality
Per CLAUDE.md: "All new features must have unit test coverage of at least 80%". The new total quota functionality in sumProviderTotalCost() and the quota helpers should have corresponding unit tests.
Review Coverage
- Logic and correctness - 2 issues found
- Security (OWASP Top 10) - Clean
- Error handling - 1 issue found
- Type safety - 1 critical issue found
- Documentation accuracy - Clean
- Test coverage - Gap identified
- Code clarity - Clean
Automated review by Claude AI
There was a problem hiding this comment.
Code Review
This pull request introduces a 'Total Quota' (USD) feature across the provider and key quota management systems, including translation updates, backend logic to calculate total costs, and UI components for viewing and editing these limits. Feedback focuses on ensuring that manual resets of total usage are respected by passing the reset timestamp to the cost calculation functions in the provider actions. Additionally, a naming inconsistency between 'costTotal' and 'limitTotalUsd' was identified across different components, which should be standardized for better maintainability.
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), |
There was a problem hiding this comment.
The call to sumProviderTotalCost is missing the resetAt parameter. Without passing provider.totalCostResetAt, the total cost calculation will ignore manual resets performed via resetProviderTotalUsage, leading to incorrect usage display in the UI.
| sumProviderTotalCost(providerId), | |
| sumProviderTotalCost(providerId, provider.totalCostResetAt), |
| limitTotalUsd?: number | null; | ||
| limitConcurrentSessions?: number | null; |
There was a problem hiding this comment.
The providers parameter in getProviderLimitUsageBatch should include totalCostResetAt to allow correct calculation of the total cost after a reset. Note that getProviders (not in this diff) also needs to be updated to include this field in its return mapping.
limitTotalUsd?: number | null;
totalCostResetAt?: Date | null;
limitConcurrentSessions?: number | null;| sumProviderCostInTimeRange(provider.id, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(provider.id, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(provider.id, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(provider.id), |
There was a problem hiding this comment.
| limitTotalUsd: p.limitTotalUsd, | ||
| limitConcurrentSessions: p.limitConcurrentSessions, |
There was a problem hiding this comment.
The mapping logic for getProviderLimitUsageBatch is missing the totalCostResetAt field. This is necessary for the server action to correctly calculate the total cost relative to the last reset time. Ensure that getProviders() is also updated to return this field and that it is converted to a Date object if necessary.
limitTotalUsd: p.limitTotalUsd,
totalCostResetAt: p.totalCostResetAt ? new Date(p.totalCostResetAt) : null,
limitConcurrentSessions: p.limitConcurrentSessions,
| costDaily: { current: number; limit: number | null; resetAt?: Date }; | ||
| costWeekly: { current: number; limit: number | null; resetAt?: Date }; | ||
| costMonthly: { current: number; limit: number | null; resetAt?: Date }; | ||
| costTotal: { current: number; limit: number | null }; |
There was a problem hiding this comment.
There is a naming inconsistency for the total quota field. Here it is named costTotal, while in the provider-related components and actions (e.g., ProviderLimitUsageData in src/actions/providers.ts), it is named limitTotalUsd. Standardizing the naming across the project would improve maintainability.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx (1)
395-398:⚠️ Potential issue | 🟡 Minor清空按钮条件遗漏总限额,导致“仅总限额”场景无法一键清空。
Line [395]-[398] 的条件未包含
currentQuota?.costTotal.limit。如果用户只配置了总限额,clearAll不会显示。建议修改
{(currentQuota?.cost5h.limit || + currentQuota?.costTotal.limit || currentQuota?.costWeekly.limit || currentQuota?.costMonthly.limit || (currentQuota?.concurrentSessions.limit ?? 0) > 0) && (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx around lines 395 - 398, The clear-all button's render condition omits currentQuota?.costTotal.limit, so when only a total cost limit is set the clear (clearAll) button won't appear; update the JSX conditional in edit-key-quota-dialog.tsx where the expression uses currentQuota (the line with currentQuota?.cost5h.limit || currentQuota?.costWeekly.limit || currentQuota?.costMonthly.limit || (currentQuota?.concurrentSessions.limit ?? 0) > 0) to include currentQuota?.costTotal.limit in the OR chain (and ensure parentheses around the concurrentSessions check remain correct) so the clearAll button is shown when only costTotal is configured.
🧹 Nitpick comments (1)
src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx (1)
19-19: 字段命名与同级不一致新增的
limitTotalUsd与同接口中cost5h/costDaily/costWeekly/costMonthly的cost*命名风格不一致,且其语义与它们一样是{ current; limit }(含已使用与上限),并非"纯限额"。i18n 也使用costTotal.label。建议统一为costTotal以避免混淆,否则未来维护时容易和数据库字段limitTotalUsd(裸数值)混为一谈。♻️ 建议改名(同步 page.tsx 中 quotaMap 与 actions/providers.ts 的返回字段)
interface ProviderQuota { cost5h: { current: number; limit: number | null; resetInfo: string }; costDaily: { current: number; limit: number | null; resetAt?: Date }; costWeekly: { current: number; limit: number | null; resetAt: Date }; costMonthly: { current: number; limit: number | null; resetAt: Date }; concurrentSessions: { current: number; limit: number }; - limitTotalUsd: { current: number; limit: number | null }; + costTotal: { current: number; limit: number | null }; }并相应调整 222–227 行的引用。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx at line 19, Rename the new prop/field limitTotalUsd to costTotal to match the naming convention of cost5h/costDaily/costWeekly/costMonthly and i18n key costTotal.label; update all usages and shapes that expect limitTotalUsd (including the component provider-quota-list-item, the quotaMap in page.tsx, and the returned fields in actions/providers.ts) and adjust the references that currently access limitTotalUsd (the block referenced around the previous 222–227 lines) to use costTotal.current and costTotal.limit instead of the old name so the shape remains { current; limit } across the codebase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/actions/providers.ts`:
- Around line 2738-2747: The total cost calls are missing the reset boundary so
resetProviderTotalUsage semantics break—pass the provider's reset boundary into
sumProviderTotalCost (the resetAt param) wherever total cost is computed (e.g.,
the call to sumProviderTotalCost in the Promise.all and the other occurrences
around the later ranges); locate usages of sumProviderTotalCost and change them
to supply the same total_cost_reset_at (or the computed reset boundary used for
aggregation) so the aggregation lower bound is respected, and ensure callers
like resetProviderTotalUsage and RateLimitService.getCurrentCost continue to
align with that resetAt value.
In `@src/app/`[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx:
- Line 395: hasKeyQuotaSet currently omits checking costTotal.limit which causes
keys that only have costTotal.limit set to be treated as having no quota; update
the hasKeyQuotaSet function in quota-helpers.ts to include a truthy/non-null
check for quota.costTotal?.limit (or equivalent) alongside the existing checks
for cost5h, costDaily, costWeekly, costMonthly and concurrentSessions so that
the component relying on hasKeyQuota (line with hasKeyQuota && key.quota &&
key.quota.costTotal.limit !== null) correctly renders when only total cost limit
is set.
---
Outside diff comments:
In
`@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 395-398: The clear-all button's render condition omits
currentQuota?.costTotal.limit, so when only a total cost limit is set the clear
(clearAll) button won't appear; update the JSX conditional in
edit-key-quota-dialog.tsx where the expression uses currentQuota (the line with
currentQuota?.cost5h.limit || currentQuota?.costWeekly.limit ||
currentQuota?.costMonthly.limit || (currentQuota?.concurrentSessions.limit ?? 0)
> 0) to include currentQuota?.costTotal.limit in the OR chain (and ensure
parentheses around the concurrentSessions check remain correct) so the clearAll
button is shown when only costTotal is configured.
---
Nitpick comments:
In
`@src/app/`[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx:
- Line 19: Rename the new prop/field limitTotalUsd to costTotal to match the
naming convention of cost5h/costDaily/costWeekly/costMonthly and i18n key
costTotal.label; update all usages and shapes that expect limitTotalUsd
(including the component provider-quota-list-item, the quotaMap in page.tsx, and
the returned fields in actions/providers.ts) and adjust the references that
currently access limitTotalUsd (the block referenced around the previous 222–227
lines) to use costTotal.current and costTotal.limit instead of the old name so
the shape remains { current; limit } across the codebase.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e4481f80-7c8e-48c5-8183-a03d7707b1d8
📒 Files selected for processing (12)
messages/en/quota.jsonmessages/ja/quota.jsonmessages/ru/quota.jsonmessages/zh-CN/quota.jsonmessages/zh-TW/quota.jsonsrc/actions/providers.tssrc/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsxsrc/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsxsrc/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsxsrc/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsxsrc/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsxsrc/app/[locale]/dashboard/quotas/providers/page.tsx
| const [cost5h, costDaily, costWeekly, costMonthly, totalCost, concurrentSessions] = | ||
| await Promise.all([ | ||
| limit5hResetMode === "fixed" | ||
| ? RateLimitService.getCurrentCost(providerId, "provider", "5h", undefined, limit5hResetMode) | ||
| : sumProviderCostInTimeRange(providerId, range5h.startTime, range5h.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), | ||
| SessionTracker.getProviderSessionCount(providerId), |
There was a problem hiding this comment.
总额使用量未传入 reset 边界,resetProviderTotalUsage 语义会失效。
sumProviderTotalCost 支持 resetAt,但这里两处调用都未传入。结合 Line [1250] 的“通过 total_cost_reset_at 作为聚合下限”设计,当前会在重置后继续累计历史总额。
建议修改
- sumProviderTotalCost(providerId),
+ sumProviderTotalCost(providerId, provider.totalCostResetAt ?? null), providers: Array<{
id: number;
+ totalCostResetAt?: Date | null;
dailyResetTime?: string | null;
dailyResetMode?: string | null;
limit5hResetMode?: string | null;
limit5hUsd?: number | null;- sumProviderTotalCost(provider.id),
+ sumProviderTotalCost(provider.id, provider.totalCostResetAt ?? null),Also applies to: 2824-2834, 2877-2905
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/actions/providers.ts` around lines 2738 - 2747, The total cost calls are
missing the reset boundary so resetProviderTotalUsage semantics break—pass the
provider's reset boundary into sumProviderTotalCost (the resetAt param) wherever
total cost is computed (e.g., the call to sumProviderTotalCost in the
Promise.all and the other occurrences around the later ranges); locate usages of
sumProviderTotalCost and change them to supply the same total_cost_reset_at (or
the computed reset boundary used for aggregation) so the aggregation lower bound
is respected, and ensure callers like resetProviderTotalUsage and
RateLimitService.getCurrentCost continue to align with that resetAt value.
There was a problem hiding this comment.
Pull request overview
This PR addresses missing/incorrect “total quota” (lifetime spending limit) visibility by plumbing limitTotalUsd through provider/key quota data models, UI rendering, and translations.
Changes:
- Add provider-side
limitTotalUsdto quota usage payloads and render “Total Cost/Total Quota” in the provider quotas UI. - Add key-side total quota (
costTotal) to the keys quota table and key quota edit dialog. - Add i18n strings for “total” quota/cost labels across multiple locales.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/[locale]/dashboard/quotas/providers/page.tsx | Passes limitTotalUsd into the provider batch quota loader. |
| src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx | Includes total quota in “has quota” detection and max-usage calculation. |
| src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx | Renders provider “Total Cost” quota indicator. |
| src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsx | Extends key quota shape with costTotal. |
| src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx | Adds a “Total Quota” column and rendering for key quotas. |
| src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx | Adds total quota input and saves it as limitTotalUsd. |
| src/actions/providers.ts | Adds limitTotalUsd usage aggregation via sumProviderTotalCost for single/batch provider quota usage. |
| messages/en/quota.json | Adds provider/key “total” labels and key edit-dialog strings. |
| messages/zh-CN/quota.json | Adds provider/key “total” labels and key edit-dialog strings. |
| messages/zh-TW/quota.json | Adds provider/key “total” labels and key edit-dialog strings. |
| messages/ru/quota.json | Adds provider/key “total” labels and key edit-dialog strings. |
| messages/ja/quota.json | Adds provider/key “total” labels and key edit-dialog strings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| sumProviderCostInTimeRange(provider.id, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(provider.id, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(provider.id, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(provider.id), |
There was a problem hiding this comment.
sumProviderTotalCost(...) runs for every provider even when limitTotalUsd is null/0 (i.e., total quota disabled). This adds an extra DB aggregate per provider for no user-visible benefit. Consider only querying total cost when limitTotalUsd is a positive number (otherwise skip and set current to 0 or omit the field).
| const [cost5h, costDaily, costWeekly, costMonthly, totalCost, concurrentSessions] = | ||
| await Promise.all([ | ||
| limit5hResetMode === "fixed" | ||
| ? RateLimitService.getCurrentCost(providerId, "provider", "5h", undefined, limit5hResetMode) | ||
| : sumProviderCostInTimeRange(providerId, range5h.startTime, range5h.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), | ||
| SessionTracker.getProviderSessionCount(providerId), | ||
| ]); |
There was a problem hiding this comment.
sumProviderTotalCost(providerId) is executed unconditionally, even if provider.limitTotalUsd is null/0 (total quota disabled). Skipping this query when the limit isn’t enabled avoids an unnecessary full-history SUM and reduces load on the quota page.
| const [cost5h, costDaily, costWeekly, costMonthly, totalCost, concurrentSessions] = | |
| await Promise.all([ | |
| limit5hResetMode === "fixed" | |
| ? RateLimitService.getCurrentCost(providerId, "provider", "5h", undefined, limit5hResetMode) | |
| : sumProviderCostInTimeRange(providerId, range5h.startTime, range5h.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | |
| sumProviderTotalCost(providerId), | |
| SessionTracker.getProviderSessionCount(providerId), | |
| ]); | |
| const totalCostPromise = | |
| provider.limitTotalUsd != null && provider.limitTotalUsd > 0 | |
| ? sumProviderTotalCost(providerId) | |
| : Promise.resolve(0); | |
| const [cost5h, costDaily, costWeekly, costMonthly, totalCost, concurrentSessions] = | |
| await Promise.all([ | |
| limit5hResetMode === "fixed" | |
| ? RateLimitService.getCurrentCost(providerId, "provider", "5h", undefined, limit5hResetMode) | |
| : sumProviderCostInTimeRange(providerId, range5h.startTime, range5h.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | |
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | |
| totalCostPromise, | |
| SessionTracker.getProviderSessionCount(providerId), | |
| ]); |
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), |
There was a problem hiding this comment.
sumProviderTotalCost(providerId) is called without the provider’s totalCostResetAt. That means the “total quota” usage shown here will be all-time, and can disagree with the actual enforcement logic (which resets cumulative total after a manual reset). Pass provider.totalCostResetAt into the total-cost aggregation (or use the same helper used for enforcement) so UI and rate limiting semantics match.
| sumProviderTotalCost(providerId), | |
| sumProviderTotalCost(providerId, provider.totalCostResetAt), |
| @@ -2887,6 +2901,7 @@ export async function getProviderLimitUsageBatch( | |||
| sumProviderCostInTimeRange(provider.id, rangeDaily.startTime, rangeDaily.endTime), | |||
| sumProviderCostInTimeRange(provider.id, rangeWeekly.startTime, rangeWeekly.endTime), | |||
| sumProviderCostInTimeRange(provider.id, rangeMonthly.startTime, rangeMonthly.endTime), | |||
| sumProviderTotalCost(provider.id), | |||
There was a problem hiding this comment.
In the batch path, sumProviderTotalCost(provider.id) also ignores the provider’s totalCostResetAt, so the displayed total usage can stay “exceeded” even after an admin resets total-cost usage. Extend the providers input to include totalCostResetAt (from getProviders()), and pass it into the total-cost sum so the batch view matches enforcement behavior.
| limitDailyUsd: p.limitDailyUsd, | ||
| limitWeeklyUsd: p.limitWeeklyUsd, | ||
| limitMonthlyUsd: p.limitMonthlyUsd, | ||
| limitTotalUsd: p.limitTotalUsd, |
There was a problem hiding this comment.
getProviderLimitUsageBatch will need the provider’s totalCostResetAt to compute “total quota” usage correctly after a reset. Right now only limitTotalUsd is passed, so the batch action can’t apply the reset boundary and will show an all-time total.
| limitTotalUsd: p.limitTotalUsd, | |
| limitTotalUsd: p.limitTotalUsd, | |
| totalCostResetAt: p.totalCostResetAt, |
| interface KeyQuota { | ||
| cost5h: { current: number; limit: number | null }; | ||
| costDaily: { current: number; limit: number | null }; | ||
| costWeekly: { current: number; limit: number | null }; | ||
| costMonthly: { current: number; limit: number | null }; | ||
| costTotal: { current: number; limit: number | null }; | ||
| concurrentSessions: { current: number; limit: number }; | ||
| } |
There was a problem hiding this comment.
KeyQuota now includes costTotal, but the logic in this component relies on hasKeyQuotaSet(...) for filtering and “independent vs inherited” labeling. hasKeyQuotaSet (and its KeyQuota type) currently doesn’t consider total quota, so keys with only a total limit will be misclassified and may be filtered out. Update the helper/type to include costTotal as a quota dimension.
| {/* 总限额 */} | ||
| <TableCell> | ||
| {hasKeyQuota && key.quota && key.quota.costTotal.limit !== null ? ( | ||
| <div className="space-y-1"> | ||
| <div className="flex items-center justify-between mb-1"> | ||
| <span className="text-xs text-muted-foreground"> | ||
| {t("table.costTotal")} |
There was a problem hiding this comment.
The new total-quota column is gated on hasKeyQuota, but hasKeyQuota comes from hasKeyQuotaSet(key.quota), which doesn’t include total quota. As a result, keys that only have a total spending limit won’t display this column and will be labeled as “inherited”. Either include costTotal in hasKeyQuotaSet (and its KeyQuota type), or adjust the gating here to consider costTotal directly.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx (1)
396-409:⚠️ Potential issue | 🟡 Minor“清除全部”按钮可见条件遗漏了
costTotal(以及costDaily)本次 PR 在
handleClearQuota中新增了limitTotalUsd: null的清空逻辑(Line 135),但该按钮的可见条件未包含currentQuota?.costTotal.limit。后果:当某 Key 仅配置了总额限额(且其他维度为空、并发为 0)时,hasKeyQuotaSet视为已配置,但用户在编辑对话框中看不到“清除全部”按钮,无法一键清空。同时此条件原本也未涵盖costDaily.limit,建议一并补齐:🐛 建议改动
- {(currentQuota?.cost5h.limit || - currentQuota?.costWeekly.limit || - currentQuota?.costMonthly.limit || - (currentQuota?.concurrentSessions.limit ?? 0) > 0) && ( + {(currentQuota?.cost5h.limit || + currentQuota?.costDaily.limit || + currentQuota?.costWeekly.limit || + currentQuota?.costMonthly.limit || + currentQuota?.costTotal.limit || + (currentQuota?.concurrentSessions.limit ?? 0) > 0) && (注:同样存在
0作为有效限额时被判为假值的隐患,若希望严格匹配hasKeyQuotaSet的语义,可改为!= null比较。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx around lines 396 - 409, The visibility condition for the "清除全部" button in the DialogFooter uses currentQuota checks but omits costTotal and costDaily and treats 0 as falsy; update the conditional that currently references currentQuota?.cost5h.limit, costWeekly, costMonthly and concurrentSessions to also include currentQuota?.costTotal.limit and currentQuota?.costDaily.limit, and use explicit null/undefined checks (e.g., != null) for each limit comparison so zero is considered a valid set limit; ensure this aligns with the handleClearQuota logic that clears limitTotalUsd and other limits.
♻️ Duplicate comments (1)
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx (1)
359-367:⚠️ Potential issue | 🟡 Minor当前用量提示在
limitTotalUsd === 0时被隐藏
currentQuota?.costTotal.limit && (...)在limit为0时为假值,因此即便管理员显式将总额限额设为0(用于硬封禁该 Key),也不会显示已用/限额提示,与其他维度上已有的同类问题一致。建议显式判断非null/undefined:🐛 建议改动
- {currentQuota?.costTotal.limit && ( + {currentQuota?.costTotal.limit != null && ( <p className="text-xs text-muted-foreground"> {t("limitTotalUsd.current", { currency: currencySymbol, current: Number(currentQuota.costTotal.current).toFixed(4), limit: Number(currentQuota.costTotal.limit).toFixed(2), })} </p> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx around lines 359 - 367, The JSX currently hides the "current/limit" line when costTotal.limit is 0 because it uses a truthy check; change the condition in the render for currentQuota?.costTotal.limit to an explicit null/undefined check (e.g., currentQuota?.costTotal.limit != null) so that a limit of 0 still renders; update the component where currentQuota and costTotal.limit are referenced (edit-key-quota-dialog, render block using currentQuota?.costTotal.limit) to use this explicit check and keep the same formatting logic for current and limit values.
🧹 Nitpick comments (2)
src/lib/utils/quota-helpers.ts (1)
8-15:costTotal类型应与上游契约保持一致(必需而非可选)上游数据源对
costTotal均为必需字段:
src/repository/statistics.ts中sumKeyQuotaCostsById()返回值始终包含costTotal: numbersrc/actions/keys.ts中getKeyLimitUsage()的返回类型显式声明costTotal: { current: number; limit: number | null }(必需)- 同 PR 中
keys-quota-client.tsx与edit-key-quota-dialog.tsx的本地KeyQuota接口也都把costTotal定义为必需仅在此处声明为
costTotal?: ...会让类型系统弱化对上游契约的约束,且其他文件的KeyQuota与本文件不再结构兼容(在严格类型检查下可能导致赋值不匹配)。建议改为必需,与其他所有定义对齐:♻️ 建议改动
export type KeyQuota = { cost5h: { current: number; limit: number | null }; costDaily: { current: number; limit: number | null }; costWeekly: { current: number; limit: number | null }; costMonthly: { current: number; limit: number | null }; - costTotal?: { current: number; limit: number | null }; + costTotal: { current: number; limit: number | null }; concurrentSessions: { current: number; limit: number }; } | null;如改为必需,下方
quota.costTotal?.limit的可选链可同步去掉。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/quota-helpers.ts` around lines 8 - 15, The KeyQuota type declares costTotal as optional which diverges from the upstream contract; update the KeyQuota type in this file to make costTotal required (change costTotal?: ... to costTotal: { current: number; limit: number | null }) so it matches sumKeyQuotaCostsById and getKeyLimitUsage and the local interfaces in keys-quota-client.tsx and edit-key-quota-dialog.tsx; after making costTotal required, remove any unnecessary optional chaining (e.g., quota.costTotal?.limit) in code that accesses costTotal.src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx (1)
39-81:null检查模式不一致(可选优化)。
hasQuotaLimit中对limitTotalUsd.limit使用显式!== null && > 0(Line [44]),而calculateMaxUsage中对同一字段使用隐式真值判断limit && limit > 0(Line [76])。两者对number | null类型行为等价,但在同一文件内风格不一致;本次新增的 total 字段沿用了不同模式,建议统一以提升可读性。建议修改:与 hasQuotaLimit 保持一致
- if (provider.quota.limitTotalUsd.limit && provider.quota.limitTotalUsd.limit > 0) { + if (provider.quota.limitTotalUsd.limit !== null && provider.quota.limitTotalUsd.limit > 0) { usages.push((provider.quota.limitTotalUsd.current / provider.quota.limitTotalUsd.limit) * 100); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx around lines 39 - 81, The null-check style is inconsistent: hasQuotaLimit uses explicit `!== null && > 0` while calculateMaxUsage uses an implicit truthy check; update calculateMaxUsage to use the same explicit null checks as hasQuotaLimit for all quota fields (e.g. `provider.quota.limitTotalUsd.limit !== null && provider.quota.limitTotalUsd.limit > 0`, and similarly for cost5h, costDaily, costWeekly, costMonthly, concurrentSessions) so the logic and style match between hasQuotaLimit and calculateMaxUsage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In
`@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 396-409: The visibility condition for the "清除全部" button in the
DialogFooter uses currentQuota checks but omits costTotal and costDaily and
treats 0 as falsy; update the conditional that currently references
currentQuota?.cost5h.limit, costWeekly, costMonthly and concurrentSessions to
also include currentQuota?.costTotal.limit and currentQuota?.costDaily.limit,
and use explicit null/undefined checks (e.g., != null) for each limit comparison
so zero is considered a valid set limit; ensure this aligns with the
handleClearQuota logic that clears limitTotalUsd and other limits.
---
Duplicate comments:
In
`@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 359-367: The JSX currently hides the "current/limit" line when
costTotal.limit is 0 because it uses a truthy check; change the condition in the
render for currentQuota?.costTotal.limit to an explicit null/undefined check
(e.g., currentQuota?.costTotal.limit != null) so that a limit of 0 still
renders; update the component where currentQuota and costTotal.limit are
referenced (edit-key-quota-dialog, render block using
currentQuota?.costTotal.limit) to use this explicit check and keep the same
formatting logic for current and limit values.
---
Nitpick comments:
In
`@src/app/`[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx:
- Around line 39-81: The null-check style is inconsistent: hasQuotaLimit uses
explicit `!== null && > 0` while calculateMaxUsage uses an implicit truthy
check; update calculateMaxUsage to use the same explicit null checks as
hasQuotaLimit for all quota fields (e.g. `provider.quota.limitTotalUsd.limit !==
null && provider.quota.limitTotalUsd.limit > 0`, and similarly for cost5h,
costDaily, costWeekly, costMonthly, concurrentSessions) so the logic and style
match between hasQuotaLimit and calculateMaxUsage.
In `@src/lib/utils/quota-helpers.ts`:
- Around line 8-15: The KeyQuota type declares costTotal as optional which
diverges from the upstream contract; update the KeyQuota type in this file to
make costTotal required (change costTotal?: ... to costTotal: { current: number;
limit: number | null }) so it matches sumKeyQuotaCostsById and getKeyLimitUsage
and the local interfaces in keys-quota-client.tsx and edit-key-quota-dialog.tsx;
after making costTotal required, remove any unnecessary optional chaining (e.g.,
quota.costTotal?.limit) in code that accesses costTotal.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2bc4d910-1b68-4996-81ca-c9f18571cf68
📒 Files selected for processing (7)
src/actions/providers.tssrc/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsxsrc/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsxsrc/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsxsrc/app/[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsxsrc/lib/utils/quota-helpers.tstests/unit/actions/providers-usage.test.ts
✅ Files skipped from review due to trivial changes (1)
- src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d64074a6d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| sumProviderCostInTimeRange(providerId, rangeDaily.startTime, rangeDaily.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeWeekly.startTime, rangeWeekly.endTime), | ||
| sumProviderCostInTimeRange(providerId, rangeMonthly.startTime, rangeMonthly.endTime), | ||
| sumProviderTotalCost(providerId), |
There was a problem hiding this comment.
Apply totalCostResetAt when computing provider total usage
sumProviderTotalCost is invoked without the provider’s reset marker, so this path always sums lifetime ledger rows even after resetProviderTotalUsage updates providers.total_cost_reset_at. That makes quota usage shown by this action diverge from enforcement (RateLimitService.checkTotalCostLimit(..., { resetAt })), and providers can appear over the total cap immediately after an admin reset. Pass provider.totalCostResetAt to the total-cost aggregation here (and in the batch variant) to keep displayed usage consistent with runtime limit checks.
Useful? React with 👍 / 👎.
Summary
Adds total cost limit (
limitTotalUsd) to provider and key quotas, enabling administrators to set absolute spending caps that never auto-reset. This complements the existing time-based quotas (5h, daily, weekly, monthly) with a permanent lifetime limit.Related PRs
getMyQuotaAPIProblem
Previously, quota management only supported time-windowed limits (5h, daily, weekly, monthly). For long-term budget control and hard spending caps, administrators needed a total/lifetime cost limit that accumulates indefinitely until manually reset.
Solution
1. Total Cost Limit Implementation
Added new
limitTotalUsdfield support across the quota system:sumProviderTotalCost()insrc/repository/statistics.ts- aggregates all historical costs for a provider with optional reset dategetProviderLimitUsage()andgetProviderLimitUsageBatch()insrc/actions/providers.tsto include total cost data2. Key Features
resetAt(if set) or from the earliest ledger recordChanges
Core Changes
src/repository/statistics.ts(+27 lines) - NewsumProviderTotalCost()functionsrc/actions/providers.ts(+19/-4 lines) - IncludelimitTotalUsdin provider limit usage queriesDashboard UI Changes
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx(+30 lines) - Total quota input fieldsrc/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx(+50 lines) - Total quota column with progress barsrc/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsx(+1 line) - Type definition updatesrc/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx(+9 lines) - Total quota displaysrc/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx(+9/-1 lines) - Total limit check in quota calculationsrc/app/[locale]/dashboard/quotas/providers/page.tsx(+1 line) - Type updatei18n Changes (5 Languages)
messages/en/quota.json(+9 lines) - English translationsmessages/ja/quota.json(+9 lines) - Japanese translationsmessages/ru/quota.json(+9 lines) - Russian translationsmessages/zh-CN/quota.json(+9 lines) - Simplified Chinese translationsmessages/zh-TW/quota.json(+9 lines) - Traditional Chinese translationsBreaking Changes
None. All changes are additive:
null(unlimited)Testing
Verification Steps
Database Query Verification
The new
sumProviderTotalCost()function uses the existingusageLedgertable with proper billing condition filtering.Checklist
Description enhanced by Claude AI
Greptile Summary
Adds a permanent
limitTotalUsdspending cap to both provider and key quotas — complementing the existing time-windowed limits (5h, daily, weekly, monthly) with a lifetime accumulator that never auto-resets. The implementation is additive:nullmeans unlimited, and all UI elements are guarded accordingly. The sharedKeyQuotahelper type inquota-helpers.tsis correctly updated with an optionalcostTotalfield, andhasKeyQuotaSet/getMaxUsageRate(and transitivelyisWarning/isExceeded) now include it.Confidence Score: 5/5
Safe to merge — all changes are additive, no breaking changes, and the shared helper types are updated correctly.
Only P2 findings: missing test assertions on the new
limitTotalUsdoutput field, and a minor redundant condition in the provider list item. No P0 or P1 issues found; the previously flagged P1 (costTotalabsent from quota-helpers) is addressed in this PR.tests/unit/actions/providers-usage.test.ts— consider adding assertions to verifylimitTotalUsdis present and correct in bothgetProviderLimitUsageandgetProviderLimitUsageBatchoutput.Important Files Changed
sumProviderTotalCost()— queriesusageLedgerwith no time truncation, correctly respecting an optionalresetAt. Implementation is consistent with the analogoussumUserTotalCost/sumKeyTotalCostpatterns.totalCostto bothgetProviderLimitUsageandgetProviderLimitUsageBatchPromise.all arrays and exposes it aslimitTotalUsd;resetAtis not passed tosumProviderTotalCostbecause providers have no such field yet — intentional per PR design.costTotalfield toKeyQuotatype and updateshasKeyQuotaSetandgetMaxUsageRate—isWarning/isExceededare covered transitively throughgetMaxUsageRate.costTotal.limittruthiness guard (falsy at 0) for the tooltip was flagged in a previous review thread.limitTotalUsdin bothhasQuotaLimit(explicit!== null && > 0) andcalculateMaxUsage; consistent with existing quota field treatment.sumProviderTotalCostMockinto existing setup but adds no assertions on thelimitTotalUsdoutput field in either test suite.Sequence Diagram
sequenceDiagram participant UI as Dashboard UI participant PA as providers.ts (action) participant ST as statistics.ts (repo) participant DB as usageLedger (DB) UI->>PA: getProviderLimitUsage(providerId) PA->>ST: sumProviderCostInTimeRange() x4 PA->>ST: sumProviderTotalCost(providerId) ST->>DB: SELECT SUM(cost_usd) WHERE finalProviderId = ? [no time cutoff] DB-->>ST: totalCost ST-->>PA: totalCost PA-->>UI: { ..., limitTotalUsd: { current, limit } } UI->>PA: getProviderLimitUsageBatch(providers[]) loop per provider PA->>ST: sumProviderTotalCost(provider.id) ST->>DB: SELECT SUM(cost_usd) WHERE finalProviderId = ? DB-->>ST: totalCost ST-->>PA: totalCost end PA-->>UI: Map<providerId, ProviderLimitUsageData>Comments Outside Diff (1)
src/lib/utils/quota-helpers.ts, line 8-14 (link)costTotalmissing from sharedKeyQuotatype and helper functionsThe PR adds
costTotalto every component-levelKeyQuotainterface, but the canonicalKeyQuotatype inquota-helpers.tsis not updated. As a result,hasKeyQuotaSet,getMaxUsageRate,isWarning, andisExceededall silently ignorecostTotal. A key whose only limit islimitTotalUsdwill be treated as having no quota at all — filter tabs ("key-quota", "warning", "exceeded") inkeys-quota-manager.tsxwill misclassify it, and the per-key status badge will show "正常" even after the total budget is exhausted.Add
costTotalto the type and include it in the helper functions:And in
hasKeyQuotaSet:And in
getMaxUsageRate:Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "fix(quota): repair total quota compile i..." | Re-trigger Greptile