Skip to content

feat(providers): expose vendor endpoint pools in settings UI#719

Merged
ding113 merged 8 commits intodevfrom
feat/vendor-endpoint-ui
Feb 7, 2026
Merged

feat(providers): expose vendor endpoint pools in settings UI#719
ding113 merged 8 commits intodevfrom
feat/vendor-endpoint-ui

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Feb 4, 2026

Summary

Expose vendor endpoint pools in the Providers settings UI with visual status indicators, hover tooltips, and a reusable CRUD table component. This PR builds on the vendor-endpoint architecture introduced in #608 by making endpoint management accessible to users through the settings interface.

Problem

After #608 introduced the vendor-endpoint architecture with health probing and circuit breakers, the UI lacked visibility into:

  • Endpoint pool status and health for each vendor
  • Individual endpoint probe/circuit breaker states
  • Easy access to endpoint management (CRUD operations)

Related Issues:

Solution

Add comprehensive UI components to expose endpoint pool information:

  • Status mapping helper (endpoint-status.ts) - Consistent probe/circuit state rendering
  • Endpoint pool hover (provider-endpoint-hover.tsx) - Shows endpoint count + per-endpoint status in provider list rows
  • Provider list enhancement (provider-rich-list-item.tsx) - Display vendor name + endpoint summary when vendor is assigned
  • Reusable CRUD table (provider-endpoints-table.tsx) - Extracted from vendor view for reusability
  • Full i18n support - All 5 languages (zh-CN, zh-TW, en, ja, ru)

Changes

Core Changes

  • src/app/[locale]/settings/providers/_components/endpoint-status.ts (+107) - Status mapping helper
  • src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx (+154) - Endpoint pool hover tooltip
  • src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx (+704) - Reusable endpoint CRUD table
  • src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx (+24/-3) - Show vendor + endpoint summary

