Skip to content

增强配置表单输入警告提示 - #768

Merged
ding113 merged 4 commits into
ding113:devfrom
tesgth032:fix/provider-settings-warnings
Feb 11, 2026
Merged

增强配置表单输入警告提示#768
ding113 merged 4 commits into
ding113:devfrom
tesgth032:fix/provider-settings-warnings

Conversation

@tesgth032

@tesgth032 tesgth032 commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Add non-blocking inline warnings across three configuration forms (provider settings, system settings, user management) to help admins catch common input mistakes before saving. No business logic changes — warnings are advisory only.

Problem

Several configuration fields (API keys, quota lease parameters, user expiration dates) are prone to accidental mis-paste or mis-entry that can be hard to notice. For example:

  • Pasting an API key with surrounding quotes, authorization headers, or non-ASCII characters
  • Setting quota lease percentages or caps to 0 (effectively disabling the budget)
  • Setting a DB refresh interval too low (DB load) or too high (stale data)
  • Selecting a past expiration date for a user (immediate disable on save)

Solution

Add yellow inline warning hints (non-blocking, save is never prevented) using a new shared InlineWarning UI component. Warnings appear contextually below the relevant input fields.

Changes

Core Changes

  • API Key warnings (src/lib/utils/validation/api-key-warnings.ts): Detects authorization headers, surrounding quotes, non-ASCII chars, whitespace, and uncommon ASCII symbols. Skips whitespace/symbol checks for JSON credentials.
  • Quota/lease warnings (src/lib/utils/validation/quota-lease-warnings.ts): Warns on zero lease percent and zero lease cap.
  • System settings form (src/app/[locale]/settings/config/_components/system-settings-form.tsx): Adds DB refresh interval warnings (too low <=2s, too high >=60s) and lease parameter warnings.
  • Provider form (src/app/[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx): Integrates API key warnings below the key input.
  • User form (src/app/[locale]/dashboard/_components/user/forms/user-form.tsx): Adds past-date warning for expiresAt field, plus a date parsing fix (src/lib/utils/date-input.ts) to use local timezone end-of-day instead of UTC midnight.

Supporting Changes

  • InlineWarning component (src/components/ui/inline-warning.tsx): Shared yellow warning display with AlertTriangle icon.
  • i18n: All 5 languages updated (en, ja, ru, zh-CN, zh-TW) for dashboard, config, and provider key messages.
  • Tests: Unit tests added for api-key-warnings, quota-lease-warnings, and date-input utilities.

Breaking Changes

None. This PR only adds advisory warnings — no existing behavior is modified.

Testing

Automated Tests

  • Unit tests added: api-key-warnings.test.ts, quota-lease-warnings.test.ts, date-input.test.ts

Manual Testing

  1. Go to Provider Settings, paste an API key with leading Bearer or surrounding quotes — yellow warning should appear
  2. Go to System Settings, set DB refresh interval to 1s or 120s — warning should appear
  3. Go to System Settings, set lease percent or lease cap to 0 — warning should appear
  4. Go to User Management, set expiration date to a past date — warning should appear
  5. Verify all warnings disappear when input is corrected
  6. Verify save is never blocked by any warning

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • i18n strings added for all 5 languages
  • No breaking changes

Original description (Chinese)

背景

供应商配置/系统配置里有些字段(如 API Key、配额租约参数等)在误粘贴/误填写时很难被及时发现。本 PR 仅新增"警告提示(warning)",不阻止保存、不改变原有业务逻辑。

变更点

1) 供应商设置:API Key 异常输入提示(仅警告)

  • 当 API Key 命中以下特征时,在输入框下方展示黄色 warning(可同时多条):
    • 看起来像请求头(Bearer/Authorization/x-api-key 等)
    • 首尾被引号包裹("..." / '...')
    • 包含非 ASCII 字符(如中文/全角字符)
    • 包含空白字符(空格/换行/制表等)
    • 包含不常见 ASCII 符号(如 @、; 等)
  • 兼容性:若输入看起来是 JSON 凭据(trim 后以 { 开头),不提示"空白/不常见符号",避免误报。

2) 系统设置:配额租约/刷新频率提示(仅警告)

  • DB 刷新频率:过低(>0 且 <=2s)/过高(>=60s)提示潜在影响(0 不提示)。
  • 各窗口 lease percent 为 0:提示可能导致租约预算始终为 0。
  • lease cap(USD)为 0:提示可能导致每次租约预算为 0(空值/非数不提示)。

3) 用户管理:expiresAt 过去时间提示(仅警告)+ 纯日期解析修复

  • 当 expiresAt(按本地时区"当天结束"计算)<= 当前时间时提示:保存后将立即过期并禁用。
  • 不做"7 天内到期"提示。
  • 新增 YYYY-MM-DD -> 本地当天 23:59:59.999 的解析函数,避免 new Date("YYYY-MM-DD") 的 UTC 解析导致提前过期。

4) UI 组件统一

  • 新增 InlineWarning 组件,统一 warning 的展示样式(黄色小字 + AlertTriangle 图标)。

测试

  • npm run lint
  • npm run typecheck
  • npm run test
  • 由于本地 node_modules 为 symlink,next build(Turbopack)会报错;已使用 npx next build --webpack 验证构建通过,并执行 node scripts/copy-version-to-standalone.cjs

Description enhanced by Claude AI

Greptile Overview

Greptile Summary

This PR adds non-blocking, inline warning hints across configuration forms to help admins catch common input mistakes.

Key changes:

  • Introduces a shared InlineWarning UI component and integrates it into provider key input, system quota/refresh interval inputs, and user expiresAt date input.
  • Adds new validation helpers (detectApiKeyWarnings, quota lease/refresh warning predicates) and date-only utilities (formatDateToLocalYmd, parseYmdToLocalEndOfDay) to avoid UTC date parsing pitfalls.
  • Updates i18n strings across 5 locales and adjusts/extends unit tests.

One functional regression remains in the system settings DB refresh interval field: clearing the numeric input and blurring will still normalize back to 1 due to how the clamp function parses empty string, reintroducing the previously-raised editing UX issue.

Confidence Score: 3/5

  • Mostly safe to merge, but there is a confirmed UX regression that should be fixed first.
  • Core changes are additive (warnings + utilities) and tests were updated, but the quota DB refresh interval input still forces 1 on blur after clearing due to Number("") clamping behavior, undermining the intended improved editing experience.
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx

Important Files Changed

Filename Overview
messages/en/settings/config.json Adds i18n strings for quota lease and DB refresh interval warnings.
src/app/[locale]/dashboard/_components/user/forms/user-form.tsx Formats/parses expiresAt as local YYYY-MM-DD and adds non-blocking past-date inline warning; validates date-only parsing before submit.
src/app/[locale]/settings/config/_components/system-settings-form.tsx Adds inline warnings for quota lease inputs and DB refresh interval; introduces string-based number input but still forces 1 on blur when field is cleared.
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx Adds API key warning detection and renders InlineWarning messages under the key input.
src/components/ui/inline-warning.tsx Adds shared InlineWarning component with icon and amber styling.
src/lib/utils/date-input.ts Adds formatDateToLocalYmd and parseYmdToLocalEndOfDay to avoid UTC date-only parsing issues.
src/lib/utils/validation/api-key-warnings.ts Adds detectApiKeyWarnings to produce advisory warning IDs for suspicious API key inputs.
src/lib/utils/validation/quota-lease-warnings.ts Adds simple predicates for quota lease and DB refresh interval warning thresholds.
tests/unit/lib/provider-endpoints/probe.test.ts Refactors endpoint-circuit-breaker mocking to include newly-used exports in probe implementation.

Sequence Diagram

sequenceDiagram
  participant Admin as Admin UI
  participant ProviderForm as ProviderForm/BasicInfoSection
  participant SystemForm as SystemSettingsForm
  participant UserForm as UserForm
  participant ApiWarn as detectApiKeyWarnings()
  participant QuotaWarn as quota-lease-warnings
  participant DateUtil as date-input utils

  Admin->>ProviderForm: Type/paste API key
  ProviderForm->>ApiWarn: detectApiKeyWarnings(key)
  ApiWarn-->>ProviderForm: warning IDs[]
  ProviderForm-->>Admin: Render InlineWarning(s)

  Admin->>SystemForm: Edit quota DB refresh / lease fields
  SystemForm->>QuotaWarn: shouldWarn*(value)
  QuotaWarn-->>SystemForm: true/false
  SystemForm-->>Admin: Render InlineWarning(s)
  Admin->>SystemForm: Blur refresh interval field
  SystemForm->>SystemForm: clampQuotaDbRefreshIntervalSeconds(str)

  Admin->>UserForm: Pick expiresAt (YYYY-MM-DD)
  UserForm->>DateUtil: parseYmdToLocalEndOfDay(ymd)
  DateUtil-->>UserForm: Date | null
  UserForm-->>Admin: Render past-date InlineWarning if <= now
  Admin->>UserForm: Submit
  UserForm->>DateUtil: parseYmdToLocalEndOfDay(ymd)
  DateUtil-->>UserForm: Date | null
  UserForm-->>Admin: toast.error if invalid date
Loading

@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

新增多语言警告文本、行内警告组件与若干验证/日期工具,并在用户表单、系统设置与提供者表单中集成这些警告显示;同时添加对应单元测试并调整若干测试用例与测试辅助 mock。

Changes

Cohort / File(s) Summary
多语言消息 - dashboard (expiresAt)
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
为用户过期字段 expiresAt 添加 pastWarning 文本,用于选择过去日期时的提示。
多语言消息 - settings/config (quotaLease warnings)
messages/en/settings/config.json, messages/ja/settings/config.json, messages/ru/settings/config.json, messages/zh-CN/settings/config.json, messages/zh-TW/settings/config.json
quotaLease 下新增 warnings 对象,包含 dbRefreshIntervalTooLowdbRefreshIntervalTooHighleasePercentZeroleaseCapZero 四个警告文本。
多语言消息 - providers/form/key (API key warnings)
messages/en/settings/providers/form/key.json, messages/ja/settings/providers/form/key.json, messages/ru/settings/providers/form/key.json, messages/zh-CN/settings/providers/form/key.json, messages/zh-TW/settings/providers/form/key.json
在 API Key 表单消息中新增 warnings 对象,包含五个警告键(looks_like_auth_headerwrapped_in_quotescontains_non_asciicontains_whitespacecontains_uncommon_ascii)。
新 UI 组件
src/components/ui/inline-warning.tsx
新增 InlineWarning 组件(图标 + 文本)用于渲染行内非阻断警告并导出组件类型。
用户表单集成与日期工具
src/app/[locale]/dashboard/_components/user/forms/user-form.tsx, src/lib/utils/date-input.ts, src/lib/utils/date-input.test.ts
引入 formatDateToLocalYmd/parseYmdToLocalEndOfDay 规范 expiresAt 的读写,移除旧内联 toEndOfDay,计算并在表单中用 InlineWarning 显示过期为过去的警告;新增对应单元测试。
系统设置表单集成与配额警告
src/app/[locale]/settings/config/_components/system-settings-form.tsx, src/lib/utils/validation/quota-lease-warnings.ts, src/lib/utils/validation/quota-lease-warnings.test.ts
将 quota-lease 警告谓词集成到系统设置表单,改为字符串输入并在 blur 时 clamp 值,保存时使用夹紧后的值;在相关字段后条件渲染 InlineWarning
提供者表单(API Key)集成与验证工具
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx, src/lib/utils/validation/api-key-warnings.ts, src/lib/utils/validation/api-key-warnings.test.ts
新增 detectApiKeyWarnings 并在提供者密钥输入下方渲染 InlineWarning 列表;包含完整测试覆盖多类警告情形。
测试与辅助改动
tests/unit/dashboard/user-form-expiry-clear-ui.test.tsx, tests/unit/lib/provider-endpoints/probe.test.ts
用本地化日期格式替换测试硬编码日期;重构并集中 provider endpoints 的 circuit-breaker mock helper 以复用。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题为「增强配置表单输入警告提示 [未完成]」,完整准确地反映了PR的主要目标:为三个配置表单添加输入警告提示。内容清晰、具体且与变更集内容完全相关。
Description check ✅ Passed PR描述详细阐述了功能需求、解决方案、核心变更、测试计划和检查清单,与代码变更高度相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @tesgth032, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求通过在各种配置表单中为潜在的错误或问题输入提供即时、非阻塞的警告反馈,从而增强了用户体验。它解决了 API 密钥、系统配额设置和用户过期日期中常见的输入问题,提高了数据完整性并减少了用户错误,同时不阻止表单提交。

Highlights

  • API Key 输入警告: 在供应商设置的 API Key 字段中实现了警告提示,检测常见的输入错误,如请求头、引号、非 ASCII 字符、空白字符和不常见符号。
  • 系统设置配额/租约警告: 为系统设置中的数据库刷新频率(过低/过高)以及租约百分比和租约上限为零的情况添加了警告提示。
  • 用户过期日期警告与解析修复: 在用户管理中引入了对过期日期设置为过去的警告,并修复了日期解析,以将 YYYY-MM-DD 正确解析为本地时区的当天结束时间,防止提前过期。
  • 统一内联警告组件: 创建了一个新的 InlineWarning UI 组件,以标准化应用程序中这些新警告消息的显示样式。
Changelog
  • messages/en/dashboard.json
    • 为用户仪表板的 expiresAt 字段添加了过期时间在过去的警告信息。
  • messages/en/settings/config.json
    • 为系统设置添加了数据库刷新间隔过低/过高、租约百分比为零和租约上限为零的警告信息。
  • messages/en/settings/providers/form/key.json
    • 为供应商表单的 API Key 字段添加了多种输入异常警告信息。
  • messages/ja/dashboard.json
    • 为用户仪表板的 expiresAt 字段添加了过期时间在过去的警告信息。
  • messages/ja/settings/config.json
    • 为系统设置添加了数据库刷新间隔过低/过高、租约百分比为零和租约上限为零的警告信息。
  • messages/ja/settings/providers/form/key.json
    • 为供应商表单的 API Key 字段添加了多种输入异常警告信息。
  • messages/ru/dashboard.json
    • 为用户仪表板的 expiresAt 字段添加了过期时间在过去的警告信息。
  • messages/ru/settings/config.json
    • 为系统设置添加了数据库刷新间隔过低/过高、租约百分比为零和租约上限为零的警告信息。
  • messages/ru/settings/providers/form/key.json
    • 为供应商表单的 API Key 字段添加了多种输入异常警告信息。
  • messages/zh-CN/dashboard.json
    • 为用户仪表板的 expiresAt 字段添加了过期时间在过去的警告信息。
  • messages/zh-CN/settings/config.json
    • 为系统设置添加了数据库刷新间隔过低/过高、租约百分比为零和租约上限为零的警告信息。
  • messages/zh-CN/settings/providers/form/key.json
    • 为供应商表单的 API Key 字段添加了多种输入异常警告信息。
  • messages/zh-TW/dashboard.json
    • 為使用者儀表板的 expiresAt 欄位添加了過期時間在過去的警告訊息。
  • messages/zh-TW/settings/config.json
    • 為系統設定添加了資料庫刷新間隔過低/過高、租約百分比為零和租約上限為零的警告訊息。
  • messages/zh-TW/settings/providers/form/key.json
    • 為供應商表單的 API Key 欄位添加了多種輸入異常警告訊息。
  • src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
    • 更新了用户表单以使用新的日期解析工具函数和内联警告组件,并移除了旧的日期处理逻辑。
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
    • 更新了系统设置表单,引入了内联警告组件和配额租约相关的验证工具函数,以显示警告。
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx
    • 更新了提供商基本信息部分,引入了内联警告组件和 API Key 警告检测工具函数,以显示警告。
  • src/components/ui/inline-warning.tsx
    • 添加了一个新的 InlineWarning React 组件,用于统一显示警告消息。
  • src/lib/utils/date-input.test.ts
    • 添加了 parseYmdToLocalEndOfDay 工具函数的测试。
  • src/lib/utils/date-input.ts
    • 添加了 parseYmdToLocalEndOfDay 工具函数,用于将 YYYY-MM-DD 格式的日期字符串解析为本地时区的当天结束时间。
  • src/lib/utils/validation/api-key-warnings.test.ts
    • 添加了 detectApiKeyWarnings 工具函数的测试。
  • src/lib/utils/validation/api-key-warnings.ts
    • 添加了 detectApiKeyWarnings 工具函数,用于检测 API Key 输入中的常见异常模式。
  • src/lib/utils/validation/quota-lease-warnings.test.ts
    • 添加了配额租约警告相关工具函数的测试。
  • src/lib/utils/validation/quota-lease-warnings.ts
    • 添加了多个工具函数,用于判断配额租约设置是否需要显示警告。
Activity
  • 目前没有发现与此拉取请求相关的评论、审查或特定进度更新。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 为多个配置表单增加了输入警告提示,极大地提升了用户体验,有助于避免常见的输入错误。整体实现非常出色,新增的验证工具函数设计良好且经过了充分测试。新的可复用 InlineWarning 组件也是一个很好的补充。代码在 UI 组件、工具函数、测试和多语言文件中的组织结构清晰。我只发现一处可以改进代码可维护性的地方,具体请看我的评论。

Comment on lines +676 to +678
{shouldWarnQuotaLeasePercentZero(quotaLeasePercent5h) && (
<InlineWarning>{t("quotaLease.warnings.leasePercentZero")}</InlineWarning>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这部分警告逻辑在 leasePercentDailyleasePercentWeeklyleasePercentMonthly 字段中重复出现。为了提高代码的可维护性并减少重复,可以考虑将整个输入字段(包括 Label, Input, pInlineWarning)封装成一个可复用的组件。

例如,可以创建一个 LeasePercentInput 组件,它接收 valueonChangelabelKeydescriptionKey 等 props,然后在内部处理显示和警告逻辑。这样可以使父组件更简洁,也便于未来统一修改。

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

25 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 11, 2026

Copy link
Copy Markdown
Additional Comments (2)

src/app/[locale]/settings/config/_components/system-settings-form.tsx
Invalid number input yields NaN

quotaDbRefreshIntervalSeconds is set via Number(e.target.value). When the user clears the numeric input, e.target.value becomes "", so this state becomes 0 (or NaN depending on browser/value), which then gets submitted in saveSystemSettings({ quotaDbRefreshIntervalSeconds }). This can bypass the intended “0 means don’t warn” behavior and may persist an unintended value to the backend. Consider guarding the state update / submit so empty input doesn’t turn into a numeric value.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/config/_components/system-settings-form.tsx
Line: 625:632

Comment:
**Invalid number input yields NaN**

`quotaDbRefreshIntervalSeconds` is set via `Number(e.target.value)`. When the user clears the numeric input, `e.target.value` becomes `""`, so this state becomes `0` (or `NaN` depending on browser/value), which then gets submitted in `saveSystemSettings({ quotaDbRefreshIntervalSeconds })`. This can bypass the intended “0 means don’t warn” behavior and may persist an unintended value to the backend. Consider guarding the state update / submit so empty input doesn’t turn into a numeric value.


How can I resolve this? If you propose a fix, please make it concise.

src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
UTC date string mismatch

The default value uses user.expiresAt.toISOString().split("T")[0], which is UTC-based. For users in negative timezones (or when expiresAt isn’t midnight UTC), this can show the wrong day in the date picker, and combined with the new local end-of-day parsing can shift the effective expiration by a day. This should format the initial expiresAt in local date (or the app’s chosen timezone) rather than UTC ISO.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
Line: 102:105

Comment:
**UTC date string mismatch**

The default value uses `user.expiresAt.toISOString().split("T")[0]`, which is UTC-based. For users in negative timezones (or when `expiresAt` isn’t midnight UTC), this can show the wrong day in the date picker, and combined with the new local end-of-day parsing can shift the effective expiration by a day. This should format the initial `expiresAt` in local date (or the app’s chosen timezone) rather than UTC ISO.


How can I resolve this? If you propose a fix, please make it concise.

@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label Feb 11, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR. The implementation adds client-side warning hints for provider settings, system quota/lease configuration, and user expiration dates. The code is well-structured with clean separation between validation logic, UI components, and i18n. The parseYmdToLocalEndOfDay refactoring correctly fixes the UTC date parsing bug described in the PR.

PR Size: M

  • Lines changed: 432 (406 additions, 26 deletions)
  • Files changed: 25

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Notes

  • All 5 i18n languages (en, ja, ru, zh-CN, zh-TW) have consistent key additions across dashboard.json, settings/config.json, and settings/providers/form/key.json.
  • The parseYmdToLocalEndOfDay function correctly avoids the new Date("YYYY-MM-DD") UTC parsing pitfall by constructing the date from local components. It returns null for invalid input (safer than the old toEndOfDay which would produce an invalid Date object).
  • Validation functions (detectApiKeyWarnings, shouldWarnQuota*) are pure, well-tested, and correctly handle edge cases (JSON credentials, NaN, empty strings).
  • The InlineWarning component correctly uses aria-hidden="true" on the decorative icon while keeping the text accessible.

Automated review by Claude AI

@tesgth032

Copy link
Copy Markdown
Contributor Author

已根据审阅意见做了补充修正:

  • quotaDbRefreshIntervalSeconds:输入框清空/非法输入时不再落到 0/NaN;onChange 里对空值做了兜底并 clamp 到 [1,300],避免提交无效值。
  • expiresAt:编辑态默认值不再使用 UTC 的 toISOString().split("T")[0],改为按本地日期格式化为 YYYY-MM-DD,与 DatePicker 的本地解析逻辑一致;同步更新了对应单测。
  • 修复 provider-endpoints/probe 单测:补齐 endpoint-circuit-breaker mock 的必要导出(getEndpointCircuitStateSync / resetEndpointCircuit),与当前实现对齐。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/user/forms/user-form.tsx:
- Line 126: parseYmdToLocalEndOfDay can return null for invalid date strings,
which currently causes expiresAt to be silently set to null and sent to the
server; locate the two usages of parseYmdToLocalEndOfDay (the assignment that
sets expiresAt from data.expiresAt and the similar occurrence around line 144)
and change the submit flow to validate the parser result: if
parseYmdToLocalEndOfDay(data.expiresAt) === null, block submission and surface a
user-facing validation error (or retain the original value instead of replacing
it with null), ensuring you update the form validation/state handling to prevent
silently clearing the expiry when parsing fails.
🧹 Nitpick comments (4)
tests/unit/lib/provider-endpoints/probe.test.ts (1)

26-33: 测试辅助函数抽取合理,DRY 改进良好。

将重复的 circuit breaker mock 逻辑统一到 createCircuitBreakerMock 中,减少了大量样板代码,各测试用例的 override 用法也清晰一致。

一个小建议:overrides 的类型 Partial<Record<string, unknown>> 较为宽松,可以考虑用更具体的类型来获得更好的自动补全和类型安全。

可选的类型收窄
-function createCircuitBreakerMock(overrides: Partial<Record<string, unknown>> = {}) {
+interface CircuitBreakerMock {
+  getEndpointCircuitStateSync: ReturnType<typeof vi.fn>;
+  resetEndpointCircuit: ReturnType<typeof vi.fn>;
+  recordEndpointFailure: ReturnType<typeof vi.fn>;
+}
+
+function createCircuitBreakerMock(overrides: Partial<CircuitBreakerMock> = {}) {
   return {
     getEndpointCircuitStateSync: vi.fn(() => "closed"),
     resetEndpointCircuit: vi.fn(async () => {}),
     recordEndpointFailure: vi.fn(async () => {}),
     ...overrides,
   };
 }
tests/unit/dashboard/user-form-expiry-clear-ui.test.tsx (2)

75-75: 测试辅助函数中的中文硬编码错误信息。

未找到按钮: ${text} 作为测试内部的 throw 信息不属于用户可见字符串,此处无需国际化,但考虑到编码规范中要求所有用户可见字符串使用 i18n,建议统一使用英文以保持测试代码的一致性和可读性(尤其在 CI 日志中)。

建议修改
-    throw new Error(`未找到按钮: ${text}`);
+    throw new Error(`Button not found: ${text}`);

80-85: 测试描述使用了中文字符串。

describetest 的标题为中文。虽然测试描述不是"用户可见字符串"(不需要 i18n),但在多语言团队协作中英文描述更易于 CI 输出阅读和问题定位。这属于风格偏好,仅作为可选建议。

src/app/[locale]/settings/config/_components/system-settings-form.tsx (1)

631-641: 即时 clamp 可能导致输入体验不佳。

当用户清空输入框准备重新输入时,值会立即跳回 1,导致难以正常编辑。例如用户想输入 15:全选删除 → 变为 1,再按 5 → 变为 15(此时前面多了一个 1,实际结果可能是 15115,取决于光标位置)。

建议参考 quotaLeaseCapUsd 的模式,将中间态存储为字符串,仅在失焦或提交时做 clamp/解析。

可选:改用字符串缓存 + onBlur clamp
-  const [quotaDbRefreshIntervalSeconds, setQuotaDbRefreshIntervalSeconds] = useState(
-    initialSettings.quotaDbRefreshIntervalSeconds ?? 10
+  const [quotaDbRefreshIntervalStr, setQuotaDbRefreshIntervalStr] = useState(
+    String(initialSettings.quotaDbRefreshIntervalSeconds ?? 10)
   );
+  const quotaDbRefreshIntervalSeconds = (() => {
+    const n = Number(quotaDbRefreshIntervalStr);
+    return Number.isFinite(n) ? n : 10;
+  })();

onChange 中直接存字符串,在 onBlur 中做 clamp:

   onChange={(e) => {
-    const raw = e.target.value;
-    if (!raw) {
-      setQuotaDbRefreshIntervalSeconds(1);
-      return;
-    }
-    const parsed = Number(raw);
-    if (!Number.isFinite(parsed)) return;
-    const clamped = Math.min(300, Math.max(1, parsed));
-    setQuotaDbRefreshIntervalSeconds(clamped);
+    setQuotaDbRefreshIntervalStr(e.target.value);
   }}
+  onBlur={() => {
+    const parsed = Number(quotaDbRefreshIntervalStr);
+    if (!Number.isFinite(parsed) || parsed < 1) {
+      setQuotaDbRefreshIntervalStr("1");
+    } else if (parsed > 300) {
+      setQuotaDbRefreshIntervalStr("300");
+    }
+  }}

Comment thread src/app/[locale]/dashboard/_components/user/forms/user-form.tsx Outdated

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

27 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +631 to +641
onChange={(e) => {
const raw = e.target.value;
if (!raw) {
setQuotaDbRefreshIntervalSeconds(1);
return;
}
const parsed = Number(raw);
if (!Number.isFinite(parsed)) return;
const clamped = Math.min(300, Math.max(1, parsed));
setQuotaDbRefreshIntervalSeconds(clamped);
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing input forces 1

In the quota-db-refresh-interval controlled <Input type="number">, onChange sets the state to 1 whenever the user clears the field (raw === ""). This makes it very hard to edit multi-digit values by backspacing (the field snaps to 1 immediately), and it prevents temporarily emptying the input while typing. Consider allowing an empty state (e.g., store "" separately) or keeping the previous value when raw is empty instead of forcing 1.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/config/_components/system-settings-form.tsx
Line: 631:641

Comment:
**Clearing input forces `1`**

In the `quota-db-refresh-interval` controlled `<Input type="number">`, `onChange` sets the state to `1` whenever the user clears the field (`raw === ""`). This makes it very hard to edit multi-digit values by backspacing (the field snaps to `1` immediately), and it prevents temporarily emptying the input while typing. Consider allowing an empty state (e.g., store `""` separately) or keeping the previous value when `raw` is empty instead of forcing `1`.

How can I resolve this? If you propose a fix, please make it concise.

@tesgth032

Copy link
Copy Markdown
Contributor Author

已按 CodeRabbit 的关键建议做了修正(均为最小改动,且仅影响本 PR 相关代码):

  1. expiresAt 提交解析兜底
  • UserForm 提交前先解析一次 expiresAt:若 data.expiresAt 非空但 parseYmdToLocalEndOfDay() 返回 null,则直接阻止提交并 toast 提示(使用 i18n 的 errors.INVALID_FORMAT + errors.EXPIRES_AT_FIELD),避免“解析失败时静默清空 expiresAt 并发送到服务端”。
  • 文件:src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
  1. quotaDbRefreshIntervalSeconds 输入体验
  • 将中间态改为字符串存储,onBlur 时再 clamp 到 [1, 300];提交时也会再兜底 clamp,确保发送到后端始终为合法数值。
  • 文件:src/app/[locale]/settings/config/_components/system-settings-form.tsx
  1. 单测 throw 信息
  • 将测试辅助函数的错误信息改为英文(非用户可见字符串)。
  • 文件:tests/unit/dashboard/user-form-expiry-clear-ui.test.tsx

对应提交:65648ac8

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/[locale]/settings/config/_components/system-settings-form.tsx (1)

640-657: ⚠️ Potential issue | 🟡 Minor

刷新间隔输入允许非整数值,后端验证会拒绝。

<Input>type="number" 没有设置 step={1},用户可以输入 1.5 等小数值。onBlur 的 clamp 逻辑(第 647-652 行)仅限制范围 [1, 300],但不执行取整。当提交表单时,后端验证(src/lib/validation/schemas.ts 中的 .int())会拒绝非整数值,返回错误 "DB refresh interval must be an integer"。

建议添加 step={1} 属性到 <Input> 组件,或在 clamp 时使用 Math.round() 转换为整数。

🧹 Nitpick comments (1)
src/app/[locale]/settings/config/_components/system-settings-form.tsx (1)

148-153: onBlurhandleSubmit 中的 clamp 逻辑重复。

Lines 148-153 的保存时 clamp 和 Lines 647-654 的 onBlur clamp 逻辑几乎完全一致,建议提取为共享的辅助函数(如 clampRefreshInterval),减少维护时两处不一致的风险。

提取共享 clamp 函数
+const clampRefreshInterval = (raw: string): number => {
+  const parsed = Number(raw);
+  if (!Number.isFinite(parsed) || parsed < 1) return 1;
+  if (parsed > 300) return 300;
+  return parsed;
+};
+
 const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
   event.preventDefault();
   // ...
-  const quotaDbRefreshIntervalSecondsToSave = (() => {
-    const parsed = Number(quotaDbRefreshIntervalSecondsStr);
-    if (!Number.isFinite(parsed) || parsed < 1) return 1;
-    if (parsed > 300) return 300;
-    return parsed;
-  })();
+  const quotaDbRefreshIntervalSecondsToSave = clampRefreshInterval(quotaDbRefreshIntervalSecondsStr);

onBlur 同样可复用:

 onBlur={() => {
-  const parsed = Number(quotaDbRefreshIntervalSecondsStr);
-  if (!Number.isFinite(parsed) || parsed < 1) {
-    setQuotaDbRefreshIntervalSecondsStr("1");
-  } else if (parsed > 300) {
-    setQuotaDbRefreshIntervalSecondsStr("300");
-  }
+  setQuotaDbRefreshIntervalSecondsStr(
+    String(clampRefreshInterval(quotaDbRefreshIntervalSecondsStr))
+  );
 }}

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

27 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 11, 2026

Copy link
Copy Markdown
Additional Comments (1)

src/lib/provider-endpoints/probe.ts
Ignores configured HTTP probe method

probeEndpointUrl always probes with HEAD then falls back to GET (src/lib/provider-endpoints/probe.ts:211-216), but resolveProbeMethod() is intended to let ENDPOINT_PROBE_METHOD choose between HEAD and GET. With ENDPOINT_PROBE_METHOD=GET, the function still sends HEAD requests, which is a behavior bug and can break probing against endpoints that disallow HEAD.

  // HTTP-based probing: try configured method first, optionally fallback
  if (method === "GET") {
    return tryProbe(url, "GET", timeoutMs);
  }

  const head = await tryProbe(url, "HEAD", timeoutMs);
  if (head.statusCode === null) {
    return tryProbe(url, "GET", timeoutMs);
  }
  return head;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/provider-endpoints/probe.ts
Line: 211:216

Comment:
**Ignores configured HTTP probe method**

`probeEndpointUrl` always probes with `HEAD` then falls back to `GET` (`src/lib/provider-endpoints/probe.ts:211-216`), but `resolveProbeMethod()` is intended to let `ENDPOINT_PROBE_METHOD` choose between `HEAD` and `GET`. With `ENDPOINT_PROBE_METHOD=GET`, the function still sends `HEAD` requests, which is a behavior bug and can break probing against endpoints that disallow `HEAD`.

```suggestion
  // HTTP-based probing: try configured method first, optionally fallback
  if (method === "GET") {
    return tryProbe(url, "GET", timeoutMs);
  }

  const head = await tryProbe(url, "HEAD", timeoutMs);
  if (head.statusCode === null) {
    return tryProbe(url, "GET", timeoutMs);
  }
  return head;
```

How can I resolve this? If you propose a fix, please make it concise.

@tesgth032

Copy link
Copy Markdown
Contributor Author

补充跟进 CodeRabbit 最新评论(均为最小改动,限定在本 PR 范围内):

  • quotaDbRefreshIntervalSeconds:已增加 step={1},并在 onBlur 与提交前统一复用 clampQuotaDbRefreshIntervalSeconds()(取整 + clamp 到 [1,300]),避免输入小数导致后端 .int() 校验失败或两处 clamp 逻辑不一致。

对应提交:7a3e278d

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

27 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +652 to +655
onBlur={() => {
setQuotaDbRefreshIntervalSecondsStr(
String(clampQuotaDbRefreshIntervalSeconds(quotaDbRefreshIntervalSecondsStr))
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty blur clamps to 1

onBlur always normalizes the input via clampQuotaDbRefreshIntervalSeconds(quotaDbRefreshIntervalSecondsStr), but clampQuotaDbRefreshIntervalSeconds uses Number(raw). In JS, Number("") === 0, so if the user clears the field and blurs, the value will be forced to 1 again. This recreates the prior UX bug where clearing the refresh interval snaps to 1 (it will happen on blur now).

A simple fix is to treat empty/whitespace as “leave empty” (don’t clamp) or bot keep the previous value when quotaDbRefreshIntervalSecondsStr.trim() is empty.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/config/_components/system-settings-form.tsx
Line: 652:655

Comment:
**Empty blur clamps to 1**

`onBlur` always normalizes the input via `clampQuotaDbRefreshIntervalSeconds(quotaDbRefreshIntervalSecondsStr)`, but `clampQuotaDbRefreshIntervalSeconds` uses `Number(raw)`. In JS, `Number("") === 0`, so if the user clears the field and blurs, the value will be forced to `1` again. This recreates the prior UX bug where clearing the refresh interval snaps to `1` (it will happen on blur now).

A simple fix is to treat empty/whitespace as “leave empty” (don’t clamp) or bot keep the previous value when `quotaDbRefreshIntervalSecondsStr.trim()` is empty.

How can I resolve this? If you propose a fix, please make it concise.

@tesgth032

Copy link
Copy Markdown
Contributor Author

看到了 Greptile 关于 probeEndpointUrl 忽略 ENDPOINT_PROBE_METHOD 的提示,这个确实像是一个行为层面的 bug(对禁用 HEAD 的上游可能有影响)。

不过该问题不在本 PR 的新增“表单 warning/校验提示”范围内,也不是本 PR 新增的代码。为避免扩大 PR 范围,这里先不混在一起改;我会另起一个 follow-up PR 专门修复 probe 的 method 选择逻辑并补充对应测试。

@tesgth032 tesgth032 changed the title 增强配置表单输入警告提示 [未完成] 增强配置表单输入警告提示 Feb 11, 2026
@ding113
ding113 merged commit cdb21d9 into ding113:dev Feb 11, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Feb 11, 2026
ding113 added a commit that referenced this pull request Feb 12, 2026
* fix(circuit-breaker): key errors should not trip endpoint circuit breaker

Remove 3 recordEndpointFailure calls from response-handler streaming
error paths (fake-200, non-200 HTTP, stream abort). These are key-level
errors where the endpoint itself responded successfully. Only
forwarder-level failures (timeout, network error) and probe failures
should penalize the endpoint circuit breaker.

Previously, a single bad API key could trip the endpoint breaker
(threshold=3, open=5min), making ALL keys on that endpoint unavailable.

* chore: format code (dev-3d584e5)

* Merge pull request #767 from ding113/fix/provider-clone-deep-copy

fix: 修复供应商克隆时因浅拷贝引用共享导致源供应商数据被意外污染的问题

* 增强配置表单输入警告提示 (#768)

* feat: 增强配置表单输入警告提示

* fix: 修复 expiresAt 显示与配额刷新输入边界

* fix: 修复 expiresAt 解析兜底并改善刷新间隔输入体验

* fix: 刷新间隔输入取整并复用 clamp

---------

Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>

* feat(circuit-breaker): endpoint CB default-off + 524 decision chain audit (#773)

* feat(circuit-breaker): endpoint circuit breaker default-off + 524 decision chain audit

- Add ENABLE_ENDPOINT_CIRCUIT_BREAKER env var (default: false) to gate endpoint-level circuit breaker
- Gate isEndpointCircuitOpen, recordEndpointFailure, recordEndpointSuccess, triggerEndpointCircuitBreakerAlert behind env switch
- Add initEndpointCircuitBreaker() startup cleanup: clear stale Redis keys when feature disabled
- Gate endpoint filtering in endpoint-selector (getPreferredProviderEndpoints, getEndpointFilterStats)
- Fix 524 vendor-type timeout missing from decision chain: add chain entry with reason=vendor_type_all_timeout in forwarder
- Add vendor_type_all_timeout to ProviderChainItem reason union type (both backend session.ts and frontend message.ts)
- Add timeline rendering for vendor_type_all_timeout in provider-chain-formatter
- Replace hardcoded Chinese strings in provider-selector circuit_open details with i18n keys
- Add i18n translations for vendor_type_all_timeout and filterDetails (5 languages: zh-CN, zh-TW, en, ja, ru)
- Enhance LogicTraceTab to render filterDetails via i18n lookup with fallback
- Add endpoint_pool_exhausted and vendor_type_all_timeout to provider-chain-popover isActualRequest/getItemStatus
- Add comprehensive unit tests for all changes (endpoint-circuit-breaker, endpoint-selector, provider-chain-formatter)

* fix(i18n): fix Russian grammar errors and rate_limited translations

- Fix Russian: "конечная точкаов" -> "конечных точек" (11 occurrences)
- Fix Russian: "Ограничение стоимости" -> "Ограничение скорости" (rate_limited)
- Fix zh-CN: "费用限制" -> "速率限制" (filterDetails.rate_limited)
- Fix zh-TW: "費用限制" -> "速率限制" (filterDetails.rate_limited)
- Add initEndpointCircuitBreaker() to dev environment in instrumentation.ts

* fix(circuit-breaker): vendor type CB respects ENABLE_ENDPOINT_CIRCUIT_BREAKER

Make vendor type circuit breaker controlled by the same
ENABLE_ENDPOINT_CIRCUIT_BREAKER switch as endpoint circuit breaker.
When disabled (default), vendor type CB will never trip or block
providers, resolving user confusion about "vendor type temporary
circuit breaker" skip reasons in decision chain.

Changes:
- Add ENABLE_ENDPOINT_CIRCUIT_BREAKER check in isVendorTypeCircuitOpen()
- Add switch check in recordVendorTypeAllEndpointsTimeout()
- Add tests for switch on/off behavior

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* 修复 Key 并发限制继承用户并发上限 (#772)

* fix: Key 并发上限默认继承用户限制

- RateLimitGuard:Key limitConcurrentSessions=0 时回退到 User limitConcurrentSessions\n- Key 配额/使用量接口:并发上限按有效值展示\n- 单测覆盖并发继承逻辑;补齐 probe 测试的 endpoint-circuit-breaker mock 导出\n- 同步更新 biome.json schema 版本以匹配当前 Biome CLI

* docs: 补齐并发上限解析工具注释

* refactor: 合并 Key 限额查询并补充并发单测

- getKeyQuotaUsage/getKeyLimitUsage:通过 leftJoin 一次取回 User 并发上限,避免额外查询\n- 新增 resolveKeyConcurrentSessionLimit 单测,覆盖关键分支\n- 修复 vacuum-filter bench 中的 Biome 报错

* fix: my-usage 并发上限继承用户限制

- getMyQuota:Key 并发为 0/null 时回退到 User 并发上限,保持与 Guard/Key 配额一致\n- 新增单测覆盖 Key->User 并发继承

* test: 补齐 my-usage 并发继承场景

- MyUsageQuota.keyLimitConcurrentSessions 收敛为 number(0 表示无限制)\n- OpenAPI 响应 schema 同步为非 nullable\n- my-usage 并发继承测试补充 Key>0 与 User=0 场景

---------

Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: hank9999 <hank9999@qq.com>
Co-authored-by: tesgth032 <tesgth032@hotmail.com>
Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Feb 12, 2026
5 tasks
@tesgth032
tesgth032 deleted the fix/provider-settings-warnings branch June 7, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:provider area:UI enhancement New feature or request size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants