fix total quota#1126
Conversation
📝 WalkthroughWalkthrough添加并传播“总 USD 配额 / 总消耗”维度:新增 i18n 文本,扩展后端动作返回值与输入,更新类型定义,并在前端多处 quota UI(提供商与密钥)显示、编辑与计算中加入该维度及其可选 resetAt 时间戳。 Changes
代码审查工作量估计🎯 3 (中等) | ⏱️ ~20 分钟 Possibly related PRs
概述此 PR 在多语言本地化文件、后端操作和前端 UI 组件中添加了新的"总 USD 配额"功能。新增了国际化字符串来支持提供商和密钥的总成本配额显示与编辑,扩展了提供商和密钥配额数据结构,并更新了计算逻辑以包含新的配额维度。 变更
代码审查工作量估计🎯 3 (中等) | ⏱️ ~20 分钟 相关 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 |
There was a problem hiding this comment.
Code Review
This pull request introduces a 'Total Quota' (limitTotalUsd) feature for providers and keys, enabling the tracking and enforcement of an overall cost limit. The changes span across localization files, backend actions for usage calculation, and frontend components for displaying and managing these quotas. The review feedback highlights a critical need to incorporate the totalCostResetAt timestamp into the cost summation logic to ensure that usage is correctly calculated relative to the last manual reset.
| limitDailyUsd?: number | null; | ||
| limitWeeklyUsd?: number | null; | ||
| limitMonthlyUsd?: number | null; | ||
| limitTotalUsd?: number | null; |
There was a problem hiding this comment.
| 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), | ||
| })} |
There was a problem hiding this comment.
Current-usage hint hidden when limit is 0
currentQuota?.costTotal.limit && (...) is falsy when limit === 0, so the usage hint is silently suppressed for a zero limit. Other quota fields use limit !== null as the guard, which is safer.
| 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), | |
| })} | |
| {currentQuota?.costTotal.limit !== null && ( |
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: 355-365
Comment:
**Current-usage hint hidden when limit is 0**
`currentQuota?.costTotal.limit && (...)` is falsy when `limit === 0`, so the usage hint is silently suppressed for a zero limit. Other quota fields use `limit !== null` as the guard, which is safer.
```suggestion
{currentQuota?.costTotal.limit !== null && (
```
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: ff7d5b6620
ℹ️ 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".
There was a problem hiding this comment.
Pull request overview
Adds “total quota / total cost” support to quota calculation and the admin dashboard UI, so providers/keys can display and manage lifetime (or reset-based) spend limits alongside existing 5h/daily/weekly/monthly and concurrency limits.
Changes:
- Extend quota data structures and UI to include total cost / total quota fields.
- Update provider usage actions to fetch and return total cost usage (
sumProviderTotalCost) for single and batch endpoints. - Add i18n strings for the new total-quota labels across supported locales and update unit tests’ mocks accordingly.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/actions/providers-usage.test.ts | Mocks sumProviderTotalCost for updated provider usage actions. |
| src/lib/utils/quota-helpers.ts | Adds optional costTotal to KeyQuota and includes it in quota detection/max-usage calculations. |
| src/actions/providers.ts | Adds limitTotalUsd usage to provider limit usage responses and calls sumProviderTotalCost. |
| src/app/[locale]/dashboard/quotas/providers/page.tsx | Includes limitTotalUsd when building the batch quota request inputs. |
| src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsx | Updates provider quota typing to include limitTotalUsd. |
| src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx | Considers total quota in “has any quota” logic and usage-based sorting. |
| src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx | Renders the total quota item in the provider quota list UI. |
| src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsx | Updates key quota typing to include costTotal. |
| src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx | Adds a “total quota” column and rendering/editing UI for key total quota. |
| src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx | Adds form state + submission payload for editing limitTotalUsd. |
| messages/zh-TW/quota.json | Adds total cost/quota labels and edit-dialog strings. |
| messages/zh-CN/quota.json | Adds total cost/quota labels and edit-dialog strings. |
| messages/ru/quota.json | Adds total cost/quota labels and edit-dialog strings. |
| messages/ja/quota.json | Adds total cost/quota labels and edit-dialog strings. |
| messages/en/quota.json | Adds total cost/quota labels and 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.
getProviderLimitUsageBatch computes totalCost via sumProviderTotalCost(provider.id) but does not thread through the provider’s total-cost reset timestamp (e.g. totalCostResetAt). This makes batch results inconsistent with the total-limit reset semantics and with RateLimitService.checkTotalCostLimit({ resetAt }). Update the batch provider input type to include the reset field and pass it into sumProviderTotalCost.
| limitDailyUsd: p.limitDailyUsd, | ||
| limitWeeklyUsd: p.limitWeeklyUsd, | ||
| limitMonthlyUsd: p.limitMonthlyUsd, | ||
| limitTotalUsd: p.limitTotalUsd, |
There was a problem hiding this comment.
The batch quota fetch currently passes limitTotalUsd but not the provider’s total-cost reset timestamp (e.g. totalCostResetAt). If sumProviderTotalCost is meant to respect manual resets, you’ll need to include that field in this mapping and in the getProviderLimitUsageBatch input type, otherwise limitTotalUsd.current will be computed from lifetime spend.
| limitTotalUsd: p.limitTotalUsd, | |
| limitTotalUsd: p.limitTotalUsd, | |
| totalCostResetAt: p.totalCostResetAt, |
| if (quota.costTotal?.limit) { | ||
| rates.push(getUsageRate(quota.costTotal.current, quota.costTotal.limit)); |
There was a problem hiding this comment.
In strict TS mode, if (quota.costTotal?.limit) { ... quota.costTotal.current ... } does not reliably narrow quota.costTotal (it can still be undefined), which can cause a compile error (Object is possibly 'undefined'). Use an explicit guard (if (quota.costTotal && quota.costTotal.limit)) or assign const total = quota.costTotal and check total?.limit before accessing total.current/total.limit.
| if (quota.costTotal?.limit) { | |
| rates.push(getUsageRate(quota.costTotal.current, quota.costTotal.limit)); | |
| const total = quota.costTotal; | |
| if (total?.limit) { | |
| rates.push(getUsageRate(total.current, total.limit)); |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx (1)
396-409:⚠️ Potential issue | 🟠 Major"清除全部限额"按钮的显示条件遗漏了
costTotal.limit(以及历史上遗漏的costDaily.limit)。当前条件只检查
cost5h/costWeekly/costMonthly/concurrentSessions。因此当用户仅设置了"总限额"(limitTotalUsd)时,按钮不会渲染,用户无法在 UI 上一键清除该限额——这正是本 PR 新增维度引入的 UX 回归。costDaily同样被遗漏(历史问题,顺手一并修复更稳妥)。建议修复
- {(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) && (🤖 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 clear-all button render condition in DialogFooter uses currentQuota checks but omits costTotal and costDaily, so when only limitTotalUsd or costDaily.limit is set the Button (variant "destructive", onClick handleClearQuota) won't render; update the boolean expression that decides rendering to include currentQuota?.costTotal.limit and currentQuota?.costDaily.limit (and keep the existing currentQuota?.concurrentSessions.limit null-coalescing check) so the Button (and its disabled prop isPending and label t("clearAll")) appears whenever any quota dimension is > 0.tests/unit/actions/providers-usage.test.ts (1)
20-42:⚠️ Potential issue | 🟡 Minor总额度路径仅被 mock,尚未被断言验证。
目前只新增了
sumProviderTotalCostmock,但没有断言它被调用、也没有校验result.data.limitTotalUsd.current。这会让总额度回归问题漏检。建议在单条与批量用例里补上对应断言。建议补充的测试断言示例
it("should return correct structure with DB-sourced costs", async () => { sumProviderCostInTimeRangeMock .mockResolvedValueOnce(1.5) .mockResolvedValueOnce(10.0) .mockResolvedValueOnce(45.0) .mockResolvedValueOnce(120.0); + sumProviderTotalCostMock.mockResolvedValueOnce(300.0); const { getProviderLimitUsage } = await import("@/actions/providers"); const result = await getProviderLimitUsage(1); expect(result.ok).toBe(true); if (result.ok) { + expect(sumProviderTotalCostMock).toHaveBeenCalledWith(1); + expect(result.data.limitTotalUsd.current).toBe(300.0); expect(result.data.cost5h.current).toBe(1.5); expect(result.data.costDaily.current).toBe(10.0); expect(result.data.costWeekly.current).toBe(45.0); expect(result.data.costMonthly.current).toBe(120.0); } });Also applies to: 171-171, 409-409
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/actions/providers-usage.test.ts` around lines 20 - 42, The tests define sumProviderTotalCostMock but never assert it or the returned total limit, so add assertions in both the single-provider and batch-provider test cases to ensure the mock is called with the correct provider id(s) and that result.data.limitTotalUsd.current equals the expected mocked value; update the tests that exercise the code paths which call sumProviderTotalCost (refer to sumProviderTotalCostMock) to set a deterministic return value and assert both sumProviderTotalCostMock was invoked and result.data.limitTotalUsd.current matches that value (do this for the individual test and the batch test where multiple providers are evaluated).
🧹 Nitpick comments (3)
src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx (1)
39-51:hasQuotaLimit与calculateMaxUsage中limitTotalUsd的顺序不一致(可选优化)。此处
limitTotalUsd被放在判断列表的最前面(Line 44),而calculateMaxUsage(Line 76-78)中又被追加在末尾。两个函数功能相关,统一顺序可提升可读性,例如都按"5h → daily → weekly → monthly → total → concurrent"排列。🤖 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 - 51, Reorder the checks inside hasQuotaLimit to match the same time-sequence used in calculateMaxUsage: check cost5h, costDaily, costWeekly, costMonthly, then limitTotalUsd, and finally concurrentSessions; update the boolean expression in hasQuotaLimit (function name: hasQuotaLimit, symbol: quota.limitTotalUsd, quota.cost5h, quota.costDaily, quota.costWeekly, quota.costMonthly, quota.concurrentSessions) so the order matches calculateMaxUsage for consistent readability.messages/en/quota.json (1)
242-246: 与已存在的editKeyForm.limitTotalUsd.label文案略有差异(可选)。新增的
keys.editDialog.limitTotalUsd.label="Total Quota (USD)",而本文件已有的keys.editKeyForm.limitTotalUsd.label(Line 348)="Total Cost Limit (USD)"。两处指向同一概念但用词不同(Quota vs Cost Limit)。如果希望端到端文案统一,建议对齐其中一种,并同步五种语言文件。否则按现状即可,影响很小。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@messages/en/quota.json` around lines 242 - 246, 两个文案键在表达同一概念但用词不一致:keys.editDialog.limitTotalUsd.label ("Total Quota (USD)") 与 keys.editKeyForm.limitTotalUsd.label ("Total Cost Limit (USD)");请选择统一的术语(例如统一为 "Total Cost Limit (USD)" 或 "Total Quota (USD)"),然后在所有语言文件中同步替换相应键的值(确保 keys.editDialog.limitTotalUsd.label 和 keys.editKeyForm.limitTotalUsd.label 在各语言版本一致),提交修改以保持端到端文案统一。src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx (1)
13-20:limitTotalUsd字段命名与同接口其它成本字段风格不统一。
ProviderQuota中其它成本维度字段名都是cost5h/costDaily/costWeekly/costMonthly,而新增的"总额"被命名为limitTotalUsd,既不遵循cost*命名模式,也不必要地在字段名中包含"Usd"后缀。一致的命名应为
costTotal,与KeyQuota.costTotal(src/lib/utils/quota-helpers.ts)对齐,提升代码可读性。建议改名
concurrentSessions: { current: number; limit: number }; - limitTotalUsd: { current: number; limit: number | null }; + costTotal: { current: number; limit: number | null }; }并同步更新
providers-quota-client.tsx中的字段引用。🤖 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 around lines 13 - 20, Rename the inconsistent field limitTotalUsd in the ProviderQuota interface to costTotal to match the cost* naming pattern and align with KeyQuota.costTotal; update all usages and imports that reference limitTotalUsd (e.g., in providers-quota-client.tsx and any mappers or serializers) to use costTotal, and ensure the shape and types remain unchanged while running type checks to catch any missed references.
🤖 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-2754: The total-cost aggregation call to sumProviderTotalCost
lacks the provider's total-reset timestamp, so resets written by
resetProviderTotalUsage are ignored and limitTotalUsd.current keeps
accumulating; fix by retrieving the provider's total reset boundary (the same
timestamp used by resetProviderTotalUsage) and pass it as the reset/time-bound
parameter into sumProviderTotalCost wherever it's called (including the
single-call in providers.ts that awaits cost5h...totalCost and the batch calls
referenced around the other block), ensuring sumProviderTotalCost uses that
reset boundary when computing totals.
In
`@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 83-85: The component accesses currentQuota.costTotal as if always
present while KeyQuota.costTotal is declared optional in quota-helpers.ts;
either make the data contract consistent by removing the optional from
KeyQuota.costTotal in quota-helpers.ts (if costTotal is guaranteed) or make the
component defensive: update usages in edit-key-quota-dialog.tsx (references:
currentQuota, costTotal, the useState init for limitTotal and the later accesses
around the block noted ~lines 359–367) to guard against undefined (use optional
chaining, provide safe defaults, and gate UI/form logic when costTotal is
missing) so runtime errors cannot occur. Ensure the chosen change is applied
consistently across all places that read costTotal.
In `@src/app/`[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx:
- Around line 393-439: The code assumes key.quota.costTotal is always present
but KeyQuota.costTotal is optional in quota-helpers.ts, so update the UI to
guard access with key.quota?.costTotal (or check costTotal presence before
reading costTotal.limit/current) and pass those guarded values into
QuotaQuickEditPopover, QuotaProgress, getUsageRate and handleSaveLimit to avoid
runtime errors; alternatively, align the types by making KeyQuota.costTotal
required in quota-helpers.ts. For the visual inconsistency, either leave the
plain <span> (acceptable) or add a `total` variant to QuotaWindowType and render
<QuotaWindowType type="total" /> to match other columns' styling.
In `@src/lib/utils/quota-helpers.ts`:
- Line 13: Update the quota type so costTotal is required (change costTotal?: {
current: number; limit: number | null } to costTotal: { current: number; limit:
number | null }) and adjust helper logic accordingly: in hasKeyQuotaSet and
getMaxUsageRate replace any costTotal?.limit checks with costTotal.limit (and
remove unnecessary null guards where server guarantees costTotal), and ensure
any internal reads use costTotal.current/limit directly; keep function
signatures (hasKeyQuotaSet, getMaxUsageRate) and other helper uses consistent
with the non-optional costTotal type.
---
Outside diff comments:
In
`@src/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 396-409: The clear-all button render condition in DialogFooter
uses currentQuota checks but omits costTotal and costDaily, so when only
limitTotalUsd or costDaily.limit is set the Button (variant "destructive",
onClick handleClearQuota) won't render; update the boolean expression that
decides rendering to include currentQuota?.costTotal.limit and
currentQuota?.costDaily.limit (and keep the existing
currentQuota?.concurrentSessions.limit null-coalescing check) so the Button (and
its disabled prop isPending and label t("clearAll")) appears whenever any quota
dimension is > 0.
In `@tests/unit/actions/providers-usage.test.ts`:
- Around line 20-42: The tests define sumProviderTotalCostMock but never assert
it or the returned total limit, so add assertions in both the single-provider
and batch-provider test cases to ensure the mock is called with the correct
provider id(s) and that result.data.limitTotalUsd.current equals the expected
mocked value; update the tests that exercise the code paths which call
sumProviderTotalCost (refer to sumProviderTotalCostMock) to set a deterministic
return value and assert both sumProviderTotalCostMock was invoked and
result.data.limitTotalUsd.current matches that value (do this for the individual
test and the batch test where multiple providers are evaluated).
---
Nitpick comments:
In `@messages/en/quota.json`:
- Around line 242-246: 两个文案键在表达同一概念但用词不一致:keys.editDialog.limitTotalUsd.label
("Total Quota (USD)") 与 keys.editKeyForm.limitTotalUsd.label ("Total Cost Limit
(USD)");请选择统一的术语(例如统一为 "Total Cost Limit (USD)" 或 "Total Quota
(USD)"),然后在所有语言文件中同步替换相应键的值(确保 keys.editDialog.limitTotalUsd.label 和
keys.editKeyForm.limitTotalUsd.label 在各语言版本一致),提交修改以保持端到端文案统一。
In
`@src/app/`[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx:
- Around line 13-20: Rename the inconsistent field limitTotalUsd in the
ProviderQuota interface to costTotal to match the cost* naming pattern and align
with KeyQuota.costTotal; update all usages and imports that reference
limitTotalUsd (e.g., in providers-quota-client.tsx and any mappers or
serializers) to use costTotal, and ensure the shape and types remain unchanged
while running type checks to catch any missed references.
In
`@src/app/`[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx:
- Around line 39-51: Reorder the checks inside hasQuotaLimit to match the same
time-sequence used in calculateMaxUsage: check cost5h, costDaily, costWeekly,
costMonthly, then limitTotalUsd, and finally concurrentSessions; update the
boolean expression in hasQuotaLimit (function name: hasQuotaLimit, symbol:
quota.limitTotalUsd, quota.cost5h, quota.costDaily, quota.costWeekly,
quota.costMonthly, quota.concurrentSessions) so the order matches
calculateMaxUsage for consistent readability.
🪄 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: 0bad63f7-c79e-4e87-a7a6-1bad4669dcb8
📒 Files selected for processing (15)
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/_components/providers-quota-manager.tsxsrc/app/[locale]/dashboard/quotas/providers/page.tsxsrc/lib/utils/quota-helpers.tstests/unit/actions/providers-usage.test.ts
| const [limitTotal, setLimitTotal] = useState<string>( | ||
| currentQuota?.costTotal.limit?.toString() ?? "" | ||
| ); |
There was a problem hiding this comment.
对 currentQuota?.costTotal 的访问与 KeyQuota 可选性约定不一致。
本文件 Line 35 把 costTotal 声明为必填,这里(Line 84、359、363、364)也按必填访问;而 src/lib/utils/quota-helpers.ts 中 KeyQuota.costTotal 是可选(costTotal?)。如果上游数据真的可能缺该字段,这些访问会在运行时抛错;反之应在 quota-helpers.ts 中也改为必填以消除歧义。建议先确定数据契约,再统一类型。
Also applies to: 359-367
🤖 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 83 - 85, The component accesses currentQuota.costTotal as if always
present while KeyQuota.costTotal is declared optional in quota-helpers.ts;
either make the data contract consistent by removing the optional from
KeyQuota.costTotal in quota-helpers.ts (if costTotal is guaranteed) or make the
component defensive: update usages in edit-key-quota-dialog.tsx (references:
currentQuota, costTotal, the useState init for limitTotal and the later accesses
around the block noted ~lines 359–367) to guard against undefined (use optional
chaining, provide safe defaults, and gate UI/form logic when costTotal is
missing) so runtime errors cannot occur. Ensure the chosen change is applied
consistently across all places that read costTotal.
| {/* 总限额 */} | ||
| <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")} | ||
| </span> | ||
| </div> | ||
| <div className="flex items-center gap-2"> | ||
| <span className="text-xs font-mono"> | ||
| {formatCurrency(key.quota.costTotal.current, currencyCode)}/ | ||
| <QuotaQuickEditPopover | ||
| currentLimit={key.quota.costTotal.limit} | ||
| label={t("table.costTotal")} | ||
| unit="currency" | ||
| currencyCode={currencyCode} | ||
| onSave={(v) => | ||
| handleSaveLimit(key.id, key.name, "limitTotalUsd", v) | ||
| } | ||
| > | ||
| <button | ||
| type="button" | ||
| className="underline-offset-4 hover:underline cursor-pointer rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||
| > | ||
| {formatCurrency(key.quota.costTotal.limit, currencyCode)} | ||
| </button> | ||
| </QuotaQuickEditPopover> | ||
| </span> | ||
| </div> | ||
| <QuotaProgress | ||
| current={key.quota.costTotal.current} | ||
| limit={key.quota.costTotal.limit} | ||
| className="h-1" | ||
| /> | ||
| <div className="text-xs text-muted-foreground text-right"> | ||
| {getUsageRate( | ||
| key.quota.costTotal.current, | ||
| key.quota.costTotal.limit | ||
| ).toFixed(1)} | ||
| % | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <span className="text-sm text-muted-foreground">-</span> | ||
| )} | ||
| </TableCell> |
There was a problem hiding this comment.
新增"总限额"列实现合理,但提示几处细节。
- Line 395 直接访问
key.quota.costTotal.limit,前提是costTotal必存在。src/lib/utils/quota-helpers.ts中KeyQuota.costTotal是可选(costTotal?),与此处本地类型(Line 38 必填)不一致——参见quota-helpers.ts中的相关评论,需统一以避免潜在运行时崩溃。 - 与其他列(5h/日/周/月)使用
QuotaWindowType组件展示窗口标签不同,此处(Line 397-401)用了一个普通<span>显示t("table.costTotal"),造成视觉风格略不一致。由于总额没有窗口概念,这种处理可以接受;若想统一可考虑增加一个QuotaWindowType的total类型。
🤖 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/keys-quota-client.tsx
around lines 393 - 439, The code assumes key.quota.costTotal is always present
but KeyQuota.costTotal is optional in quota-helpers.ts, so update the UI to
guard access with key.quota?.costTotal (or check costTotal presence before
reading costTotal.limit/current) and pass those guarded values into
QuotaQuickEditPopover, QuotaProgress, getUsageRate and handleSaveLimit to avoid
runtime errors; alternatively, align the types by making KeyQuota.costTotal
required in quota-helpers.ts. For the visual inconsistency, either leave the
plain <span> (acceptable) or add a `total` variant to QuotaWindowType and render
<QuotaWindowType type="total" /> to match other columns' styling.
| costDaily: { current: number; limit: number | null }; | ||
| costWeekly: { current: number; limit: number | null }; | ||
| costMonthly: { current: number; limit: number | null }; | ||
| costTotal?: { current: number; limit: number | null }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查找服务端构造 KeyQuota 的位置,确认是否始终填充 costTotal
rg -nP -C3 '\bcostTotal\s*:' --type=ts -g '!**/*.test.ts'
rg -nP -C5 'quota\s*[:=]\s*\{' --type=ts -g 'src/actions/**' -g 'src/repository/**'Repository: ding113/claude-code-hub
Length of output: 6800
🏁 Script executed:
cat -n src/lib/utils/quota-helpers.ts | head -40Repository: ding113/claude-code-hub
Length of output: 1372
🏁 Script executed:
# Find where KeyQuota type is defined
rg -nP 'type\s+KeyQuota|interface\s+KeyQuota' --type=tsRepository: ding113/claude-code-hub
Length of output: 840
🏁 Script executed:
# Check src/actions/keys.ts to see if costTotal is always populated
sed -n '830,970p' src/actions/keys.tsRepository: ding113/claude-code-hub
Length of output: 4969
🏁 Script executed:
# Check keys-quota-client.tsx for costTotal access
sed -n '30,100p' src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsxRepository: ding113/claude-code-hub
Length of output: 2036
🏁 Script executed:
# Check edit-key-quota-dialog.tsx for costTotal access
sed -n '30,100p' src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsxRepository: ding113/claude-code-hub
Length of output: 2584
🏁 Script executed:
# Search for all unsafe costTotal.limit access without optional chaining
rg -n 'costTotal\.limit' --type=ts -B2 -A2Repository: ding113/claude-code-hub
Length of output: 6325
确认 costTotal 的可选性与其他文件不一致,存在潜在的类型安全隐患。
src/lib/utils/quota-helpers.ts 将 costTotal 声明为可选字段(第 13 行),但:
-
UI 文件本地声明为必填:
keys-quota-client.tsx第 38 行、edit-key-quota-dialog.tsx第 35 行、key-limit-usage.tsx第 21 行均声明costTotal为必填 -
存在不安全的直接访问:
keys-quota-client.tsx第 395、404、406、418、425、431 行直接访问key.quota.costTotal.limit而未进行空值检查edit-key-quota-dialog.tsx第 363-364 行在currentQuota?.costTotal.limit条件内直接访问costTotal.current和costTotal.limitkey-limit-usage.tsx第 102 行直接访问data.costTotal.limit
-
服务端始终填充:
src/actions/keys.ts的getKeyLimitUsage()函数(第 958-960 行)保证总是返回costTotal对象
建议将 costTotal 改为必填字段,并更新 quota-helpers.ts 中的辅助函数相应调整:
建议修改
- costTotal?: { current: number; limit: number | null };
+ costTotal: { current: number; limit: number | null };同时在 hasKeyQuotaSet() 和 getMaxUsageRate() 中将 costTotal?.limit 改为 costTotal.limit(第 36、92 行)。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/utils/quota-helpers.ts` at line 13, Update the quota type so
costTotal is required (change costTotal?: { current: number; limit: number |
null } to costTotal: { current: number; limit: number | null }) and adjust
helper logic accordingly: in hasKeyQuotaSet and getMaxUsageRate replace any
costTotal?.limit checks with costTotal.limit (and remove unnecessary null guards
where server guarantees costTotal), and ensure any internal reads use
costTotal.current/limit directly; keep function signatures (hasKeyQuotaSet,
getMaxUsageRate) and other helper uses consistent with the non-optional
costTotal type.
| @@ -17,6 +17,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; | |||
| const getSessionMock = vi.fn(); | |||
There was a problem hiding this comment.
[Medium] [TEST-MISSING-CRITICAL] No test assertions for the new limitTotalUsd field
Why this is a problem: The PR adds a mock for sumProviderTotalCost returning 0, but no test case verifies that the returned data includes the limitTotalUsd field with correct values. The existing test "should return correct structure with DB-sourced costs" asserts cost5h, costDaily, costWeekly, costMonthly but not limitTotalUsd. If the limitTotalUsd computation logic breaks in the future, no test will catch it. CLAUDE.md requires: "All new features must have unit test coverage of at least 80%".
Suggested fix - Add assertions to the existing "should return correct structure" tests:
// In "should return correct structure with DB-sourced costs" test:
sumProviderTotalCostMock.mockResolvedValueOnce(50.0);
if (result.ok) {
// ... existing assertions ...
expect(result.data.limitTotalUsd.current).toBe(50.0);
expect(result.data.limitTotalUsd.limit).toBeNull();
}
// In batch test "should return correct costs from DB for each provider":
// Add assertions for limitTotalUsd in both provider results
expect(p1Data?.limitTotalUsd.current).toBe(0);
expect(p1Data?.limitTotalUsd.limit).toBeNull();| @@ -127,6 +132,7 @@ export function EditKeyQuotaDialog({ | |||
| dailyResetTime: resetTime, | |||
There was a problem hiding this comment.
[Medium] [LOGIC-BUG] "Clear All" button visibility condition missing costTotal.limit
Why this is a problem: This new line clears limitTotalUsd when the admin clicks "Clear All", but the button visibility condition (in DialogFooter, the currentQuota?.cost5h.limit || costWeekly.limit || ... expression) does not include currentQuota?.costTotal?.limit. If a key has only limitTotalUsd set (no 5h/weekly/monthly/concurrent quotas), the "Clear All" button will not appear, making it impossible to clear the total quota via this button. Note: costDaily.limit has the same pre-existing gap.
Suggested fix - Update the button visibility condition in DialogFooter:
{(currentQuota?.cost5h.limit ||
currentQuota?.costDaily.limit ||
currentQuota?.costWeekly.limit ||
currentQuota?.costMonthly.limit ||
currentQuota?.costTotal?.limit ||
(currentQuota?.concurrentSessions.limit ?? 0) > 0) && (There was a problem hiding this comment.
Code Review Summary
This PR adds limitTotalUsd (total cost quota) support to provider and API key quota management, extending the existing time-windowed quota system with a lifetime spend limit. The implementation is consistent with existing patterns for time-based quotas. Two medium-severity issues were identified: missing test assertions for the new field and a UI button visibility gap.
PR Size: M
- Lines changed: 207
- Files changed: 15
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 1 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 1 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Medium Priority Issues (Should Fix)
-
[TEST-MISSING-CRITICAL] No test assertions for
limitTotalUsdfield (tests/unit/actions/providers-usage.test.ts)- The mock for
sumProviderTotalCostis added and configured to return 0, but no test verifies thelimitTotalUsdfield appears in the returned data with correct values. BothgetProviderLimitUsageandgetProviderLimitUsageBatchare affected. Per CLAUDE.md: "All new features must have unit test coverage of at least 80%".
- The mock for
-
[LOGIC-BUG] "Clear All" button visibility condition missing
costTotal.limit(edit-key-quota-dialog.tsx)- The
handleClearQuotafunction now clearslimitTotalUsd, but the button visibility condition inDialogFooterdoes not checkcurrentQuota?.costTotal?.limit. A key with only a total quota set will not show the "Clear All" button.
- The
Review Coverage
- Logic and correctness - Gap found (Clear All button condition)
- Security (OWASP Top 10) - Clean (numeric values only, schema validation in editKey action)
- Error handling - Clean (follows existing try/catch patterns with logging)
- Type safety - Clean (types updated consistently across interfaces)
- Documentation accuracy - Clean (comments updated to include new quota type)
- Test coverage - Gap found (no assertions for limitTotalUsd return value)
- Code clarity - Good (follows existing patterns consistently)
Automated review by Claude AI
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsx (1)
14-21: LGTM:类型扩展与后端返回结构一致。
ProviderQuota新增limitTotalUsd: { current; limit; resetAt? }与src/actions/providers.ts中ProviderLimitUsageData的形状对齐。可选的小建议:在
providers-quota-client.tsx中新增字段被放到了concurrentSessions之后,本文件放在了concurrentSessions之前。两处都正确,但保持字段顺序一致便于后续维护与代码评审 diff 阅读。长期看,这两处重复的ProviderQuota接口定义可以抽取到共享类型文件中(属于既有代码风格,不必本 PR 处理)。🤖 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-manager.tsx around lines 14 - 21, The ProviderQuota interface field order differs from providers-quota-client.tsx (limitTotalUsd placed before concurrentSessions here but after in the client) which makes diffs noisy; update the ProviderQuota definition in this file to match the same field order used in providers-quota-client.tsx (place limitTotalUsd after concurrentSessions) and ensure the shape still matches ProviderLimitUsageData from src/actions/providers.ts so runtime typings remain correct.src/actions/providers.ts (2)
2741-2757: LGTM:总额度统计已传入 reset 边界。
getProviderLimitUsage与getProviderLimitUsageBatch两条路径都已将provider.totalCostResetAt传给sumProviderTotalCost,与resetProviderTotalUsage写入的重置时间戳形成正确闭环。附一个小小的一致性建议(可选):单条路径 line 2755 直接传
provider.totalCostResetAt,而批量路径 line 2914 使用?? null兜底。函数签名是Date | null | undefined,两种写法都正确,只是风格不一致。如愿统一成?? null会更明显地表达"未设置即视为不限制聚合下限"的意图。Also applies to: 2898-2915
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/actions/providers.ts` around lines 2741 - 2757, Unify the way we pass the provider total-cost reset boundary: in both getProviderLimitUsage and getProviderLimitUsageBatch ensure the call to sumProviderTotalCost uses provider.totalCostResetAt ?? null (rather than sometimes passing provider.totalCostResetAt directly) so the Date | null | undefined signature is handled consistently; update the call sites (e.g., in getProviderLimitUsage where sumProviderTotalCost(providerId, provider.totalCostResetAt) is used and in getProviderLimitUsageBatch where you already use ?? null) to all use ?? null.
1258-1270: 缓存失效失败会让 DB 已成功的重置返回错误。
publishProviderCacheInvalidation()直接await而未做异常隔离:当 Redis 发布失败(连接断开/超时等)时,异常会被外层catch捕获并返回{ ok: false, error: "重置供应商总用量失败" },但此时数据库的totalCostResetAt已经更新成功,会让前端误以为重置失败而引发重复操作。本文件中其它写路径(
addProvider、editProvider、removeProvider、batchUpdateProviders、batchDeleteProviders)统一使用了broadcastProviderCacheInvalidation(...)这一带 try/catch 的封装。建议这里同样改为带容错的调用,或就地包裹 try/catch 仅记录 warn 而不影响成功语义。建议修正
const ok = await resetProviderTotalCostResetAt(providerId, new Date()); if (!ok) { return { ok: false, error: "供应商不存在" }; } - await publishProviderCacheInvalidation(); + await broadcastProviderCacheInvalidation({ operation: "edit", providerId }); return { ok: true };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/actions/providers.ts` around lines 1258 - 1270, The DB reset path currently awaits publishProviderCacheInvalidation() directly which can throw and turn a successful resetProviderTotalCostResetAt(providerId, new Date()) into an overall failure; wrap the cache publish in a try/catch (or replace the call with the existing fault-tolerant helper broadcastProviderCacheInvalidation(...)) so any Redis/publish errors are logged (use logger.warn or similar) but do not propagate, ensuring the function still returns { ok: true } when the DB update succeeded; keep the outer catch for real DB errors.
🤖 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/app/`[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx:
- Around line 359-367: The conditional rendering for the costTotal line uses a
truthy check and hides the row when limit === 0; update the check in
edit-key-quota-dialog.tsx to explicitly test for null/undefined (e.g.,
currentQuota?.costTotal.limit !== null && currentQuota?.costTotal.limit !==
undefined or currentQuota?.costTotal.limit != null) so that a 0 limit still
renders the paragraph that references currentQuota.costTotal.current and .limit;
make the same change only for the new costTotal branch (leave other branches
unchanged).
---
Nitpick comments:
In `@src/actions/providers.ts`:
- Around line 2741-2757: Unify the way we pass the provider total-cost reset
boundary: in both getProviderLimitUsage and getProviderLimitUsageBatch ensure
the call to sumProviderTotalCost uses provider.totalCostResetAt ?? null (rather
than sometimes passing provider.totalCostResetAt directly) so the Date | null |
undefined signature is handled consistently; update the call sites (e.g., in
getProviderLimitUsage where sumProviderTotalCost(providerId,
provider.totalCostResetAt) is used and in getProviderLimitUsageBatch where you
already use ?? null) to all use ?? null.
- Around line 1258-1270: The DB reset path currently awaits
publishProviderCacheInvalidation() directly which can throw and turn a
successful resetProviderTotalCostResetAt(providerId, new Date()) into an overall
failure; wrap the cache publish in a try/catch (or replace the call with the
existing fault-tolerant helper broadcastProviderCacheInvalidation(...)) so any
Redis/publish errors are logged (use logger.warn or similar) but do not
propagate, ensuring the function still returns { ok: true } when the DB update
succeeded; keep the outer catch for real DB errors.
In
`@src/app/`[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsx:
- Around line 14-21: The ProviderQuota interface field order differs from
providers-quota-client.tsx (limitTotalUsd placed before concurrentSessions here
but after in the client) which makes diffs noisy; update the ProviderQuota
definition in this file to match the same field order used in
providers-quota-client.tsx (place limitTotalUsd after concurrentSessions) and
ensure the shape still matches ProviderLimitUsageData from
src/actions/providers.ts so runtime typings remain correct.
🪄 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: 978144e0-4f21-47b5-bbfb-00904d6a16aa
📒 Files selected for processing (12)
src/actions/key-quota.tssrc/actions/keys.tssrc/actions/providers.tssrc/app/[locale]/dashboard/_components/user/key-limit-usage.tsxsrc/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/_components/providers-quota-manager.tsxsrc/app/[locale]/dashboard/quotas/providers/page.tsxsrc/types/provider.ts
✅ Files skipped from review due to trivial changes (3)
- src/types/provider.ts
- src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsx
- src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- src/app/[locale]/dashboard/quotas/providers/page.tsx
- src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx
- src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx
| {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.
limit 为 0 时不应吞掉提示展示。
L359 currentQuota?.costTotal.limit && (...) 使用 truthy 判断,当 limit === 0 时提示行不会渲染。虽然项目其它分支(limit5h 等)均沿用了同一模式,但本 PR 的目标说明里明确要求改为 limit !== null 以允许 0 值场景显示。建议至少在新增的 costTotal 上采用更准确的判定:
建议修改
- {currentQuota?.costTotal.limit && (
+ {currentQuota?.costTotal?.limit != 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 359 - 367, The conditional rendering for the costTotal line uses a
truthy check and hides the row when limit === 0; update the check in
edit-key-quota-dialog.tsx to explicitly test for null/undefined (e.g.,
currentQuota?.costTotal.limit !== null && currentQuota?.costTotal.limit !==
undefined or currentQuota?.costTotal.limit != null) so that a 0 limit still
renders the paragraph that references currentQuota.costTotal.current and .limit;
make the same change only for the new costTotal branch (leave other branches
unchanged).
Summary
Adds total cost quota (`limitTotalUsd`) support to provider and API key quota management, completing the quota system alongside existing 5h, daily, weekly, and monthly quotas.
Problem
The quota system previously supported time-windowed limits (5h, daily, weekly, monthly) but lacked a lifetime/total cost limit. This prevented administrators from setting a hard cap on total spend for providers or keys, which is essential for budget control and preventing runaway costs.
Solution
Extends the quota system to support `limitTotalUsd` (total cost limit) for both providers and API keys:
Changes
Core Changes
UI Changes
i18n Updates
Test Updates
Testing
Automated Tests
Manual Testing
Related PRs
Checklist
Description enhanced by Claude AI
Greptile Summary
This PR completes the quota system by adding
limitTotalUsd(lifetime total cost limit) support for both providers and API keys, covering data fetching, UI display, quick-edit, and i18n across all five supported languages. The core logic is sound:sumProviderTotalCostcorrectly receivestotalCostResetAtin both the single and batch paths, andpublishProviderCacheInvalidationis now properly called afterresetProviderTotalUsage.Confidence Score: 5/5
Safe to merge; only P2 style/test quality issues found.
All core logic (quota enforcement,
resetAtforwarding, cache invalidation) is correct. Two P2 findings: the test mock drops theresetAtargument, and the list item omitsresetAtwhen callingrenderQuotaItem, suppressing the countdown timer for total quota after a manual reset. Neither causes incorrect quota enforcement or data loss.provider-quota-list-item.tsx(missingresetAtinrenderQuotaItem) andproviders-usage.test.ts(mock dropsresetAtarg).Important Files Changed
limitTotalUsdtracking to both single and batch provider usage functions;sumProviderTotalCostnow correctly receivestotalCostResetAtin both paths. Also adds apublishProviderCacheInvalidationcall toresetProviderTotalUsage.renderQuotaItembut omitsresetAt, so the countdown timer is never shown after a manual reset.sumProviderTotalCostMockbut the mock wrapper silently drops theresetAtargument, preventing future assertions on argument correctness.hasKeyQuotaSetandgetMaxUsageRateto includecostTotal; uses optional chaining since the field is optional inKeyQuota, consistent with surrounding patterns.limitTotalstate and input field for the total quota in the key edit dialog; wired tolimitTotalUsdon save.hasQuotaLimitandcalculateMaxUsageto includelimitTotalUsd; null/zero checks are correct and consistent.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[getProviderLimitUsage / getProviderLimitUsageBatch] --> B[sumProviderTotalCost\nproviderId, totalCostResetAt] A --> C[sumProviderCostInTimeRange\n5h / daily / weekly / monthly] B --> D[limitTotalUsd result\ncurrent, limit, resetAt] C --> E[cost5h / costDaily / costWeekly / costMonthly] D --> F[ProviderLimitUsageData] E --> F F --> G[UI: providers-quota-client\nhasQuotaLimit / calculateMaxUsage] F --> H[UI: provider-quota-list-item\nrenderQuotaItem — resetAt missing] F --> I[UI: keys-quota-client\nTotal Quota column] J[quota-helpers.ts\nhasKeyQuotaSet / getMaxUsageRate] --> K[includes costTotal?.limit] L[edit-key-quota-dialog\nlimitTotal state] --> M[updateKeyQuota\nlimitTotalUsd field]Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "fix(quota): honor total reset markers" | Re-trigger Greptile