Supporting Changes

  • src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (+9/-519) - Refactored to use extracted table component
  • messages/*/settings/providers/strings.json (+60) - i18n strings for endpoint status UI

Testing

  • tests/unit/settings/providers/endpoint-status.test.ts (+101) - Status mapping tests
  • tests/unit/settings/providers/provider-endpoint-hover.test.tsx (+244) - Hover component tests
  • tests/unit/settings/providers/provider-endpoints-table.test.tsx (+567) - CRUD table tests
  • tests/unit/settings/providers/provider-rich-list-item-endpoints.test.tsx (+239) - List item integration tests

Testing

Automated Tests

  • Unit tests added for all new components (1,151 lines of test code)
  • Tests pass locally (bun run test)
  • Type checking passes (bun run typecheck)
  • Linting passes (bun run lint)
  • Production build succeeds (bun run build)

Manual Testing

  1. Navigate to Settings > Providers
  2. Switch to "Vendor" view mode (if not already)
  3. Observe vendor names and endpoint counts in provider list rows
  4. Hover over endpoint count badge to see per-endpoint status
  5. Expand a vendor to see the endpoint CRUD table
  6. Verify endpoint status indicators (healthy/unhealthy/circuit open)

Notes

  • Future work: ProviderForm endpoint-pool integration is not included in this PR yet
  • Code quality: Greptile identified minor i18n placeholder mismatch in hover component (non-blocking)
  • Net reduction: Despite adding features, code decreased by 527 lines due to refactoring

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally (4 new test files, 1,151 lines)
  • i18n strings added for all 5 languages
  • Documentation updated (inline comments)

Description enhanced by Claude AI

Greptile Overview

Greptile Summary

  • Adds new provider settings UI for vendor endpoint pools: status mapping helper, hover tooltip in provider list rows, and extracted reusable endpoints CRUD table.
  • Refactors vendor view to reuse the new table component and wires ProviderForm to resolve endpoint pools from website URL/vendor metadata.
  • Extends i18n strings across all locales and updates the zh-TW translation quality test to handle nested objects.
  • Adds comprehensive unit tests for status mapping, hover tooltip behavior, CRUD table, and ProviderForm endpoint-pool integration.

Confidence Score: 3/5

  • This PR is close to merge-ready, but has a couple of correctness/maintenance concerns to address first.
  • Most changes are additive and well-tested, but there is at least one likely-unintended config regression (Biome schema downgrade) and broad React Query cache invalidation patterns that will cause unnecessary refetching across the settings UI as usage grows.
  • biome.json; src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx

Important Files Changed

Filename Overview
biome.json Downgrades Biome $schema URL from 2.3.11 to 2.3.10; likely unintended config regression.
messages/en/settings/providers/form/errors.json Adds required-field error strings (key/name/url) for provider form validation.
messages/en/settings/providers/form/sections.json Adds Endpoint Pool section title/description strings.
messages/en/settings/providers/strings.json Adds endpointStatus nested strings and sortOrder label; updates tooltip/view-details text to include {count}.
messages/ja/settings/providers/form/errors.json Adds required-field error strings (key/name/url) in Japanese.
messages/ja/settings/providers/form/sections.json Adds Endpoint Pool section title/description strings in Japanese.
messages/ja/settings/providers/strings.json Adds endpointStatus nested strings and sortOrder label in Japanese.
messages/ru/settings/providers/form/errors.json Adds required-field error strings (key/name/url) in Russian.
messages/ru/settings/providers/form/sections.json Adds Endpoint Pool section title/description strings in Russian.
messages/ru/settings/providers/strings.json Adds endpointStatus nested strings and sortOrder label in Russian.
messages/zh-CN/settings/providers/form/errors.json Adds required-field error strings (key/name/url) in zh-CN.
messages/zh-CN/settings/providers/form/sections.json Adds Endpoint Pool section title/description strings in zh-CN.
messages/zh-CN/settings/providers/strings.json Adds endpointStatus nested strings and sortOrder label in zh-CN.
messages/zh-TW/settings/providers/form/errors.json Adds required-field error strings (key/name/url) in zh-TW.
messages/zh-TW/settings/providers/form/sections.json Adds Endpoint Pool section title/description strings in zh-TW.
messages/zh-TW/settings/providers/strings.json Adds endpointStatus nested strings and sortOrder label in zh-TW; aligns with zh-TW quality test recursion.
src/app/[locale]/settings/providers/_components/endpoint-status.ts Adds getEndpointStatusModel helper to map probe/circuit state into UI label/icon/color model.
src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx Adds tooltip hover UI that fetches vendor endpoints and per-endpoint circuit state; includes latency and status text.
src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx Extracts reusable endpoints CRUD table/section for vendor view and provider form; uses React Query and server actions. Noted broad invalidateQueries calls.
src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx Enhances provider list rows to show vendor display name and endpoint hover badge when providerVendorId is set.
src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx Refactors vendor view to use extracted ProviderEndpointsSection instead of inline CRUD table implementation.
src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx Integrates endpoint pool into ProviderForm by resolving vendor from website URL and using endpoints as preferred URL; invalidates vendors/endpoints caches on create.
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx Reorders Website URL earlier and conditionally renders endpoint pool section; hides legacy URL input when endpoint pool resolves with enabled endpoints.
tests/unit/i18n/zh-tw-providers-strings-quality.test.ts Updates zh-TW strings quality test to recursively validate nested translation objects (required after adding endpointStatus).
tests/unit/settings/providers/endpoint-status.test.ts Adds unit tests covering getEndpointStatusModel mapping for probe and circuit states.
tests/unit/settings/providers/provider-endpoint-hover.test.tsx Adds unit tests for ProviderEndpointHover tooltip behavior and ordering/filtering logic.
tests/unit/settings/providers/provider-endpoints-table.test.tsx Adds extensive tests for endpoints CRUD table behaviors (rendering, mutations, query keys, readOnly/type filtering).
tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx Adds ProviderForm tests for endpoint-pool resolution from website URL and URL input hiding/submit requirements.
tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx Adjusts ProviderForm total limit tests for new QueryClientProvider and updated ProviderDisplay shape.
tests/unit/settings/providers/provider-rich-list-item-endpoints.test.tsx Adds integration tests for provider rich list item showing vendor + endpoint hover when vendor is assigned.

Sequence Diagram

sequenceDiagram
  participant UI as Settings UI
  participant Hover as ProviderEndpointHover
  participant Table as ProviderEndpointsTable
  participant Form as ProviderForm
  participant RQ as React Query
  participant Actions as provider-endpoints actions

  UI->>Hover: render(vendorId, providerType)
  Hover->>RQ: useQuery(["provider-endpoints", vendorId])
  RQ->>Actions: getProviderEndpointsByVendor(vendorId)
  Actions-->>RQ: endpoints[]
  RQ-->>Hover: endpoints[]
  Hover->>RQ: (tooltip open) useQuery(["endpoint-circuit", endpointId])
  RQ->>Actions: getEndpointCircuitInfo(endpointId)
  Actions-->>RQ: circuitState
  RQ-->>Hover: circuitState

  UI->>Table: render(vendorId, providerType?, queryKeySuffix?)
  Table->>RQ: useQuery(["provider-endpoints", vendorId, providerType?, suffix?])
  RQ->>Actions: getProviderEndpoints(...) / getProviderEndpointsByVendor(...)
  Actions-->>RQ: endpoints[]
  RQ-->>Table: endpoints[]

  UI->>Form: render(mode, websiteUrl, providerType)
  Form->>RQ: useQuery(["provider-vendors"])
  RQ->>Actions: getProviderVendors()
  Actions-->>RQ: vendors[]
  RQ-->>Form: vendors[]
  Form->>RQ: useQuery(["provider-endpoints", resolvedVendorId, providerType, "provider-form"])
  RQ->>Actions: getProviderEndpoints(resolvedVendorId, providerType)
  Actions-->>RQ: endpoints[]
  RQ-->>Form: endpoints[]

  Table->>Actions: add/edit/remove/probe endpoint
  Actions-->>Table: result
  Table->>RQ: invalidateQueries(["provider-endpoints", ...])
Loading

@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

新增提供商端点管理的本地化字符串与错误提示,引入端点状态模型与 UI 组件(悬停列表、端点表格、端点池表单集成),对提供商视图与表单进行重构并添加大量单元测试与少量配置调整。

Changes

Cohort / File(s) Summary
本地化字符串(providers/strings)
messages/en/settings/providers/strings.json, messages/ja/settings/providers/strings.json, messages/ru/settings/providers/strings.json, messages/zh-CN/settings/providers/strings.json, messages/zh-TW/settings/providers/strings.json
新增顶级键 sortOrder,新增嵌套对象 endpointStatus(含 viewDetailsactiveEndpointsnoEndpointshealthyunhealthyunknowncircuitOpencircuitHalfOpen)。
表单本地化(errors / sections)
messages/*/settings/providers/form/errors.json, messages/*/settings/providers/form/sections.json
为多语言添加验证错误键 keyRequired/nameRequired/urlRequired,并在各语言的表单 basic 中新增 endpointPool 文本条目(标题与描述)。
端点状态模型
src/app/[locale]/settings/providers/_components/endpoint-status.ts
新增类型与接口(EndpointCircuitState / EndpointStatusSeverity / EndpointStatusToken / EndpointStatusModel)及导出函数 getEndpointStatusModel,用于基于 lastProbeOk 与 circuitState 派生 UI 状态模型与视觉属性。
端点悬停组件
src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx
新增客户端组件 ProviderEndpointHover:按状态与 sortOrder 排序,悬停时显示端点列表,延迟获取电路信息并使用 getEndpointStatusModel 渲染状态/延迟/徽章。
端点表格与管理
src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
新增大型模块(ProviderEndpointsTableProviderEndpointsSectionAddEndpointButton、编辑对话等),支持列控制、排序、添加/编辑/探测/删除端点、只读模式与缓存失效处理。
提供商列表项集成
src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
新增对 getProviderVendors 的查询并在能找到 vendor 时显示 vendor 名与 ProviderEndpointHover,找不到时回退显示 legacy URL。
提供商视图重构
src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx
将原内联端点管理替换为集中化 ProviderEndpointsSection,移除大量内联端点组件与相关 mutation 导入,简化渲染路径。
表单端点池集成
src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx, .../basic-info-section.tsx
在 ProviderForm / BasicInfoSection 中加入 vendor 与 endpoints 查询、网站域名归一化、决定是否隐藏 legacy URL 的 endpointPool 流程,修改校验/提交路径及缓存失效逻辑。
单元测试(端点与表单)
tests/unit/settings/providers/endpoint-status.test.ts, provider-endpoint-hover.test.tsx, provider-endpoints-table.test.tsx, provider-rich-list-item-endpoints.test.tsx, provider-form-endpoint-pool.test.tsx, provider-form-total-limit-ui.test.tsx
新增大量单元测试覆盖端点状态、悬停组件、表格交互、表单端点池集成与相关场景(渲染、排序、懒加载、提交、对话框等)。
测试工具与质量检查
tests/unit/i18n/zh-tw-providers-strings-quality.test.ts
改用通用 JsonValue 与递归遍历 visitStrings,以遍历嵌套 i18n 字符串并增强字符串质量验证。
其他小改动
biome.json, messages/en/settings/providers/form/errors.json, 其他少量文件
biome.json schema 版本调整;新增表单错误字符串与 sections 本地化;若干内部逻辑/导入调整以配合集成。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% 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标题准确描述了主要变更:在设置UI中公开供应商端点池,这与代码摘要和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
  • Commit unit tests in branch feat/vendor-endpoint-ui

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
Contributor

Summary of Changes

Hello @ding113, 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!

This pull request significantly upgrades the user interface for managing provider endpoints within the settings. It introduces dedicated components for displaying endpoint health, providing detailed endpoint summaries on hover, and centralizing all endpoint creation, modification, and deletion functionalities. These changes aim to provide a more intuitive and consistent experience for users interacting with provider endpoint configurations.

Highlights

  • New Endpoint Status Logic: Introduced a new utility function (getEndpointStatusModel) to consistently determine and display the UI status (healthy, unhealthy, unknown, circuit open/half-open) of provider endpoints based on their probe results and circuit breaker states.
  • Endpoint Pool Hover Component: Added a ProviderEndpointHover component that displays the count of active endpoints for a vendor and, on hover, provides a detailed list of each endpoint's status and latency, enhancing visibility into endpoint health.
  • Reusable Endpoint CRUD Table: Created a new ProviderEndpointsTable component to centralize and standardize CRUD operations (add, edit, delete, probe, toggle status) for vendor endpoints. This component supports filtering by provider type and a read-only mode.
  • UI Integration and Refactoring: Integrated the new endpoint pool summary into the ProviderRichListItem component, replacing direct URL display with vendor names and the interactive endpoint hover when a vendor is assigned. The ProviderVendorView was refactored to utilize the new ProviderEndpointsSection component, significantly reducing its complexity.
  • Internationalization Support: Added new i18n strings across multiple language files (English, Japanese, Russian, Simplified Chinese, Traditional Chinese) to support the new endpoint status labels and CRUD UI elements.
  • Comprehensive Unit Tests: New unit tests were added for the endpoint-status.ts utility, provider-endpoint-hover.tsx, provider-endpoints-table.tsx, and the endpoint display logic within provider-rich-list-item.tsx, ensuring the reliability and correctness of the new features.
Changelog
  • messages/en/settings/providers/strings.json
    • Added 'Sort Order' string.
    • Added new endpointStatus object with keys for 'viewDetails', 'activeEndpoints', 'noEndpoints', 'healthy', 'unhealthy', 'unknown', 'circuitOpen', and 'circuitHalfOpen'.
  • messages/ja/settings/providers/strings.json
    • Added '並び順' (Sort Order) string.
    • Added new endpointStatus object with Japanese translations for endpoint statuses.
  • messages/ru/settings/providers/strings.json
    • Added 'Порядок' (Sort Order) string.
    • Added new endpointStatus object with Russian translations for endpoint statuses.
  • messages/zh-CN/settings/providers/strings.json
    • Added '排序' (Sort Order) string.
    • Added new endpointStatus object with Simplified Chinese translations for endpoint statuses.
  • messages/zh-TW/settings/providers/strings.json
    • Added '排序' (Sort Order) string.
    • Added new endpointStatus object with Traditional Chinese translations for endpoint statuses.
  • src/app/[locale]/settings/providers/_components/endpoint-status.ts
    • New file: Defines types and getEndpointStatusModel function to map endpoint probe and circuit state to a UI status model (icon, color, label).
  • src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx
    • New file: Implements a component to show active endpoint count and a detailed hover tooltip with individual endpoint statuses and latencies.
  • src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
    • New file: Provides a reusable table component for CRUD operations on provider endpoints, including add, edit, delete, probe, and enable/disable functionality.
  • src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
    • Integrated ProviderEndpointHover to display vendor name and endpoint pool summary when a vendor is assigned, replacing the direct URL.
    • Added useQuery to fetch vendor data for display.
  • src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx
    • Refactored to use the new ProviderEndpointsSection component for managing vendor endpoints, removing duplicated endpoint table logic.
  • tests/unit/settings/providers/endpoint-status.test.ts
    • New file: Added unit tests for getEndpointStatusModel covering various endpoint and circuit states.
  • tests/unit/settings/providers/provider-endpoint-hover.test.tsx
    • New file: Added unit tests for ProviderEndpointHover to verify correct rendering, filtering, sorting, and lazy data fetching.
  • tests/unit/settings/providers/provider-endpoints-table.test.tsx
    • New file: Added comprehensive unit tests for ProviderEndpointsTable, AddEndpointButton, and ProviderEndpointsSection components.
  • tests/unit/settings/providers/provider-rich-list-item-endpoints.test.tsx
    • New file: Added unit tests for ProviderRichListItem to confirm conditional rendering of legacy URLs versus vendor endpoint summaries.
Activity
  • The author ding113 initiated this pull request to enhance provider endpoint management.
  • New UI components for endpoint status display and CRUD operations have been developed.
  • Existing UI components have been refactored to integrate these new features and improve code organization.
  • Internationalization support has been extended to cover all new user-facing strings.
  • Extensive unit tests have been added to validate the functionality of the new and modified components.
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.

@github-actions github-actions bot added size/XL Extra Large PR (> 1000 lines) enhancement New feature or request area:UI area:provider area:i18n labels Feb 4, 2026
Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request is a great addition, exposing vendor endpoint pools in the settings UI. The changes are well-structured, with new components for endpoint status display, a hover-over for endpoint details, and a reusable CRUD table for endpoints. The refactoring in provider-vendor-view.tsx to use the new reusable ProviderEndpointsSection component is a significant improvement in code organization and maintainability. The addition of comprehensive unit tests for the new functionality is also highly commendable. I have one suggestion to improve UI consistency.

Comment on lines +338 to +342
onClick={() => {
if (confirm(t("confirmDeleteEndpoint"))) {
deleteMutation.mutate();
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The delete confirmation uses window.confirm(), which is a blocking browser dialog. This is inconsistent with other parts of the application (e.g., deleting a vendor uses a non-blocking AlertDialog). Using an AlertDialog here would provide a more consistent and modern user experience, improving the overall feel of the interface.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +62 to +68
<div
className="flex items-center gap-1.5 cursor-help opacity-80 hover:opacity-100 transition-opacity focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded px-1"
tabIndex={0}
role="button"
aria-label={t("endpointStatus.viewDetails", { count })}
data-testid="endpoint-hover-trigger"
>
Copy link

Choose a reason for hiding this comment

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

[P1] i18n placeholder mismatch: t("endpointStatus.viewDetails", { count }) passes count, but messages/*/settings/providers/strings.json defines endpointStatus.viewDetails without a {count} placeholder. This will either be ignored or can surface as an intl formatting error depending on next-intl config, and it also means screen readers won’t get the count you intended.

Consider either removing the { count } argument or updating the translation string to include {count} (e.g. "View details ({count})").

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx
Line: 62:68

Comment:
[P1] i18n placeholder mismatch: `t("endpointStatus.viewDetails", { count })` passes `count`, but `messages/*/settings/providers/strings.json` defines `endpointStatus.viewDetails` without a `{count}` placeholder. This will either be ignored or can surface as an intl formatting error depending on `next-intl` config, and it also means screen readers won’t get the count you intended.

Consider either removing the `{ count }` argument or updating the translation string to include `{count}` (e.g. "View details ({count})").

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

Comment on lines 127 to 131
{endpoint.lastProbeLatencyMs && (
<span className="text-[10px] text-muted-foreground tabular-nums shrink-0">
{endpoint.lastProbeLatencyMs}ms
</span>
)}
Copy link

Choose a reason for hiding this comment

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

[P2] Latency display drops valid 0 values: endpoint.lastProbeLatencyMs && (...) treats 0 as falsy, so an endpoint that reports 0ms (or is mocked/tests with 0) won’t show latency. If 0 is a valid value, prefer a null/undefined check (e.g. endpoint.lastProbeLatencyMs != null).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx
Line: 127:131

Comment:
[P2] Latency display drops valid `0` values: `endpoint.lastProbeLatencyMs && (...)` treats `0` as falsy, so an endpoint that reports 0ms (or is mocked/tests with 0) won’t show latency. If `0` is a valid value, prefer a null/undefined check (e.g. `endpoint.lastProbeLatencyMs != null`).

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

Comment on lines 97 to 101
// Build query key based on whether we filter by type
const queryKey = providerType
? ["provider-endpoints", vendorId, providerType, queryKeySuffix].filter(Boolean)
: ["provider-endpoints", vendorId, queryKeySuffix].filter(Boolean);

Copy link

Choose a reason for hiding this comment

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

[P2] queryKey uses .filter(Boolean), which will drop valid falsy values (e.g. 0, ""). If queryKeySuffix is intentionally allowed to be ""/0 for isolation, this will collapse distinct caches and lead to stale/mixed data. Prefer filtering only null/undefined (e.g. filter((v) => v != null)).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
Line: 97:101

Comment:
[P2] `queryKey` uses `.filter(Boolean)`, which will drop valid falsy values (e.g. `0`, `""`). If `queryKeySuffix` is intentionally allowed to be `""`/`0` for isolation, this will collapse distinct caches and lead to stale/mixed data. Prefer filtering only `null`/`undefined` (e.g. `filter((v) => v != null)`).

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

});
}
} else {
toast.error(res.error || t("endpointAddFailed"));
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH] [STANDARD-VIOLATION] UI surfaces non-i18n backend res.error strings on endpoint create

Why this is a problem: CLAUDE.md states: 3. **i18n Required** - All user-facing strings must use i18n (5 languages supported). Never hardcode display text. Here the UI renders res.error directly:

toast.error(res.error || t("endpointAddFailed"));

The backing action returns non-localized error text alongside errorCode (e.g. Chinese messages in src/actions/provider-endpoints.ts), so users can see the wrong language and we bypass the existing getErrorMessage mapping.

Suggested fix:

import { getErrorMessage } from "@/lib/utils/error-messages";

// inside AddEndpointButton
const tErrors = useTranslations("errors");

// ...
} else {
  toast.error(res.errorCode ? getErrorMessage(tErrors, res.errorCode) : t("endpointAddFailed"));
}

setOpen(false);
queryClient.invalidateQueries({ queryKey: ["provider-endpoints"] });
} else {
toast.error(res.error || t("endpointUpdateFailed"));
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH] [STANDARD-VIOLATION] UI surfaces non-i18n backend res.error strings on endpoint edit

Why this is a problem: CLAUDE.md states: 3. **i18n Required** - All user-facing strings must use i18n (5 languages supported). Never hardcode display text. Here the UI renders res.error directly:

toast.error(res.error || t("endpointUpdateFailed"));

The action returns errorCode, so we should translate via getErrorMessage(...) (consistent with provider-vendor-view.tsx) instead of showing raw backend text.

Suggested fix:

import { getErrorMessage } from "@/lib/utils/error-messages";

// inside EditEndpointDialog
const tErrors = useTranslations("errors");

// ...
} else {
  toast.error(
    res.errorCode ? getErrorMessage(tErrors, res.errorCode) : t("endpointUpdateFailed")
  );
}

statusModel.color
)}
>
{circuitState}
Copy link
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] [STANDARD-VIOLATION] Circuit state badge renders raw token (open/half-open) instead of i18n

Why this is a problem: CLAUDE.md states: 3. **i18n Required** - All user-facing strings must use i18n (5 languages supported). Never hardcode display text. This tooltip currently shows:

{circuitState}

Those raw tokens are not localized.

Suggested fix:

{(circuitState === "open" || circuitState === "half-open") && (
  <Badge
    variant="outline"
    className={cn(
      "h-4 px-1 text-[9px] uppercase tracking-wider border-current opacity-80",
      statusModel.color
    )}
  >
    {t(circuitState === "open" ? "endpointStatus.circuitOpen" : "endpointStatus.circuitHalfOpen")}
  </Badge>
)}

className="flex items-center gap-1.5 cursor-help opacity-80 hover:opacity-100 transition-opacity focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded px-1"
tabIndex={0}
role="button"
aria-label={t("endpointStatus.viewDetails", { count })}
Copy link
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] [COMMENT-INACCURATE] i18n placeholder mismatch - viewDetails translation doesn't include {count} parameter

Why this is a problem: Line 66 passes { count } to the translation key, but the translation strings across all languages don't include the {count} placeholder. This may cause runtime errors depending on the i18n library's strictness setting, or silently drop the count from the accessibility label.

Evidence:

  • src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx:66 - aria-label={t("endpointStatus.viewDetails", { count })}
  • messages/en/settings/providers/strings.json:108 - "viewDetails": "View Details" (no {count} placeholder)

Suggested fix:

"viewDetails": "View Details ({count} endpoints)"

Then update all 5 language files (en, ja, ru, zh-CN, zh-TW) with the translated equivalents.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR adds a new provider endpoint pool UI (hover status + reusable endpoint CRUD table) and refactors the vendor view to use it. The remaining issues are i18n compliance problems where UI surfaces raw backend error strings / raw circuit-state tokens that are not localized.

PR Size: XL

  • Lines changed: 2736
  • Files changed: 14
  • Split suggestions: Consider splitting into (1) endpoint status model + hover UI + i18n keys, (2) endpoint CRUD table extraction + vendor view integration, (3) unit tests.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 2 1 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  • [HIGH] [STANDARD-VIOLATION] src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:430 surfaces res.error directly on endpoint create; use errorCode + getErrorMessage instead. (Confidence: 90)
  • [HIGH] [STANDARD-VIOLATION] src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:588 surfaces res.error directly on endpoint edit; use errorCode + getErrorMessage instead. (Confidence: 90)
  • [MEDIUM] [STANDARD-VIOLATION] src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx:147 renders raw circuit state tokens (open/half-open) in UI; localize via i18n. (Confidence: 80)

Review Coverage

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

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Applied PR size label size/XL to PR #719 (feat(providers): expose vendor endpoint pools in settings UI), posted 3 inline review comments, and submitted the review summary via gh pr review.

Issues raised (all on new/changed lines):

  • src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:430 [HIGH] [STANDARD-VIOLATION] toast.error(res.error || ...) surfaces raw backend res.error (can be non-localized); use errorCode + getErrorMessage(...) instead.
  • src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:588 [HIGH] [STANDARD-VIOLATION] same issue on endpoint edit failure path.
  • src/app/[locale]/settings/providers/_components/provider-endpoint-hover.tsx:147 [MEDIUM] [STANDARD-VIOLATION] renders raw circuitState token (open/half-open) in UI; localize via i18n.

XL split suggestion included in the summary: (1) endpoint status model + hover UI + i18n keys, (2) endpoint CRUD table extraction + vendor view integration, (3) unit tests.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR successfully exposes vendor endpoint pools in the Settings UI with comprehensive visual indicators and a reusable CRUD table component. The implementation is well-architected with strong test coverage (1,151 lines of tests across 4 new test files).

PR Size: XL

  • Lines changed: 1,682 (2,209 additions + 527 deletions)
  • Files changed: 14

Note: Despite the XL size, the PR has a net code reduction of 527 lines due to effective refactoring (extracted provider-endpoints-table from vendor view).

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 1 0
Tests 0 0 0 0
Simplification 0 0 0 0

Medium Priority Issues (Should Fix)

  1. i18n Placeholder Mismatch (provider-endpoint-hover.tsx:66)
    • The viewDetails translation key receives a {count} parameter but the translation strings don't include this placeholder
    • May cause runtime errors or silently drop the count from the accessibility label
    • Action Required: Update all 5 language translation files to include {count} placeholder

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Comprehensive (all mutations have error handlers with user feedback)
  • Type safety - Strong (good use of TypeScript types, no any usage)
  • Documentation accuracy - Good (inline comments match implementation)
  • Test coverage - Excellent (80%+ coverage with 1,151 lines of tests)
  • Code clarity - Very good (well-structured, clear naming, good separation of concerns)

Strengths

  1. Excellent Test Coverage: 4 comprehensive test files covering all new components
  2. Complete i18n Support: All 5 languages properly supported (except the one placeholder issue)
  3. Good Refactoring: Successfully extracted reusable components, reducing code by 527 lines
  4. Type Safety: Strong TypeScript usage with no any types
  5. Error Handling: All mutations have proper error handling with toast notifications
  6. Accessibility: Good use of ARIA labels and semantic HTML

Minor Observations (No Action Required)

  1. Query Key Construction: Lines 99-100 in provider-endpoints-table.tsx use .filter(Boolean) which could theoretically collapse distinct cache keys if falsy suffixes are intentionally used. However, this appears to be intentional design since queryKeySuffix is optional.

  2. Latency Display: Line 127 in provider-endpoint-hover.tsx only displays latency when truthy, which means 0ms latency won't be shown. This is likely intentional UX design.

Overall Assessment

This is a high-quality PR that successfully builds on the vendor-endpoint architecture from #608. The single i18n issue is straightforward to fix. The code is well-tested, properly typed, and follows project conventions. The refactoring demonstrates good engineering practices by reducing duplication while adding features.

Recommendation: Approve after fixing the i18n placeholder mismatch.


Automated review by Claude AI

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@coderabbitai coderabbitai bot left a comment

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]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx:
- Around line 74-96: The SectionCard and the inner SmartInputWrapper both use
the same label t("websiteUrl.label"), causing duplicate visible labels; remove
the redundant label by deleting the SectionCard's title prop (SectionCard(...
title={t("websiteUrl.label")} ...)) so the input keeps its label via
SmartInputWrapper, or conversely remove the SmartInputWrapper label and keep
SectionCard's title—adjust only one of SectionCard or SmartInputWrapper
surrounding the website URL input to eliminate the duplicate.
🧹 Nitpick comments (3)
tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx (1)

18-23: 建议简化 hasOwn 辅助函数

当前实现使用了不必要的类型转换。可以直接使用 Object.hasOwn (ES2022+) 或 Object.prototype.hasOwnProperty.call

♻️ 建议的简化方案
-function hasOwn(obj: object, prop: PropertyKey): boolean {
-  return (Object as unknown as { hasOwn: (obj: object, prop: PropertyKey) => boolean }).hasOwn(
-    obj,
-    prop
-  );
-}
+function hasOwn(obj: object, prop: PropertyKey): boolean {
+  return Object.prototype.hasOwnProperty.call(obj, prop);
+}
src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx (1)

145-149: 建议澄清端点 URL 回退逻辑

endpointPoolPreferredUrl 会回退到禁用端点的 URL,但由于 endpointPoolHideLegacyUrlInput 在无启用端点时为 false,这个回退值实际不会被用于提交。逻辑上是安全的,但可以考虑简化以提高可读性。

♻️ 可选的简化方案
-  const endpointPoolPreferredUrl =
-    (enabledEndpointPoolEndpoints[0] ?? endpointPoolEndpoints[0])?.url ?? null;
+  const endpointPoolPreferredUrl = enabledEndpointPoolEndpoints[0]?.url ?? null;

由于仅在 endpointPoolHasEnabledEndpointstrue 时才隐藏旧版 URL 输入,回退到禁用端点的逻辑是冗余的。

tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx (1)

104-112: setNativeValue 辅助函数重复

此辅助函数与 provider-form-total-limit-ui.test.tsx 中的实现完全相同。建议提取到共享的测试工具模块中以避免代码重复。

♻️ 建议提取共享工具函数

创建 tests/unit/utils/test-helpers.ts

export function setNativeValue(element: HTMLInputElement, value: string) {
  const prototype = Object.getPrototypeOf(element) as unknown as { value?: unknown };
  const descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
  if (descriptor?.set) {
    descriptor.set.call(element, value);
    return;
  }
  element.value = value;
}

export async function flushTicks(times = 3) {
  for (let i = 0; i < times; i++) {
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });
  }
}

然后在两个测试文件中导入使用。

Comment on lines +74 to +96
{/* Website URL */}
{!hideWebsiteUrl && (
<SectionCard
title={t("websiteUrl.label")}
description={t("websiteUrl.desc")}
icon={ExternalLink}
>
<SmartInputWrapper label={t("websiteUrl.label")}>
<div className="relative">
<Input
id={isEdit ? "edit-website-url" : "website-url"}
type="url"
value={state.basic.websiteUrl}
onChange={(e) => dispatch({ type: "SET_WEBSITE_URL", payload: e.target.value })}
placeholder={t("websiteUrl.placeholder")}
disabled={state.ui.isPending}
className="pr-10"
/>
<ExternalLink className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
</div>
</SmartInputWrapper>
</SectionCard>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Website URL 区块存在重复的标签

SectionCardtitle 和内部 SmartInputWrapperlabel 都设置为 t("websiteUrl.label"),导致标签文本重复显示。

🐛 建议移除重复的标签
       <SectionCard
         title={t("websiteUrl.label")}
         description={t("websiteUrl.desc")}
         icon={ExternalLink}
       >
-        <SmartInputWrapper label={t("websiteUrl.label")}>
+        <SmartInputWrapper label="">
           <div className="relative">

或者移除 SectionCardtitle,只保留 SmartInputWrapperlabel

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Website URL */}
{!hideWebsiteUrl && (
<SectionCard
title={t("websiteUrl.label")}
description={t("websiteUrl.desc")}
icon={ExternalLink}
>
<SmartInputWrapper label={t("websiteUrl.label")}>
<div className="relative">
<Input
id={isEdit ? "edit-website-url" : "website-url"}
type="url"
value={state.basic.websiteUrl}
onChange={(e) => dispatch({ type: "SET_WEBSITE_URL", payload: e.target.value })}
placeholder={t("websiteUrl.placeholder")}
disabled={state.ui.isPending}
className="pr-10"
/>
<ExternalLink className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
</div>
</SmartInputWrapper>
</SectionCard>
)}
{/* Website URL */}
{!hideWebsiteUrl && (
<SectionCard
title={t("websiteUrl.label")}
description={t("websiteUrl.desc")}
icon={ExternalLink}
>
<SmartInputWrapper label="">
<div className="relative">
<Input
id={isEdit ? "edit-website-url" : "website-url"}
type="url"
value={state.basic.websiteUrl}
onChange={(e) => dispatch({ type: "SET_WEBSITE_URL", payload: e.target.value })}
placeholder={t("websiteUrl.placeholder")}
disabled={state.ui.isPending}
className="pr-10"
/>
<ExternalLink className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
</div>
</SmartInputWrapper>
</SectionCard>
)}
🤖 Prompt for AI Agents
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/sections/basic-info-section.tsx
around lines 74 - 96, The SectionCard and the inner SmartInputWrapper both use
the same label t("websiteUrl.label"), causing duplicate visible labels; remove
the redundant label by deleting the SectionCard's title prop (SectionCard(...
title={t("websiteUrl.label")} ...)) so the input keeps its label via
SmartInputWrapper, or conversely remove the SmartInputWrapper label and keep
SectionCard's title—adjust only one of SectionCard or SmartInputWrapper
surrounding the website URL input to eliminate the duplicate.

ding113 and others added 2 commits February 7, 2026 12:25
- Add bg-popover/text-popover-foreground to tooltip for correct dark mode colors
- Show endpoint URL directly instead of label in hover tooltip
- Use structured errorCode with getErrorMessage for add/edit endpoint errors
- Add i18n circuit state labels (circuitOpen/circuitHalfOpen)
- Add endpoint count to viewDetails i18n string across all 5 languages
- Replace .filter(Boolean) with .filter(value => value != null) per linter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

30 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +205 to +207
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["provider-endpoints"] });
if (data?.result.ok) {
Copy link

Choose a reason for hiding this comment

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

Over-broad cache invalidation

invalidateQueries({ queryKey: ["provider-endpoints"] }) will refetch every endpoints query (all vendors/types/suffixes) whenever you probe/delete/toggle/edit a single endpoint. Since ProviderEndpointHover and the form also use provider-endpoints keys, this can cause unnecessary network churn and UI jank as the settings page grows. Prefer invalidating the specific queryKey used by the table (e.g. include vendorId/providerType/queryKeySuffix) or use invalidateQueries({ queryKey, exact: true }) where possible.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
Line: 205:207

Comment:
**Over-broad cache invalidation**

`invalidateQueries({ queryKey: ["provider-endpoints"] })` will refetch *every* endpoints query (all vendors/types/suffixes) whenever you probe/delete/toggle/edit a single endpoint. Since `ProviderEndpointHover` and the form also use `provider-endpoints` keys, this can cause unnecessary network churn and UI jank as the settings page grows. Prefer invalidating the specific `queryKey` used by the table (e.g. include `vendorId`/`providerType`/`queryKeySuffix`) or use `invalidateQueries({ queryKey, exact: true })` where possible.

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

Comment on lines 1 to 4
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"vcs": {
"enabled": true,
Copy link

Choose a reason for hiding this comment

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

Schema version regression

This changes Biome’s $schema from 2.3.11 to 2.3.10. If the repo/tooling expects the newer schema (e.g., editors using the schema URL for validation/autocomplete), this is a downgrade that can break config validation or hide config errors. Unless you have a concrete reason to pin to the older schema, revert this change and keep the schema consistent with the rest of the repo/tooling.

Prompt To Fix With AI
This is a comment left during a code review.
Path: biome.json
Line: 1:4

Comment:
**Schema version regression**

This changes Biome’s `$schema` from `2.3.11` to `2.3.10`. If the repo/tooling expects the newer schema (e.g., editors using the schema URL for validation/autocomplete), this is a downgrade that can break config validation or hide config errors. Unless you have a concrete reason to pin to the older schema, revert this change and keep the schema consistent with the rest of the repo/tooling.

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

Copy link

@coderabbitai coderabbitai bot left a comment

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]/settings/providers/_components/provider-endpoints-table.tsx:
- Around line 291-298: The Switch's aria-label is static and doesn't reflect the
current enabled state; update the Switch (in the JSX around Switch, using
endpoint.isEnabled and t(...)) to provide a dynamic accessible label (or
aria-checked) that changes with endpoint.isEnabled so screen readers know
whether the endpoint is enabled or disabled; ensure you continue to respect
readOnly and isToggling and use the same toggleMutation.mutate handler.
🧹 Nitpick comments (8)
messages/en/settings/providers/strings.json (1)

71-71: 顶层 noEndpointsendpointStatus.noEndpoints 键名重复,注意区分上下文。

顶层 noEndpoints(Line 71)用于表格空状态,endpointStatus.noEndpoints(Line 110)用于悬浮提示。语义不同但键名相同,后续维护时容易混淆。当前不阻塞,仅提醒注意。

Also applies to: 110-110

src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx (7)

116-128: 变量 t 遮蔽了外层的翻译函数 t

Line 118 中 typeOrder.map((t, i) => [t, i]) 的回调参数 t 遮蔽了 Line 95 的 useTranslations 返回值 t。虽然在 map 回调内没有引用翻译函数,不会造成运行时错误,但会降低可读性,建议重命名为 typept

建议修改
   const endpoints = useMemo(() => {
     const typeOrder = getAllProviderTypes();
-    const typeIndexMap = new Map(typeOrder.map((t, i) => [t, i]));
+    const typeIndexMap = new Map(typeOrder.map((type, i) => [type, i]));

197-220: isProbing / isToggling 手动状态管理与 mutation 内置 isPending 冗余。

useMutation 已经提供 probeMutation.isPendingtoggleMutation.isPending,无需额外用 useState + onMutate/onSettled 手动追踪。简化后可减少状态同步风险。

示例:移除手动状态,改用 mutation.isPending
- const [isProbing, setIsProbing] = useState(false);
- const [isToggling, setIsToggling] = useState(false);

  const probeMutation = useMutation({
    mutationFn: async () => {
      const res = await probeProviderEndpoint({ endpointId: endpoint.id });
      if (!res.ok) throw new Error(res.error);
      return res.data;
    },
-   onMutate: () => setIsProbing(true),
-   onSettled: () => setIsProbing(false),
    onSuccess: (data) => { /* ... */ },
    onError: () => { /* ... */ },
  });

  // In JSX, replace isProbing with probeMutation.isPending
  // and isToggling with toggleMutation.isPending

Also applies to: 238-256


389-391: 可选类型的硬编码排除列表不够灵活。

Line 390 硬编码了 ["claude-auth", "gemini-cli"] 作为排除项。如果未来新增不可选类型,需要手动更新此列表。建议在 PROVIDER_TYPE_CONFIG 中增加如 selectable: boolean 属性,由配置驱动过滤。


497-506: URL 输入框混用了受控与非受控模式。

url Input 没有 value 属性(非受控),但通过 onChange 同步到 state 以供 UrlPreview 使用。而 label Input(Line 514)却是完全受控的(有 value={label})。同一表单内两种模式混用易导致混淆。建议统一为受控模式:

建议修改
           <Input
             id="url"
             name="url"
             placeholder={t("endpointUrlPlaceholder")}
             required
+            value={url}
             onChange={(e) => setUrl(e.target.value)}
           />

如果改为受控,handleSubmit 中也可以直接使用 url state 而非 formData.get("url"),使数据流更清晰。


424-432: 重复的缓存失效调用。

Line 425 的 invalidateQueries({ queryKey: ["provider-endpoints", vendorId] }) 已经会匹配所有以 ["provider-endpoints", vendorId] 开头的查询键(包括带 providerTypequeryKeySuffix 的),因此 Lines 426-431 的第二次失效调用是冗余的。

建议简化
       if (res.ok) {
         toast.success(t("endpointAddSuccess"));
         setOpen(false);
-        // Invalidate both specific and general queries
         queryClient.invalidateQueries({ queryKey: ["provider-endpoints", vendorId] });
-        if (fixedProviderType) {
-          queryClient.invalidateQueries({
-            queryKey: ["provider-endpoints", vendorId, fixedProviderType, queryKeySuffix].filter(
-              (value) => value != null
-            ),
-          });
-        }
       } else {

560-666: 编辑对话框缺少 UrlPreview,与新增对话框不一致。

AddEndpointButton 在 Line 539 包含了 <UrlPreview baseUrl={url} providerType={providerType} />,但 EditEndpointDialog 没有。用户编辑 URL 时同样可能受益于实时预览,确认代理路径是否正确。如果是有意省略请忽略。


339-346: 删除操作使用浏览器原生 confirm() 而非自定义确认对话框。

Line 342 使用 confirm(...) 弹出浏览器原生确认框,与项目其他地方(如 deleteVendorConfirmTitle / deleteVendorDoubleConfirmTitle 等 i18n 键暗示的自定义确认对话框)风格不一致,且无法被主题样式覆盖。建议后续统一为自定义 AlertDialog 组件。

Comment on lines +291 to +298
{!readOnly && (
<Switch
checked={endpoint.isEnabled}
onCheckedChange={(checked) => toggleMutation.mutate(checked)}
disabled={isToggling}
aria-label={t("enabledStatus")}
/>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Switch 的 aria-label 是静态的,无法反映当前启用/禁用状态。

Line 296 的 aria-label={t("enabledStatus")} 始终为 "enabled",不随开关状态变化。屏幕阅读器用户无法区分当前状态。建议根据 endpoint.isEnabled 动态切换。

建议修改
             <Switch
               checked={endpoint.isEnabled}
               onCheckedChange={(checked) => toggleMutation.mutate(checked)}
               disabled={isToggling}
-              aria-label={t("enabledStatus")}
+              aria-label={endpoint.isEnabled ? t("enabledStatus") : t("disabledStatus")}
             />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{!readOnly && (
<Switch
checked={endpoint.isEnabled}
onCheckedChange={(checked) => toggleMutation.mutate(checked)}
disabled={isToggling}
aria-label={t("enabledStatus")}
/>
)}
{!readOnly && (
<Switch
checked={endpoint.isEnabled}
onCheckedChange={(checked) => toggleMutation.mutate(checked)}
disabled={isToggling}
aria-label={endpoint.isEnabled ? t("enabledStatus") : t("disabledStatus")}
/>
)}
🤖 Prompt for AI Agents
In `@src/app/`[locale]/settings/providers/_components/provider-endpoints-table.tsx
around lines 291 - 298, The Switch's aria-label is static and doesn't reflect
the current enabled state; update the Switch (in the JSX around Switch, using
endpoint.isEnabled and t(...)) to provide a dynamic accessible label (or
aria-checked) that changes with endpoint.isEnabled so screen readers know
whether the endpoint is enabled or disabled; ensure you continue to respect
readOnly and isToggling and use the same toggleMutation.mutate handler.

@ding113 ding113 merged commit 137fdd7 into dev Feb 7, 2026
6 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Feb 7, 2026
@github-actions github-actions bot mentioned this pull request Feb 7, 2026
10 tasks
ding113 added a commit that referenced this pull request Feb 7, 2026
* fix(proxy): add 'cannot be modified' error detection to thinking signature rectifier

Extend the thinking signature rectifier to detect and handle the
Anthropic API error when thinking/redacted_thinking blocks have been
modified from their original response. This error occurs when clients
inadvertently modify these blocks in multi-turn conversations.

The rectifier will now remove these blocks and retry the request,
similar to how it handles other thinking-related signature errors.

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

* chore(deps): bump jspdf in the npm_and_yarn group across 1 directory

Bumps the npm_and_yarn group with 1 update in the / directory: [jspdf](https://github.com/parallax/jsPDF).


Updates `jspdf` from 3.0.4 to 4.1.0
- [Release notes](https://github.com/parallax/jsPDF/releases)
- [Changelog](https://github.com/parallax/jsPDF/blob/master/RELEASE.md)
- [Commits](parallax/jsPDF@v3.0.4...v4.1.0)

---
updated-dependencies:
- dependency-name: jspdf
  dependency-version: 4.1.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: Hot-reload cache invalidation for Request Filters and Sensitive Words (#710)

* fix: hot-reload request filters via globalThis singleton pattern

EventEmitter and RequestFilterEngine now use globalThis caching to ensure
the same instance is shared across different Next.js worker contexts.
This fixes the issue where filter changes required Docker restart.

Added diagnostic logging for event subscription and propagation.

* fix(redis): wait for subscriber connection ready before subscribe

- ensureSubscriber now returns Promise<Redis>, waits for 'ready' event
- subscribeCacheInvalidation returns null on failure instead of noop
- RequestFilterEngine checks cleanup !== null before logging success
- Fixes false "Subscribed" log when Redis connection fails

* feat(sensitive-words): add hot-reload via Redis pub/sub

Enable real-time cache invalidation for sensitive words detector,
matching the pattern used by request-filter-engine and error-rule-detector.

* fix(redis): harden cache invalidation subscriptions

Ensure sensitive-words CRUD emits update events so hot-reload propagates across workers. Roll back failed pub/sub subscriptions, add retry/timeout coverage, and avoid sticky provider-cache subscription state.

* fix(codex): bump default User-Agent fallback

Update the hardcoded Codex UA used when requests lack an effective user-agent (e.g. filtered out). Keep unit tests in sync with the new default.

* fix(redis): resubscribe cache invalidation after reconnect

Clear cached subscription state on disconnect and resubscribe on ready so cross-worker cache invalidation survives transient Redis reconnects. Add unit coverage, avoid misleading publish logs, track detector cleanup handlers, and translate leftover Russian comments to English.

* fix(sensitive-words): use globalThis singleton detector

Align SensitiveWordDetector with existing __CCH_* singleton pattern to avoid duplicate instances across module reloads. Extend singleton unit tests to cover the detector.

* chore: format code (req-fix-dda97fd)

* fix: address PR review comments

- pubsub.ts: use `once` instead of `on` for ready event to prevent
  duplicate resubscription handlers on reconnect
- forwarder.ts: extract DEFAULT_CODEX_USER_AGENT constant
- provider-cache.ts: wrap subscribeCacheInvalidation in try/catch
- tests: use exported constant instead of hardcoded UA string

* fix(redis): resubscribe across repeated reconnects

Ensure pub/sub resubscribe runs on every reconnect, extend unit coverage, and keep emitRequestFiltersUpdated resilient when logger import fails.

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* feat(logs): make cost column toggleable with improved type safety (#715)

close #713

* fix(proxy): add OpenAI chat completion format support in usage extraction (#705) (#716)

The `extractUsageMetrics` function was missing support for OpenAI chat
completion format fields (`prompt_tokens`/`completion_tokens`), causing
token statistics to not be recorded for OpenAI-compatible providers.

Changes:
- Add `prompt_tokens` -> `input_tokens` mapping
- Add `completion_tokens` -> `output_tokens` mapping
- Preserve priority: Claude > Gemini > OpenAI format
- Add 5 unit tests for OpenAI format handling

Closes #705

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix(currency): respect system currencyDisplay setting in UI (#717)

Fixes #678 - Currency display unit configuration was not applied.

Root cause:
- `users-page-client.tsx` hardcoded `currencyCode="USD"`
- `UserLimitBadge` and `LimitStatusIndicator` had hardcoded `unit="$"` default
- `big-screen/page.tsx` used hardcoded "$" in multiple places

Changes:
- Add `getCurrencySymbol()` helper function to currency.ts
- Fetch system settings in `users-page-client.tsx` and pass to table
- Pass `currencySymbol` from `user-key-table-row.tsx` to limit badges
- Remove hardcoded "$" defaults from badge components
- Update big-screen page to fetch settings and use dynamic symbol
- Add unit tests for `getCurrencySymbol`

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* feat(gemini): add Google Search web access preference for Gemini providers (#721)

* feat(gemini): add Google Search web access preference for Gemini providers

Add provider-level preference for Gemini API type providers to control
Google Search (web access) tool injection:

- inherit: Follow client request (default)
- enabled: Force inject googleSearch tool into request
- disabled: Force remove googleSearch tool from request

Changes:
- Add geminiGoogleSearchPreference field to provider schema
- Add GeminiGoogleSearchPreference type and validation
- Implement applyGeminiGoogleSearchOverride with audit trail
- Add UI controls in provider form (Gemini Overrides section)
- Add i18n translations for 5 languages (en, zh-CN, zh-TW, ja, ru)
- Integrate override logic in proxy forwarder for Gemini requests
- Add 22 unit tests for the override logic

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

* fix(gemini): address code review feedback

- Use explicit else-if for disabled preference check (gemini-code-assist)
- Use i18n for SelectValue placeholder instead of hardcoded string (coderabbitai)
- Sync overridden body back to session.request.message for log consistency (coderabbitai)
- Persist Gemini special settings immediately, matching Anthropic pattern (coderabbitai)

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

* fix(gemini): use strict types for provider config and audit

- Narrow preference type to "enabled" | "disabled" (exclude unreachable "inherit")
- Use ProviderType and GeminiGoogleSearchPreference types instead of string

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix(api): 透传 /api/actions 认证会话以避免 getUsers 返回空数据 (#720)

* fix(api): 透传 /api/actions 认证会话以避免 getUsers 返回空数据

* fix(auth): 让 scoped session 继承 allowReadOnlyAccess 语义并支持内部降权校验

* chore: format code (dev-93585fa)

* fix: bound SessionTracker active_sessions zsets by env TTL (#718)

* fix(session): bound active_sessions zsets by env ttl

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(rate-limit): pass session ttl to lua cleanup

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(session): validate SESSION_TTL env and prevent ZSET leak on invalid values

- Add input validation for SESSION_TTL (reject NaN, 0, negative; default 300)
- Guard against invalid TTL in Lua script to prevent clearing all sessions
- Use dynamic EXPIRE based on SESSION_TTL instead of hardcoded 3600s
- Add unit tests for TTL validation and dynamic expiry behavior

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(provider): stop standard-path fallback to legacy provider url

* feat(providers): expose vendor endpoint pools in settings UI (#719)

* feat(providers): add endpoint status mapping

* feat(providers): add endpoint pool hover

* feat(providers): show vendor endpoints in list rows

* feat(providers): extract vendor endpoint CRUD table

* chore(i18n): add provider endpoint UI strings

* fix(providers): integrate endpoint pool into provider form

* fix(provider): wrap endpoint sync in transactions to prevent race conditions (#730)

* fix(provider): wrap provider create/update endpoint sync in transactions

Provider create and update operations now run vendor resolution and
endpoint sync inside database transactions to prevent race conditions
that could leave orphaned or inconsistent endpoint rows.

Key changes:
- createProvider: wrap vendor + insert + endpoint seed in a single tx
- updateProvider: wrap vendor + update + endpoint sync in a single tx
- Add syncProviderEndpointOnProviderEdit for atomic URL/type/vendor
  migration with in-place update, soft-delete, and conflict handling
- Vendor cleanup failures degrade to warnings instead of propagating
- Add comprehensive unit and integration tests for sync edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(provider): defer endpoint circuit reset until transaction commit

Avoid running endpoint circuit reset side effects inside DB transactions to prevent rollback inconsistency. Run resets only after commit and add regression tests for deferred reset behavior in helper and provider update flows.

* fix(provider): distinguish noop from created-next in endpoint sync action label

When ensureNextEndpointActive() returns "noop" (concurrent transaction
already created an active next endpoint), the action was incorrectly
labelled "kept-previous-and-created-next". Add a new
"kept-previous-and-kept-next" action to ProviderEndpointSyncAction and
use a three-way branch so callers and logs reflect the true outcome.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review comments from PR #731

- fix(auth): prevent scoped session access widening via ?? -> && guard
- fix(i18n): standardize zh-CN provider terminology to "服务商"
- fix(i18n): use consistent Russian translations for circuit status
- fix(i18n): replace raw formatDistanceToNow with locale-aware RelativeTime
- fix(gemini): log warning for unknown google search preference values
- fix(error-rules): check subscribeCacheInvalidation return value
- fix(test): correct endpoint hover sort test to assert URLs not labels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export auth session storage and fix test mock types

- Export authSessionStorage from auth-session-storage.node.ts to prevent
  undefined on named imports; remove duplicate declare global block
- Fix mockEndpoints in provider-endpoint-hover test: remove nonexistent
  lastOk/lastLatencyMs fields, add missing lastProbe* fields, use Date
  objects for createdAt/updatedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: miraserver <20286838+miraserver@users.noreply.github.com>
Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 泠音 <im@ling.plus>
Co-authored-by: Longlone <41830147+WAY29@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
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/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant