feat: add user savings estimates - #6499
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds configurable savings estimation from usage logs, historical reconstruction, summary and trend APIs, a pausable lifetime savings backfill system task with aggregation worker, dashboard cards and charts, wallet and usage-log details, audit events, localized strings, and design documentation. ChangesSavings estimates, trend reporting, and lifetime backfill
Estimated code review effort: 5 (Critical) | ~150 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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.
Actionable comments posted: 9
🧹 Nitpick comments (10)
service/savings_estimate_test.go (1)
481-493: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueParameter
modelshadows the importedmodelpackage.Harmless here, but it will break compilation the moment someone references
model.Xinside this helper. Rename tomodelName.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@service/savings_estimate_test.go` around lines 481 - 493, Rename the savingsRelayInfo parameter from model to modelName and update its OriginModelName assignment accordingly, avoiding shadowing of the imported model package.model/log.go (1)
117-136: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueOptional: hoist the setting lookup out of the loop.
savings_setting.ShowOnUsageLogs()takes an RWMutex read lock on every iteration. Reading it once before the loop is both cheaper and makes the redaction consistent across the whole page.♻️ Proposed refactor
func formatUserLogs(logs []*Log, startIdx int) { + showSavings := savings_setting.ShowOnUsageLogs() for i := range logs { ... - if !savings_setting.ShowOnUsageLogs() { + if !showSavings { delete(otherMap, "savings_estimate") }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@model/log.go` around lines 117 - 136, In formatUserLogs, read savings_setting.ShowOnUsageLogs() once before iterating over logs, store the result, and use that value for savings_estimate redaction inside the loop. Preserve the existing field-removal behavior and display-ID assignment.service/savings_estimate.go (2)
640-657: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConfusing nil-map fallback branch.
price, ok := localPrices[candidate]is executed first and then discarded whenlocalPrices == nil, which reads as a bug even though it works. An explicit branch is clearer.♻️ Proposed refactor
for _, candidate := range candidates { - price, ok := localPrices[candidate] - if localPrices == nil { - if localPricing, found := model.GetPricingByModel(candidate); found { - price = savingsOfficialPriceFromPricing(localPricing, priceSnapshotAt) - ok = true - } - } + var ( + price savings_setting.OfficialPrice + ok bool + ) + if localPrices != nil { + price, ok = localPrices[candidate] + } else if localPricing, found := model.GetPricingByModel(candidate); found { + price = savingsOfficialPriceFromPricing(localPricing, priceSnapshotAt) + ok = true + } if !ok { continue }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@service/savings_estimate.go` around lines 640 - 657, Clarify the candidate pricing lookup in the LocalPricingOfficialConfirmed branch by explicitly separating the localPrices-nil fallback from the normal localPrices[candidate] lookup. Update the loop around finalizeSavingsOfficialPrice so each branch sets price and ok without performing a lookup whose result is immediately discarded.
916-929: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUndocumented
|||guard.
strings.Contains(expr, "|||")silently rejects an expression form with no explanation. Add a short comment (or reference the rule inpkg/billingexpr/expr.md) so the intent survives future edits.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@service/savings_estimate.go` around lines 916 - 929, Document the purpose of the strings.Contains(expr, "|||") guard in calculateSavingsTieredTextQuota by adding a concise comment or referencing the applicable rule in pkg/billingexpr/expr.md. Keep the existing rejection behavior unchanged.Source: Coding guidelines
model/savings_log.go (1)
13-33: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftIndex coverage for the
(user_id, type, created_at)filter.
Logindexesuser_id,(user_id, id)and(created_at, type), but there is no composite index matching this predicate, so a 31-day window for a heavy user will scan a large slice oflogstwice (count + fetch). Consider a(user_id, created_at)(or(user_id, type, created_at)) index, and note that the ClickHouse log backend has different ordering characteristics.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@model/savings_log.go` around lines 13 - 33, The CountUserSavingsConsumeLogs and GetUserSavingsConsumeLogs queries need composite index coverage for their user, type, and created_at filters. Add the appropriate database index on Log, preferably (user_id, type, created_at), and account for the ClickHouse backend’s differing ordering characteristics without changing the existing query behavior.controller/savings.go (1)
14-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFour duplicated hand-rolled 400 responses; prefer the shared helper + i18n.
The rest of the codebase returns errors through
common.ApiError/message helpers, and user-facing strings elsewhere go throughi18n.T(c, ...). These blocks hardcode Chinese and duplicate the same shape four times.♻️ Suggested consolidation
+func abortSavingsBadRequest(c *gin.Context, message string) { + c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": message}) +}Also applies to: 40-64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@controller/savings.go` around lines 14 - 29, Replace the duplicated 400-response blocks in the savings controller, including the handlers around parseSavingsTimeRange and NormalizeSavingsSummaryWindow, with the established common.ApiError/message helper flow. Route user-facing messages through i18n.T(c, ...) and preserve each branch’s existing validation error semantics while consolidating the repeated response shape across all four cases.setting/savings_setting/config.go (1)
195-208: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTrimming keys can silently drop a config entry.
If the admin JSON contains both
"gpt-4o"and" gpt-4o", the trimmed insert overwrites the other entry non-deterministically (map iteration order). Also, re-inserting the trimmed key duringrangemay re-visit it; normalization is idempotent so it's harmless today, but it is fragile.Consider building a new map instead of mutating in place.
♻️ Proposed refactor
- for rawModel, price := range s.OfficialPrices { - model := strings.TrimSpace(rawModel) - if model == "" { - delete(s.OfficialPrices, rawModel) - continue - } - if model != rawModel { - delete(s.OfficialPrices, rawModel) - } - price.SourceURL = publicSourceURL(price.SourceURL) - price.Source = strings.TrimSpace(price.Source) - price.BillingMode = strings.TrimSpace(price.BillingMode) - s.OfficialPrices[model] = price - } + normalized := make(map[string]OfficialPrice, len(s.OfficialPrices)) + for rawModel, price := range s.OfficialPrices { + model := strings.TrimSpace(rawModel) + if model == "" { + continue + } + price.SourceURL = publicSourceURL(price.SourceURL) + price.Source = strings.TrimSpace(price.Source) + price.BillingMode = strings.TrimSpace(price.BillingMode) + normalized[model] = price + } + s.OfficialPrices = normalized🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@setting/savings_setting/config.go` around lines 195 - 208, Update the OfficialPrices normalization loop to build a separate map rather than deleting and re-inserting entries while ranging over s.OfficialPrices. Trim each model key and normalize its price fields before adding it to the new map, explicitly handle collisions between keys that normalize to the same value, then replace s.OfficialPrices with the resulting map.setting/savings_setting/config_test.go (1)
9-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
assertfor independent, non-fatal checks.All assertions use
require, so inTestUpdateSettingSanitizesOfficialSourceURLaSourcemismatch aborts before checkingSourceURL, and inTestUpdateSettingUsesLocalPricingAndLegacyRebuildDefaultsa failedLocalPricingOfficialConfirmedcheck hides theRebuildLegacyLogsresult. These are independent, non-fatal property checks.As per coding guidelines, "New or substantially rewritten Go backend tests must use testify/require for setup and fatal assertions and testify/assert for non-fatal checks."
♻️ Proposed fix
import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ setting := GetSetting() price, ok := setting.OfficialPrices["gpt-4o-mini"] require.True(t, ok) - require.Equal(t, "OpenAI", price.Source) - require.Equal(t, "https://example.com/pricing?model=gpt", price.SourceURL) + assert.Equal(t, "OpenAI", price.Source) + assert.Equal(t, "https://example.com/pricing?model=gpt", price.SourceURL) } @@ setting := GetSetting() - require.True(t, setting.LocalPricingOfficialConfirmed) - require.True(t, setting.RebuildLegacyLogs) + assert.True(t, setting.LocalPricingOfficialConfirmed) + assert.True(t, setting.RebuildLegacyLogs) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@setting/savings_setting/config_test.go` around lines 9 - 46, Update the independent property checks in TestUpdateSettingSanitizesOfficialSourceURL and TestUpdateSettingUsesLocalPricingAndLegacyRebuildDefaults to use testify/assert instead of require, while keeping require for setup, cleanup, and other fatal assertions.Source: Coding guidelines
web/src/features/system-settings/models/savings-estimate-settings.tsx (1)
76-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd unit tests for
parseSavingsSetting/formatSavingsSetting.These are pure functions with meaningful normalization logic (type coercion, legacy-field stripping, default fallback) but ship without a test file.
As per coding guidelines, "新增功能、缺陷修复或行为修改必须同步新增或更新测试" (new features must be accompanied by new/updated tests).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-estimate-settings.tsx` around lines 76 - 129, Add unit tests covering parseSavingsSetting and formatSavingsSetting, including valid JSON normalization, boolean and numeric fallback behavior, legacy-field removal, invalid JSON handling, and formatting preservation for unparseable values. Use the existing DEFAULT_SETTING expectations to assert normalized defaults and verify valid settings are serialized with the expected indentation.Source: Coding guidelines
web/src/features/dashboard/lib/savings-chart.ts (1)
27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse the shared
TimeGranularityunion here.
DashboardTimeGranularityduplicatesTimeGranularityfrom@/lib/time, including the same values (hour,day,week). ImportTimeGranularityto avoid local drift from the canonical granularity type.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/dashboard/lib/savings-chart.ts` at line 27, Replace the local DashboardTimeGranularity declaration with the shared TimeGranularity type from "`@/lib/time`", importing it where needed and updating references in savings-chart.ts to use the canonical union.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@service/savings_estimate.go`:
- Around line 363-380: In service/savings_estimate.go lines 363-380, update
loadUserSavingsRows to cache the computed per-user summary/trend result for a
short TTL, or replace repeated row processing with database-side aggregation;
preserve existing limits and return values. In router/api-router.go lines 91-92,
apply middleware.SearchRateLimit() or CriticalRateLimit() to both
/savings/summary and /savings/trend, matching other scan-heavy read routes.
In `@web/src/features/dashboard/components/models/savings-trend-chart.tsx`:
- Around line 440-457: Mark the decorative Info icon as aria-hidden="true"
within the labelled TooltipTrigger button in
web/src/features/dashboard/components/models/savings-trend-chart.tsx lines
440-457, and apply the same change to the decorative ArrowUpRight icon inside
the labelled TooltipTrigger/Link in
web/src/features/dashboard/components/overview/summary-cards.tsx lines 440-456.
In `@web/src/features/dashboard/components/overview/summary-cards.tsx`:
- Around line 240-248: Update the savingsAmountDisplay calculation near
savingsSummary to use the shared currency configuration values already used by
the savings trend chart, preferably currency.quotaPerUnit and
currency.usdExchangeRate, instead of status?.quota_per_unit and
status?.usd_exchange_rate. Keep the existing null handling and
formatSavingsQuotaAsCNY call unchanged otherwise.
In `@web/src/i18n/locales/ru.json`:
- Line 703: Update the Russian translation for “Calculate estimated savings
using official model prices.” to use “оценочную экономию” instead of “экономию,”
preserving the estimated qualifier while keeping the rest of the translation
unchanged.
- Line 3681: Update the Russian translation for the “Recalculate legacy usage
logs” locale key from the imperfective “Пересчитывать” to the perfective
“Пересчитать,” preserving the existing key and punctuation.
- Around line 1128-1129: Update the Russian translations for “Covered request
actual cost” and “Covered requests” in the locale entries to use
“охваченных”/“Охваченные” consistently, replacing “учтённых”/“Учтённые” while
preserving the existing sentence structure.
In `@web/src/i18n/locales/vi.json`:
- Around line 3104-3105: Update the Vietnamese translations for "Official Price
Updated" and "Official price updated {{time}}" to use explicit completed-state
wording, such as "Giá chính thức đã được cập nhật" and "Giá chính thức được cập
nhật lúc {{time}}", while preserving the existing interpolation placeholder.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 4235: Update the translation value for “Show the savings summary and
trend on the user dashboard.” in the zh-TW locale to use `用戶` instead of `使用者`,
preserving the rest of the translation unchanged.
- Line 4790: Use consistent terminology for locally configured pricing in both
entries: update web/src/i18n/locales/zh-TW.json lines 4790-4790 and 5046-5046,
replacing 本機 with 本地 (or the same agreed equivalent) in the translations for
local marketplace prices and official pricing.
---
Nitpick comments:
In `@controller/savings.go`:
- Around line 14-29: Replace the duplicated 400-response blocks in the savings
controller, including the handlers around parseSavingsTimeRange and
NormalizeSavingsSummaryWindow, with the established common.ApiError/message
helper flow. Route user-facing messages through i18n.T(c, ...) and preserve each
branch’s existing validation error semantics while consolidating the repeated
response shape across all four cases.
In `@model/log.go`:
- Around line 117-136: In formatUserLogs, read savings_setting.ShowOnUsageLogs()
once before iterating over logs, store the result, and use that value for
savings_estimate redaction inside the loop. Preserve the existing field-removal
behavior and display-ID assignment.
In `@model/savings_log.go`:
- Around line 13-33: The CountUserSavingsConsumeLogs and
GetUserSavingsConsumeLogs queries need composite index coverage for their user,
type, and created_at filters. Add the appropriate database index on Log,
preferably (user_id, type, created_at), and account for the ClickHouse backend’s
differing ordering characteristics without changing the existing query behavior.
In `@service/savings_estimate_test.go`:
- Around line 481-493: Rename the savingsRelayInfo parameter from model to
modelName and update its OriginModelName assignment accordingly, avoiding
shadowing of the imported model package.
In `@service/savings_estimate.go`:
- Around line 640-657: Clarify the candidate pricing lookup in the
LocalPricingOfficialConfirmed branch by explicitly separating the
localPrices-nil fallback from the normal localPrices[candidate] lookup. Update
the loop around finalizeSavingsOfficialPrice so each branch sets price and ok
without performing a lookup whose result is immediately discarded.
- Around line 916-929: Document the purpose of the strings.Contains(expr, "|||")
guard in calculateSavingsTieredTextQuota by adding a concise comment or
referencing the applicable rule in pkg/billingexpr/expr.md. Keep the existing
rejection behavior unchanged.
In `@setting/savings_setting/config_test.go`:
- Around line 9-46: Update the independent property checks in
TestUpdateSettingSanitizesOfficialSourceURL and
TestUpdateSettingUsesLocalPricingAndLegacyRebuildDefaults to use testify/assert
instead of require, while keeping require for setup, cleanup, and other fatal
assertions.
In `@setting/savings_setting/config.go`:
- Around line 195-208: Update the OfficialPrices normalization loop to build a
separate map rather than deleting and re-inserting entries while ranging over
s.OfficialPrices. Trim each model key and normalize its price fields before
adding it to the new map, explicitly handle collisions between keys that
normalize to the same value, then replace s.OfficialPrices with the resulting
map.
In `@web/src/features/dashboard/lib/savings-chart.ts`:
- Line 27: Replace the local DashboardTimeGranularity declaration with the
shared TimeGranularity type from "`@/lib/time`", importing it where needed and
updating references in savings-chart.ts to use the canonical union.
In `@web/src/features/system-settings/models/savings-estimate-settings.tsx`:
- Around line 76-129: Add unit tests covering parseSavingsSetting and
formatSavingsSetting, including valid JSON normalization, boolean and numeric
fallback behavior, legacy-field removal, invalid JSON handling, and formatting
preservation for unparseable values. Use the existing DEFAULT_SETTING
expectations to assert normalized defaults and verify valid settings are
serialized with the expected indentation.
🪄 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 Plus
Run ID: a0e65be1-d737-40b5-a949-6334bec4b80b
📒 Files selected for processing (43)
controller/audit.gocontroller/option.gocontroller/savings.godocs/user-savings-estimate-design.mddocs/user-savings-trend-design.mdmodel/log.gomodel/log_format_test.gomodel/option.gomodel/pricing.gomodel/savings_log.gorouter/api-router.goservice/savings_estimate.goservice/savings_estimate_test.goservice/text_quota.gosetting/savings_setting/config.gosetting/savings_setting/config_test.goweb/src/features/dashboard/api.tsweb/src/features/dashboard/components/models/savings-trend-chart.tsxweb/src/features/dashboard/components/overview/summary-cards.tsxweb/src/features/dashboard/index.tsxweb/src/features/dashboard/lib/__tests__/savings-chart.test.tsweb/src/features/dashboard/lib/__tests__/savings-i18n.test.tsweb/src/features/dashboard/lib/__tests__/savings-time-range.test.tsweb/src/features/dashboard/lib/savings-chart.tsweb/src/features/dashboard/lib/savings.tsweb/src/features/dashboard/types.tsweb/src/features/models/components/drawers/model-mutate-drawer.tsxweb/src/features/system-settings/billing/index.tsxweb/src/features/system-settings/billing/section-registry.tsxweb/src/features/system-settings/models/index.tsxweb/src/features/system-settings/models/ratio-settings-card.tsxweb/src/features/system-settings/models/savings-estimate-settings.tsxweb/src/features/system-settings/types.tsweb/src/features/usage-logs/components/dialogs/details-dialog.tsxweb/src/features/usage-logs/lib/format.tsweb/src/features/usage-logs/types.tsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.json
- harden savings aggregation with rate limits, bounded settings, caching, and indexes - localize validation errors and align dashboard currency, accessibility, and translations - add regression tests and document remediation decisions
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 (3)
web/src/features/system-settings/models/savings-estimate-settings.tsx (3)
323-330: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winHide decorative icons from assistive technology.
Code2andSaveare next to visible button labels, so they are decorative. Addaria-hidden="true"to comply with the accessibility guideline.Also applies to: 343-353
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-estimate-settings.tsx` around lines 323 - 330, Update the Code2 and Save icon components in the mode-change buttons handled by handleModeChange to include aria-hidden="true", keeping the existing visible button labels and behavior unchanged.Source: Coding guidelines
121-134: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winNormalize JSON-mode settings before saving.
JSON validation only checks the root object, while Lines 122-123 send
jsonTextunchanged. This bypassesparseSavingsSettingfor values such asofficial_prices: [], invalid booleans, and fractional limits. The existing parser contract and its tests expect these values to be normalized before persistence.Proposed fix
- const currentText = - editMode === 'visual' ? JSON.stringify(setting) : jsonText - if (editMode === 'json' && !validation.valid) { + const currentSetting = + editMode === 'visual' ? setting : parseSavingsSetting(jsonText) + if (!currentSetting) { toast.error(validationMessage) return } + const currentText = JSON.stringify(currentSetting)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-estimate-settings.tsx` around lines 121 - 134, Update handleSave to parse and normalize JSON-mode input with the existing parseSavingsSetting contract before persistence, rather than sending jsonText unchanged. Ensure values such as official_prices, booleans, and fractional limits undergo the parser’s normalization, while preserving visual-mode behavior and the existing no-changes check.
136-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winHandle
mutateAsyncerrors inhandleSave.
useUpdateOptionalready callsonError, butawait updateOption.mutateAsync(...)still rejects, andhandleSavehas no localtry/catch, so save failures can surface as unhandled promise rejections. Wrap the mutation await and keep the success toast after the await path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-estimate-settings.tsx` around lines 136 - 139, Update handleSave to wrap the updateOption.mutateAsync call in a local try/catch, handling rejected saves without unhandled promise rejections. Keep the success toast only after the await completes successfully, and preserve the existing useUpdateOption onError behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/user-savings-review-remediation.md`:
- Line 63: 修正该 Markdown 表格中 N4 行的 `|||`
内容,避免被解析为额外列;请改写该单元格或转义竖线字符,并确保整行仍与表头列数一致、通过 MD056。
In `@web/src/features/system-settings/models/savings-estimate-setting.ts`:
- Around line 86-94: Update the validation condition in the savings-setting
normalization logic to reject values below 1 before flooring, while retaining
the existing finite-number and fallback behavior. Ensure values such as 0.5 use
DEFAULT_SAVINGS_SETTING rather than becoming 0, and add a regression test
covering this case.
---
Outside diff comments:
In `@web/src/features/system-settings/models/savings-estimate-settings.tsx`:
- Around line 323-330: Update the Code2 and Save icon components in the
mode-change buttons handled by handleModeChange to include aria-hidden="true",
keeping the existing visible button labels and behavior unchanged.
- Around line 121-134: Update handleSave to parse and normalize JSON-mode input
with the existing parseSavingsSetting contract before persistence, rather than
sending jsonText unchanged. Ensure values such as official_prices, booleans, and
fractional limits undergo the parser’s normalization, while preserving
visual-mode behavior and the existing no-changes check.
- Around line 136-139: Update handleSave to wrap the updateOption.mutateAsync
call in a local try/catch, handling rejected saves without unhandled promise
rejections. Keep the success toast only after the await completes successfully,
and preserve the existing useUpdateOption onError behavior.
🪄 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 Plus
Run ID: e64bf6c0-9cf3-47de-a505-a8fd286d6552
📒 Files selected for processing (25)
controller/savings.gocontroller/savings_test.godocs/user-savings-review-remediation.mdi18n/keys.goi18n/locales/en.yamli18n/locales/zh-CN.yamli18n/locales/zh-TW.yamlmodel/log.gorouter/api-router.goservice/savings_estimate.goservice/savings_estimate_test.gosetting/savings_setting/config.gosetting/savings_setting/config_test.goweb/src/features/dashboard/components/models/savings-trend-chart.tsxweb/src/features/dashboard/components/overview/summary-cards.tsxweb/src/features/dashboard/lib/__tests__/savings-i18n.test.tsweb/src/features/dashboard/lib/__tests__/savings-time-range.test.tsweb/src/features/dashboard/lib/savings-chart.tsweb/src/features/dashboard/lib/savings.tsweb/src/features/system-settings/models/__tests__/savings-estimate-setting.test.tsweb/src/features/system-settings/models/savings-estimate-setting.tsweb/src/features/system-settings/models/savings-estimate-settings.tsxweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.json
🚧 Files skipped from review as they are similar to previous changes (12)
- router/api-router.go
- web/src/features/dashboard/lib/tests/savings-time-range.test.ts
- web/src/features/dashboard/lib/savings-chart.ts
- controller/savings.go
- web/src/features/dashboard/lib/savings.ts
- web/src/features/dashboard/components/models/savings-trend-chart.tsx
- setting/savings_setting/config.go
- web/src/i18n/locales/vi.json
- web/src/features/dashboard/components/overview/summary-cards.tsx
- web/src/i18n/locales/zh-TW.json
- web/src/i18n/locales/ru.json
- service/savings_estimate.go
- validate numeric limits before flooring and normalize JSON mode - catch rejected saves and hide decorative setting icons - update the remediation document for follow-up review
- aggregate savings into idempotent event, daily, and lifetime totals - add resumable historical backfill and visual administration controls - show frozen RMB savings on dashboard and wallet with localized status
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/src/i18n/locales/en.json (1)
45-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAvoid a hard-coded plural for a count of one.
With
count = 1, this renders as “1 historical requests…”. Use count-safe wording such as “Recalculated historical requests at current official prices: {{count}}”, or add singular/plural variants consistently across locales and callers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/i18n/locales/en.json` at line 45, Update the locale entry for “{{count}} historical requests recalculated at current official prices” to use count-safe wording that remains grammatically correct when count is 1, preferably by placing the count after a neutral phrase. Keep the interpolation and corresponding translation key consistent with existing callers.web/src/i18n/locales/ru.json (1)
5092-5092: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate “overrides” as “переопределения.”
исключенийmeans exceptions, not configuration overrides, and may mislead administrators about howofficial_pricesis used. Preferofficial_prices нужен только для переопределений.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/i18n/locales/ru.json` at line 5092, Update the Russian translation for the key containing “official_prices” to translate “overrides” as “переопределений” rather than “исключений,” preserving the rest of the localized message.
🧹 Nitpick comments (7)
model/savings_log_test.go (1)
58-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNote the fidelity limit of simulating ClickHouse over SQLite.
These tests force
common.SetLogDatabaseType(common.DatabaseTypeClickHouse)while the connection is in-memory SQLite, so they validate the composite-keyset predicate shape but not ClickHouse-specific SQL acceptance or ordering semantics. Worth a short comment in the test so a future reader does not assume ClickHouse coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@model/savings_log_test.go` around lines 58 - 82, Add a brief comment above TestGetSavingsLifetimeLogBatchUsesClickHouseCompositeKeyset explaining that it uses SQLite while forcing the ClickHouse database type, so it only validates the composite-keyset predicate shape and not ClickHouse-specific SQL acceptance or ordering semantics.service/savings_lifetime_backfill.go (2)
204-229: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winPer-user summary does a global pending-events count.
CountPendingSavingsLifetimeEvents()scans the whole events table (not scoped touserID) on every dashboard/wallet summary request, andGetLatestSystemTaskadds another query. Consider caching the pending count / backfill status for a few seconds in memory since it is global state, or derivingIsCompletefrom the task row alone.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@service/savings_lifetime_backfill.go` around lines 204 - 229, The per-user summary currently performs a global CountPendingSavingsLifetimeEvents query on every request. Update the summary flow around GetLatestSystemTask and CountPendingSavingsLifetimeEvents to avoid this repeated table scan, preferably by deriving IsComplete from the global backfill task status or reusing a short-lived in-memory cache for the global pending count and status.
400-414: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCompute
amountMicrosonly when the frozen value is unusable.The fallback conversion runs unconditionally and can fail the whole batch (error return at Line 402) even when a frozen
SavingsCNYMicrosis present and would have been used. Reordering also avoids the redundant decimal work per row.♻️ Prefer the frozen snapshot first
estimate := result.Estimate - amountMicros, err := savingsLifetimeAmountMicros(int64(estimate.SavingsQuota), payload.QuotaPerUnit, payload.USDCNYRateMicros) - if err != nil { - return model.SavingsLifetimeEvent{}, false, err - } + var amountMicros int64 + frozenUsed := false if estimate.SavingsCNYMicros != "" && estimate.QuotaPerUnit > 0 && estimate.USDCNYRateMicros > 0 { if frozen, parseErr := strconv.ParseInt(estimate.SavingsCNYMicros, 10, 64); parseErr == nil { amountMicros = frozen + frozenUsed = true event.QuotaPerUnitSnapshot = estimate.QuotaPerUnit event.USDCNYRateMicros = estimate.USDCNYRateMicros } } - if event.QuotaPerUnitSnapshot == 0 { + if !frozenUsed { + converted, err := savingsLifetimeAmountMicros(int64(estimate.SavingsQuota), payload.QuotaPerUnit, payload.USDCNYRateMicros) + if err != nil { + return model.SavingsLifetimeEvent{}, false, err + } + amountMicros = converted event.QuotaPerUnitSnapshot = payload.QuotaPerUnit event.USDCNYRateMicros = payload.USDCNYRateMicros }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@service/savings_lifetime_backfill.go` around lines 400 - 414, Update the conversion flow around savingsLifetimeAmountMicros to parse and validate the frozen estimate.SavingsCNYMicros snapshot first; when it is usable, assign amountMicros and snapshot fields without calling the fallback conversion. Only invoke savingsLifetimeAmountMicros and return its error when the frozen value is absent or unusable, while preserving the existing payload snapshot fallback.web/src/features/system-settings/models/savings-lifetime-backfill.tsx (2)
67-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider splitting this component.
The file is ~280 lines with four mutations plus presentation. Extracting a
useSavingsLifetimeBackfill()hook for the query/mutations and a small progress/summary subcomponent would keep it within the size guidance.As per coding guidelines: "组件文件超过约 200 行时,应考虑拆分子组件或提取自定义 Hook."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-lifetime-backfill.tsx` around lines 67 - 279, The SavingsLifetimeBackfill component combines query/mutation state management with presentation and exceeds the recommended size. Extract the query and four mutation workflows into a useSavingsLifetimeBackfill hook, and move the task progress/summary markup into a focused subcomponent; keep SavingsLifetimeBackfill responsible for layout and action controls while preserving the existing behavior and rendered values.Source: Coding guidelines
85-89: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winInvalidate dependent queries after the mutations succeed.
All four mutations only write the backfill task into its own cache entry. The savings summary shown on the dashboard/wallet (and any other query derived from backfill status) keeps serving stale data until its own refetch. Add an
invalidateQueriesfor the related keys alongside thesetQueryData.As per coding guidelines: "React Query 中数据获取使用
useQuery、变更使用useMutation;每个查询必须有唯一且层级一致的数组形式queryKey,成功后使相关 query 失效."Also applies to: 107-107, 121-121, 135-135
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/system-settings/models/savings-lifetime-backfill.tsx` around lines 85 - 89, Update all four mutation success handlers in the savings lifetime backfill flow to invalidate the related savings summary and other backfill-derived query keys after calling setQueryData with the successful task response. Use the existing array-form query key constants and React Query invalidateQueries pattern, while preserving the current cache update behavior.Source: Coding guidelines
model/log.go (1)
61-64: 🩺 Stability & Availability | 🔵 TrivialNew composite index on
logs— verify rollout plan for large tables.Adding
idx_logs_user_type_created_id(user_id, type, created_at, id) to theLogmodel is a sensible design for the new savings-log queries, but building this index viaAutoMigrateon an already largelogstable can be a long-running, potentially locking DDL operation, especially on MySQL 5.7 (the guideline's supported floor) where online DDL support is more limited than 8.0. Confirm the deployment runbook accounts for this (e.g., pre-flight index creation withALGORITHM=INPLACE, LOCK=NONEon MySQL, or running the migration during a low-traffic window) rather than relying purely on automatic migration at startup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@model/log.go` around lines 61 - 64, The new idx_logs_user_type_created_id index on Log requires an explicit large-table rollout plan rather than relying solely on AutoMigrate at startup. Update the migration or deployment flow to support pre-creating the index with MySQL-compatible online DDL where available, or ensure it runs during a documented low-traffic window, including compatibility with MySQL 5.7.web/src/features/dashboard/components/overview/summary-cards.tsx (1)
190-203: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConsider extracting the savings/lifetime summary block into its own component or hook.
This file now bundles two extra data-fetching queries (
savingsSummaryQuery,savingsLifetimeQuery) plus ~90 lines of derived display-string logic and a matching JSX block, on top of the pre-existing usage/runway summary. The whole reviewed file is well past the ~200-line guideline threshold for splitting.♻️ Suggested direction
Extract a
useSavingsSummaryCard()hook (owning both queries + all derived display strings) and a<SavingsSummaryCard />presentational component, then haveSummaryCardssimply render it. This keepsSummaryCardsfocused on the usage/runway concern and isolates the savings feature for easier future changes.As per coding guidelines: "组件文件超过约 200 行时,应考虑拆分子组件或提取自定义 Hook。"
Also applies to: 252-338, 467-558
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/dashboard/components/overview/summary-cards.tsx` around lines 190 - 203, Extract the savings-related logic from SummaryCards into a dedicated useSavingsSummaryCard hook containing savingsSummaryQuery, savingsLifetimeQuery, and their derived display strings, plus a SavingsSummaryCard presentational component for the matching JSX block. Update SummaryCards to use the new component so it remains focused on the usage/runway summary while preserving the existing savings data and display behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/user-savings-estimate-design.md`:
- Around line 649-656: Update the wallet-page display rule in the documented
recharge flow to explicitly allow partial backfill values only with “counted so
far” semantics, such as “已统计节省” or “累计统计中”. Ensure partial results are never
presented as final lifetime savings, and retain the requirement that completed,
reliable totals use the final cumulative-savings presentation.
In `@model/savings_lifetime.go`:
- Around line 34-55: Bound the integrity-check work performed by
checkSQLiteDatabaseIntegrity so request-path callers such as
CheckSavingsLifetimeSQLiteIntegrity do not run an unbounded full-database scan;
use a bounded quick-check form that stops after the first error while preserving
the existing error parsing and reporting behavior.
In `@web/src/features/system-info/components/system-tasks-panel.tsx`:
- Line 246: Update the hasActiveTasks logic in the system tasks panel so paused
tasks are excluded from the “Auto-refreshing” indicator. Track whether any task
is actively polling using isPollingStatus, while preserving the existing task
activity detection separately.
In `@web/src/features/system-settings/api.ts`:
- Around line 75-120: Explicitly annotate the return types of
startSavingsLifetimeBackfill, getSavingsLifetimeBackfill,
pauseSavingsLifetimeBackfill, resumeSavingsLifetimeBackfill, and
retrySavingsLifetimeBackfill with their exact Promise response types in
web/src/features/system-settings/api.ts:75-120. Also annotate isActiveStatus and
isPollingStatus with boolean in
web/src/features/system-info/components/system-tasks-panel.tsx:104-117; no other
changes are required.
In `@web/src/features/wallet/components/wallet-stats-card.tsx`:
- Around line 123-129: Update both formatSavingsCNYMicros calls in the lifetime
savings rendering branch to pass the active i18n.language locale, ensuring
formatting follows the selected UI language for complete and in-progress
savings.
In `@web/src/i18n/locales/ru.json`:
- Line 267: Update the Russian translation for “Aggregate new usage into a
frozen lifetime savings total.” to explicitly convey that new usage is accounted
for in the frozen total lifetime savings, preserving both aggregation and total
semantics.
- Line 1586: Update the Russian translation for “Enable and save lifetime
savings before starting a backfill.” so it explicitly refers to saving the
lifetime-savings setting, using the reviewer’s preferred wording and preserving
the instruction to enable it before the backfill.
In `@web/src/i18n/locales/zh.json`:
- Line 1749: Update the zh.json translation for the “Estimated: {{count}}” key
to use neutral wording such as “已估算:{{count}}” or “估算:{{count}}”, removing the
“successfully” claim while preserving the count placeholder.
---
Outside diff comments:
In `@web/src/i18n/locales/en.json`:
- Line 45: Update the locale entry for “{{count}} historical requests
recalculated at current official prices” to use count-safe wording that remains
grammatically correct when count is 1, preferably by placing the count after a
neutral phrase. Keep the interpolation and corresponding translation key
consistent with existing callers.
In `@web/src/i18n/locales/ru.json`:
- Line 5092: Update the Russian translation for the key containing
“official_prices” to translate “overrides” as “переопределений” rather than
“исключений,” preserving the rest of the localized message.
---
Nitpick comments:
In `@model/log.go`:
- Around line 61-64: The new idx_logs_user_type_created_id index on Log requires
an explicit large-table rollout plan rather than relying solely on AutoMigrate
at startup. Update the migration or deployment flow to support pre-creating the
index with MySQL-compatible online DDL where available, or ensure it runs during
a documented low-traffic window, including compatibility with MySQL 5.7.
In `@model/savings_log_test.go`:
- Around line 58-82: Add a brief comment above
TestGetSavingsLifetimeLogBatchUsesClickHouseCompositeKeyset explaining that it
uses SQLite while forcing the ClickHouse database type, so it only validates the
composite-keyset predicate shape and not ClickHouse-specific SQL acceptance or
ordering semantics.
In `@service/savings_lifetime_backfill.go`:
- Around line 204-229: The per-user summary currently performs a global
CountPendingSavingsLifetimeEvents query on every request. Update the summary
flow around GetLatestSystemTask and CountPendingSavingsLifetimeEvents to avoid
this repeated table scan, preferably by deriving IsComplete from the global
backfill task status or reusing a short-lived in-memory cache for the global
pending count and status.
- Around line 400-414: Update the conversion flow around
savingsLifetimeAmountMicros to parse and validate the frozen
estimate.SavingsCNYMicros snapshot first; when it is usable, assign amountMicros
and snapshot fields without calling the fallback conversion. Only invoke
savingsLifetimeAmountMicros and return its error when the frozen value is absent
or unusable, while preserving the existing payload snapshot fallback.
In `@web/src/features/dashboard/components/overview/summary-cards.tsx`:
- Around line 190-203: Extract the savings-related logic from SummaryCards into
a dedicated useSavingsSummaryCard hook containing savingsSummaryQuery,
savingsLifetimeQuery, and their derived display strings, plus a
SavingsSummaryCard presentational component for the matching JSX block. Update
SummaryCards to use the new component so it remains focused on the usage/runway
summary while preserving the existing savings data and display behavior.
In `@web/src/features/system-settings/models/savings-lifetime-backfill.tsx`:
- Around line 67-279: The SavingsLifetimeBackfill component combines
query/mutation state management with presentation and exceeds the recommended
size. Extract the query and four mutation workflows into a
useSavingsLifetimeBackfill hook, and move the task progress/summary markup into
a focused subcomponent; keep SavingsLifetimeBackfill responsible for layout and
action controls while preserving the existing behavior and rendered values.
- Around line 85-89: Update all four mutation success handlers in the savings
lifetime backfill flow to invalidate the related savings summary and other
backfill-derived query keys after calling setQueryData with the successful task
response. Use the existing array-form query key constants and React Query
invalidateQueries pattern, while preserving the current cache update behavior.
🪄 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 Plus
Run ID: 3b5b9a47-980c-4cfe-b611-4d3b906e4b11
📒 Files selected for processing (40)
controller/savings.gocontroller/system_task.godocs/user-savings-estimate-design.mddocs/user-savings-trend-design.mdmain.gomodel/log.gomodel/main.gomodel/savings_lifetime.gomodel/savings_lifetime_test.gomodel/savings_log.gomodel/savings_log_test.gomodel/system_task.gomodel/system_task_test.gorouter/api-router.goservice/savings_estimate.goservice/savings_lifetime_backfill.goservice/savings_lifetime_test.goservice/savings_lifetime_worker.goservice/system_task.goservice/text_quota.gosetting/savings_setting/config.goweb/src/features/dashboard/api.tsweb/src/features/dashboard/components/overview/summary-cards.tsxweb/src/features/dashboard/lib/__tests__/savings-time-range.test.tsweb/src/features/dashboard/lib/savings.tsweb/src/features/dashboard/types.tsweb/src/features/system-info/components/system-tasks-panel.tsxweb/src/features/system-settings/api.tsweb/src/features/system-settings/models/savings-estimate-setting.tsweb/src/features/system-settings/models/savings-estimate-settings.tsxweb/src/features/system-settings/models/savings-lifetime-backfill.tsxweb/src/features/system-settings/types.tsweb/src/features/wallet/components/wallet-stats-card.tsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (9)
- service/text_quota.go
- web/src/features/dashboard/types.ts
- web/src/features/dashboard/lib/savings.ts
- web/src/features/system-settings/models/savings-estimate-setting.ts
- setting/savings_setting/config.go
- docs/user-savings-trend-design.md
- web/src/features/system-settings/models/savings-estimate-settings.tsx
- web/src/i18n/locales/fr.json
- web/src/i18n/locales/vi.json
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/i18n/locales/ru.json (1)
1609-1609: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDescribe enabling lifetime tracking, not an amount.
Включить накопленную экономиюreads as enabling an already accumulated savings amount. PreferВключить накопление экономии за всё времяorВключить учёт экономии за всё время.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/i18n/locales/ru.json` at line 1609, Update the Russian translation for the “Enable lifetime savings” key to describe enabling lifetime savings tracking or accumulation, using wording such as “Включить накопление экономии за всё время” or “Включить учёт экономии за всё время” instead of implying an existing accumulated amount.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/user-savings-lifetime-review-remediation.md`:
- Around line 113-127: Update the long-term event amount-selection flow so a
valid non-negative int64 SavingsCNYMicros is used immediately, without requiring
positive quota_per_unit or usd_cny_rate_micros. Only validate and use the frozen
conversion parameters when SavingsCNYMicros is missing or invalid, and preserve
batch termination on fallback conversion failure or overflow; avoid calling
savingsLifetimeAmountMicros before deciding this path.
In `@web/src/i18n/locales/ru.json`:
- Line 2547: Update the Russian translation for the “Lifetime savings counted so
far” key to “Накопленная экономия на данный момент,” matching the adjacent
counted-so-far wording.
- Line 4519: Update the Russian translation for the “System historical data is
being counted” locale key to “Идёт подсчёт исторических данных”, preserving the
counting status rather than describing generic processing.
- Line 861: Update the Russian translation for the “Clear usage and balance” key
to use action-oriented reset terminology, such as “Очистить использование и
баланс,” replacing the current wording that means “transparent usage and
balance.”
- Line 4485: Update the Russian translation for the “supported billing models”
key to use billing terminology rather than “моделей оплаты”; replace the value
with wording such as “поддерживаемых моделей тарификации” while preserving the
key and JSON validity.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 3173: Update both endpoint translations in
web/src/i18n/locales/zh-TW.json: at lines 3173 and 5061, consistently translate
“endpoint” as “端點,” using “一個端點、一枚金鑰” and “相容端點” respectively.
---
Outside diff comments:
In `@web/src/i18n/locales/ru.json`:
- Line 1609: Update the Russian translation for the “Enable lifetime savings”
key to describe enabling lifetime savings tracking or accumulation, using
wording such as “Включить накопление экономии за всё время” or “Включить учёт
экономии за всё время” instead of implying an existing accumulated amount.
🪄 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 Plus
Run ID: d5e73b76-bf1d-428f-98d5-52f4c91a0a41
📒 Files selected for processing (36)
docs/user-savings-estimate-design.mddocs/user-savings-lifetime-review-remediation.mddocs/user-savings-summary-ui-redesign.mddocs/user-savings-trend-design.mdmodel/savings_lifetime.gomodel/savings_lifetime_test.gomodel/savings_log_test.goservice/savings_estimate.goservice/savings_lifetime_backfill.goservice/savings_lifetime_test.gosetting/savings_setting/config.gosetting/savings_setting/config_test.goweb/src/features/dashboard/components/models/savings-trend-chart.tsxweb/src/features/dashboard/components/overview/__tests__/summary-cards-layout.test.tsweb/src/features/dashboard/components/overview/summary-cards-layout.tsweb/src/features/dashboard/components/overview/summary-cards.tsxweb/src/features/dashboard/lib/__tests__/savings-i18n.test.tsweb/src/features/dashboard/lib/__tests__/savings-lifetime.test.tsweb/src/features/dashboard/lib/savings-query-keys.tsweb/src/features/dashboard/lib/savings.tsweb/src/features/system-info/components/system-tasks-panel.tsxweb/src/features/system-info/lib/__tests__/system-task-status.test.tsweb/src/features/system-info/lib/system-task-status.tsweb/src/features/system-settings/api.tsweb/src/features/system-settings/lib/__tests__/savings-lifetime-query.test.tsweb/src/features/system-settings/lib/savings-lifetime-query.tsweb/src/features/system-settings/models/savings-estimate-settings.tsxweb/src/features/system-settings/models/savings-lifetime-backfill.tsxweb/src/features/wallet/components/wallet-stats-card.tsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (17)
- web/src/features/system-settings/api.ts
- setting/savings_setting/config_test.go
- web/src/features/wallet/components/wallet-stats-card.tsx
- model/savings_lifetime_test.go
- web/src/features/dashboard/components/models/savings-trend-chart.tsx
- model/savings_log_test.go
- setting/savings_setting/config.go
- web/src/features/system-info/components/system-tasks-panel.tsx
- service/savings_estimate.go
- docs/user-savings-estimate-design.md
- service/savings_lifetime_backfill.go
- docs/user-savings-trend-design.md
- web/src/features/dashboard/components/overview/summary-cards.tsx
- web/src/features/system-settings/models/savings-estimate-settings.tsx
- web/src/i18n/locales/ja.json
- web/src/i18n/locales/en.json
- web/src/i18n/locales/fr.json
| "One API": "One API", | ||
| "One domain per line": "每行一個域名", | ||
| "One domain per line (only used when domain restriction is enabled)": "每行一個域名 (僅在啟用域名限制時使用)", | ||
| "One endpoint, one key, and a clear view of every request.": "一個介面、一枚金鑰,每次請求都清楚可見。", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Translate “endpoint” consistently as 端點.
web/src/i18n/locales/zh-TW.json#L3173-L3173: use一個端點、一枚金鑰.web/src/i18n/locales/zh-TW.json#L5061-L5061: use相容端點instead of相容介面.
📍 Affects 1 file
web/src/i18n/locales/zh-TW.json#L3173-L3173(this comment)web/src/i18n/locales/zh-TW.json#L5061-L5061
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/i18n/locales/zh-TW.json` at line 3173, Update both endpoint
translations in web/src/i18n/locales/zh-TW.json: at lines 3173 and 5061,
consistently translate “endpoint” as “端點,” using “一個端點、一枚金鑰” and “相容端點”
respectively.
Important
📝 变更描述 / Description
新增面向用户的节省金额估算功能,以同一次请求的实际消耗和管理员确认的官方价格快照为依据,展示累计节省、覆盖率和时间趋势。
max(official_quota - actual_quota, 0)计算并写入消费日志,汇总时保持逐请求非负语义。AI assistance disclosure: 本次实现、移植和验证由 AI 在 RHZHZ 的指导下协助完成;提交者已结合官方最新代码处理计费安全、工具附加费和多语言兼容性。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
📸 运行证明 / Proof of Work
go test ./service -run Savings -count=1go test ./setting/savings_setting ./controller ./router -count=1go test ./model -run Savings -count=1bun run typecheckbun run buildbun test src/features/dashboard/lib/__tests__/savings-i18n.test.ts src/features/dashboard/lib/__tests__/savings-chart.test.ts src/features/dashboard/lib/__tests__/savings-time-range.test.ts(14 passed)oxlint与oxfmt --check通过。bun run i18n:sync:7 种语言均为 0 missing、0 extras、0 untranslated。git diff --check通过。补充:全量
bun run copyright:check仅报告官方基线中未改动的src/features/channels/lib/channel-field-update.ts;本 PR 新增及修改的前端源码版权头均符合当前脚本要求。Summary by CodeRabbit