Skip to content

feat(routes): 在路由页展示生效策略并支持行内编辑权重#605

Merged
awsl233777 merged 3 commits into
mainfrom
feat/routes-strategy-weight-ui
Jun 7, 2026
Merged

feat(routes): 在路由页展示生效策略并支持行内编辑权重#605
awsl233777 merged 3 commits into
mainfrom
feat/routes-strategy-weight-ui

Conversation

@Bowl42

@Bowl42 Bowl42 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

背景

routes 页面(全局页和 project 页,二者共用 ClientTypeRoutesContent)一直存在两个缺口:

  1. 看不到生效的路由策略 —— 站在某个 client/project 的路由列表前,无从得知走的是 priority 还是 weighted_random,策略配置和路由管理两个页面割裂。
  2. weight 不可见也不可编辑 —— 新建 route 时 weight 被硬编码为 1,列表里既不显示也无法修改,排序只能拖拽改 position。结果:即使把某 project 切到 weighted_random,纯前端用户也看不到它生效、更调不了权重(只能靠 CLI route set-weight 或 API)。

改动

  • 策略横幅:在路由列表顶部展示当前作用域实际生效的策略,解析顺序与后端 router.getRoutingStrategy 一致(project 专属 → 全局 → priority 默认),并标注「继承自全局」/「默认」,附带跳转到「路由策略」页的快捷入口。
  • 行内权重编辑:当生效策略为 weighted_random 时,每条路由行显示一个可编辑的权重输入框。失焦 / 回车提交,<1 归一到 1,值未变不发请求,Esc 取消;pointer 事件做了 stopPropagation,不会误触发拖拽或详情弹窗。复用已有的 updateRoute mutation(后端早已支持 weight 字段)。
  • 会话亲和(sticky)状态:weighted_random 下横幅额外显示 session 亲和是否开启及其 scope(token/conversation)和 TTL。亲和需要在策略 config 里单独开启(只切 weighted_random 不会自动生效 —— 见 router.go 的三段式 gating),过去 routes 页完全看不到它,导致"权重明明配了却看不出被亲和黏住"。现在一眼可见;priority 下不显示(那里亲和是 no-op)。
  • 全局页与所有 project 页自动同时生效(共用同一组件,按 projectID 区分作用域)。
  • showWeight 透传到 memo 化的行组件,并同步更新其 equality 比较函数,保持原有重渲染优化。
  • 新增 en / zh 文案。

改动文件

  • web/src/components/routes/ClientTypeRoutesContent.tsx —— 策略解析 + 横幅 + 传 showWeight
  • web/src/pages/client-routes/components/provider-row.tsx —— RouteWeightControl 行内权重编辑 + prop 透传
  • web/src/locales/{en,zh}.json —— 文案

验证

  • tsc 类型检查通过
  • eslint 干净
  • prettier 通过(pre-commit lint-staged 已跑)
  • vite build 生产构建成功

截图

截图取自本地实例 + 假数据(Demo Provider Alpha/Bravo/... + api.example.com),不含任何真实数据。

priority(默认) —— 横幅显示 Priority (by position) + (default),提示拖拽排序,不显示权重:

routes priority

weighted_random —— 横幅切到 Weighted Random,每行多出可编辑的权重输入框(5 / 3 / 2 / 1):

routes weighted

weighted_random + 会话亲和开启 —— 横幅多出一枚 📌 Affinity on · by conversation · 30m 徽章,直接暴露 sticky 的开关状态、scope 和 TTL:

routes affinity

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新功能

    • 在路由列表上方新增“有效路由策略”提示横幅,展示当前生效策略、继承/默认状态、并可跳转至配置页
    • 在加权随机策略下显示权重与会话亲和(sticky)状态,支持 Pin 标识与 TTL 显示
    • 在路由列表中新增内联权重编辑器,允许直接修改并提交路由权重(含取消/回退与输入校验)
  • 文档/本地化

    • 补充中/英文路由策略、权重与亲和相关的本地化文案与提示

…tes pages

The routes pages (global and per-project, both backed by ClientTypeRoutesContent)
never showed which routing strategy was actually in effect, and route weight was
hardcoded to 1 with no way to view or edit it from the UI — so weighted_random was
effectively unusable for web-only users.

- Add a banner above the routes list showing the effective strategy for the scope,
  resolved in the same order as the backend (project-specific → global → priority
  default), with an "inherited from Global" / "default" hint and a shortcut to the
  Routing Strategies page.
- When the effective strategy is weighted_random, render an inline, editable weight
  input on each route row (commit on blur/Enter, clamps to >=1, no-op when unchanged,
  Esc to cancel). Reuses the existing updateRoute mutation.
- Thread showWeight through the memoized row components and keep their equality
  checks in sync.
- Add en/zh i18n keys.

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

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a08afcc0-140c-47b0-ad4a-c45e82335308

📥 Commits

Reviewing files that changed from the base of the PR and between 24ca9fe and 857c2bb.

📒 Files selected for processing (2)
  • web/src/locales/zh.json
  • web/src/pages/client-routes/components/provider-row.tsx
✅ Files skipped from review due to trivial changes (1)
  • web/src/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/pages/client-routes/components/provider-row.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e
  • GitHub Check: Backend Checks
  • GitHub Check: playwright

📝 Walkthrough

Walkthrough

在路由列表顶部显示生效路由策略横幅(按项目/全局优先解析),在 weighted_random 策略下为 provider 行启用权重内联编辑,并对 provider 行 UI 与中英文文案做相应调整。

Changes

路由策略与权重控制

Layer / File(s) Summary
策略解析与 banner 渲染
web/src/components/routes/ClientTypeRoutesContent.tsx
新增 RoutingStrategyBanner,使用 useRoutingStrategies 按 project/global 优先解析生效策略并在顶部渲染;派生 isWeighted 供行级联动。
权重控制器实现与行级集成
web/src/pages/client-routes/components/provider-row.tsx
新增 RouteWeightControl 受控数值编辑,Escape 取消、失焦/回车提交至 updateRoute.mutate;在 SortableProviderRow/ProviderRowContent 中新增 showWeight?: boolean 并纳入 memo 相等性比较,按 showWeight && item.route 条件渲染编辑器并阻止拖拽冲突。
provider 行 UI 结构与样式优化
web/src/pages/client-routes/components/provider-row.tsx
重排 healthLevel 条件分支、主按钮与图标容器样式,重构 Stats Grid 与冷却区 JSX 与回调入参映射。
国际化文案支持
web/src/locales/en.json, web/src/locales/zh.json
routes 命名空间新增/替换路由策略、weight 与 sticky 相关中英文文案键及 tooltip。

Sequence Diagram

sequenceDiagram
  participant ClientTypeRoutesContent
  participant useRoutingStrategies
  participant RoutingStrategyBanner
  participant ProviderRowContent
  participant RouteWeightControl
  ClientTypeRoutesContent->>useRoutingStrategies: 请求策略集合
  useRoutingStrategies-->>ClientTypeRoutesContent: 返回策略列表
  ClientTypeRoutesContent->>ClientTypeRoutesContent: 解析生效策略并计算 isWeighted
  ClientTypeRoutesContent->>RoutingStrategyBanner: 传递策略信息并渲染横幅
  ClientTypeRoutesContent->>ProviderRowContent: 传递 showWeight = isWeighted
  ProviderRowContent->>RouteWeightControl: 条件渲染权重编辑器
  RouteWeightControl->>updateRoute: 提交权重 (onBlur/Enter)
Loading

预估审查工作量

🎯 3 (Moderate) | ⏱️ ~25 分钟

可能相关的 PR

  • awsl-project/maxx#444: 与前端加权随机路由 UI 与后端 Route.weight 持久化/选择逻辑存在直接联动。
  • awsl-project/maxx#584: 与 weighted_random 的后端实现及 session-affinity/sticky 配置在字段与类型上对应。

建议审查人

  • awsl233777
  • ymkiux

兔兔之诗

横幅亮起策略牌,🐰
行中权重轻点下,
粘性会话记时长,
文案双语齐上架,
代码整洁审得快。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确概括了主要变更:在路由页添加策略横幅展示和权重行内编辑功能,与raw_summary及objectives完全对应。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/routes-strategy-weight-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.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
web/src/locales/zh.json (1)

629-637: 💤 Low value

可考虑统一中文标点符号为全角格式。

所有翻译内容准确清晰,仅有一处可选的样式改进:

Line 637 的 tooltip 中使用了半角括号 (加权随机策略),建议改为全角括号 (加权随机策略) 以符合中文排版习惯。不过本文件中已存在半角/全角混用的情况(如 Line 248-249 用全角),因此这只是可选的样式优化建议。

♻️ 可选的标点优化
-    "weightTooltip": "权重越大被选中的概率越高(加权随机策略)。"
+    "weightTooltip": "权重越大被选中的概率越高(加权随机策略)。"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/locales/zh.json` around lines 629 - 637, Update the "weightTooltip"
translation value to use fullwidth Chinese parentheses: replace the halfwidth
"(加权随机策略)" with fullwidth "(加权随机策略)" in the JSON entry for the "weightTooltip"
key so the tooltip follows Chinese punctuation conventions; ensure the string
remains valid JSON and only that punctuation is changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web/src/locales/zh.json`:
- Around line 629-637: Update the "weightTooltip" translation value to use
fullwidth Chinese parentheses: replace the halfwidth "(加权随机策略)" with fullwidth
"(加权随机策略)" in the JSON entry for the "weightTooltip" key so the tooltip follows
Chinese punctuation conventions; ensure the string remains valid JSON and only
that punctuation is changed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1bc9b64e-a072-43e2-8e2e-87d36644b7ab

📥 Commits

Reviewing files that changed from the base of the PR and between d60e315 and 0bde9b2.

📒 Files selected for processing (4)
  • web/src/components/routes/ClientTypeRoutesContent.tsx
  • web/src/locales/en.json
  • web/src/locales/zh.json
  • web/src/pages/client-routes/components/provider-row.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: playwright
🔇 Additional comments (13)
web/src/locales/en.json (1)

630-638: LGTM!

web/src/components/routes/ClientTypeRoutesContent.tsx (4)

8-9: LGTM!

Also applies to: 36-36, 43-43, 49-50


108-156: 策略横幅组件实现良好。

RoutingStrategyBanner 组件清晰地展示了有效的路由策略,包括策略类型、继承/默认状态以及策略说明。Link 到 /routing-strategies 为用户提供了便捷的配置入口。

结构合理,国际化文案正确集成,UI 层次清晰。


197-212: 策略解析逻辑正确镜像后端优先级。

解析顺序(项目专属 → 全局 → 默认优先级)与 PR 目标中描述的后端 router.getRoutingStrategy 一致:

  • own: 当前项目的策略
  • global: projectID 为 0 的全局策略
  • resolved: 优先使用项目策略,否则回退到全局
  • inheritedisDefault 的判断逻辑准确

useMemo 依赖项正确,避免不必要的重新计算。


445-450: 策略驱动的权重显示集成正确。

横幅渲染和 showWeight={isWeighted} 的传递逻辑清晰:

  • 横幅始终显示当前生效策略
  • 仅当策略为 weighted_random 时启用权重编辑器
  • 正确透传到每个路由行组件

功能按设计实现。

Also applies to: 473-473

web/src/pages/client-routes/components/provider-row.tsx (8)

9-15: LGTM!

Also applies to: 20-20, 22-22


25-83: 权重编辑器实现完善,交互逻辑清晰。

RouteWeightControlBase 组件的实现要点:

  • 使用 editing 状态标记,配合 useEffect 同步服务端值,避免在用户输入时覆盖本地编辑
  • 失焦/回车提交,Escape 取消并恢复原值
  • 验证逻辑将 < 1 或非法值归一到 1,与后端 Route 契约一致
  • 仅在值实际变化时才调用 updateRoute.mutate,避免无效请求
  • stopPropagation 阻止事件冒泡,防止触发拖拽或行点击

事件处理和状态管理均符合 React 最佳实践。


117-117: showWeight 属性正确集成到组件接口与 memo 优化。

新增的 showWeight?: boolean 属性:

  • 已添加到 SortableProviderRowPropsProviderRowContentProps
  • 已纳入两个 memo 相等性比较函数(areSortableProviderRowEqualareProviderRowContentEqual
  • 正确透传到子组件

确保策略切换时权重编辑器的显示/隐藏能触发必要的重新渲染。

Also applies to: 133-133, 144-144, 187-187, 230-230, 315-315, 329-329


638-638: 条件渲染逻辑正确。

{showWeight && item.route && <RouteWeightControl route={item.route} />}

仅在策略为 weighted_random 且路由对象存在时渲染权重编辑器,逻辑严谨。


352-359: 健康级别计算重构提升可读性。

将冷却状态的多级判断从内联表达式拆分为更清晰的多行条件结构:

  • 无冷却 → healthy
  • 存在全局冷却(无 clientType & model)→ frozen
  • 存在客户端级冷却(有 clientType,无 model)→ limited
  • 否则 → degraded(模型级冷却)

逻辑保持不变,可读性显著提升。


586-629: 统计网格结构简化,保持功能一致。

将 SR/TKN/Cost 三个指标的 JSX 结构改为更紧凑的 span 布局,并优化类名分支。逻辑未变,代码更简洁。


665-691: 冷却区 UI 结构优化,样式表达更清晰。

雪花图标、标签、倒计时与"清除冷却"按钮的 JSX 结构和类名被重新组织,提升了代码可维护性和一致性。


695-698: onClearCooldown 调用方式改为对象 API,提升可维护性。

从位置参数 onClearCooldown?.(cd.clientType || undefined, cd.model || undefined) 改为对象参数:

onClearCooldown?.({
  clientType: cd.clientType || undefined,
  model: cd.model || undefined,
})

与类型签名 (options?: { clientType?: string; model?: string }) => void 匹配,代码意图更明确。

When the effective strategy is weighted_random, the banner now shows whether
session affinity is on, plus its scope (token/conversation) and TTL — so the
routes' weights are read in context. Affinity is intentionally not shown under
priority (it's a no-op there per router.go). Adds en/zh copy.
Bowl42 pushed a commit that referenced this pull request Jun 7, 2026
@awsl233777

Copy link
Copy Markdown
Collaborator

Review note from checking the inline weight editor behavior:

The new Esc cancel path in RouteWeightControlBase can still commit the in-progress value because it calls blur() after setValue(String(route.weight ?? 1)), and the input's onBlur always runs commit().

Current flow in provider-row.tsx:

} else if (e.key === 'Escape') {
  setEditing(false);
  setValue(String(route.weight ?? 1));
  (e.target as HTMLInputElement).blur();
}

setValue(...) is not guaranteed to be reflected before the blur handler reads value, so pressing Esc after typing a changed weight can still send updateRoute.mutate(...) from commit() instead of cancelling.

Suggested fix: add an explicit cancel guard/ref for the next blur, or split cancel() from commit() so Escape resets local state without invoking the blur commit path. That keeps the intended blur/Enter commit behavior while making Esc a true cancel.

Esc reset setValue() then called blur(), but onBlur=commit() runs
synchronously and reads the pre-reset value from its closure, so a
changed weight was still committed instead of cancelled. Guard commit
with a cancelRef set by Esc. Also switch zh weightTooltip parens to
fullwidth.

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

Bowl42 commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator Author

Good catch — confirmed and fixed in 857c2bb.

You're right: Escape called setValue(原值) then blur(), but onBlur={commit} runs synchronously within the keydown handler and reads the pre-reset value from its render closure, so a changed weight was still committed. setValue hadn't taken effect yet.

Rather than thread the reset through async state, I added a cancelRef (a ref, since blur fires before any state update from this render is visible to commit). Escape sets cancelRef.current = true then blurs; commit sees the flag, restores the original value, and returns early without mutating. Blur/Enter commit behavior is unchanged.

const cancelRef = useRef(false);

const commit = () => {
  setEditing(false);
  if (cancelRef.current) {
    cancelRef.current = false;
    setValue(String(route.weight ?? 1));
    return;
  }
  // ...normalize + mutate only when changed
};

// Escape:
cancelRef.current = true;
(e.target as HTMLInputElement).blur();

Also applied the fullwidth-parens nitpick on weightTooltip. tsc clean.

@awsl233777 awsl233777 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved after re-checking the current head. Checks are green, CodeRabbit is successful, and the Esc-cancel issue raised in review was fixed in 857c2bb with a cancelRef guard so blur no longer commits cancelled edits.

@awsl233777 awsl233777 merged commit 616f8bc into main Jun 7, 2026
5 checks passed
@awsl233777 awsl233777 deleted the feat/routes-strategy-weight-ui branch June 7, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants