Skip to content

feat: 添加路由拖动排序功能和批量更新位置API#85

Merged
awsl233777 merged 3 commits intomainfrom
feat/bhwa233
Jan 16, 2026
Merged

feat: 添加路由拖动排序功能和批量更新位置API#85
awsl233777 merged 3 commits intomainfrom
feat/bhwa233

Conversation

@lxw15337674
Copy link
Contributor

@lxw15337674 lxw15337674 commented Jan 16, 2026

Summary by CodeRabbit

发布说明

  • 新功能

    • 支持批量更新路由位置(新增批量更新端点与对应界面调用)
    • 全面引入国际化,新增中/英文词条并本地化大量界面文本
  • 优化改进

    • 路由拖拽采用乐观更新,交互更顺畅且失败回滚
    • 缓存刷新增强,批量更新后界面即时生效
    • 多处界面样式与配色调整,改进项目卡片与视觉层次感

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

新增后端批量更新路由位置的类型、接口、事务性仓储实现与缓存重载;新增 /admin/routes/batch-positions PUT 管理端点及服务方法;前端改为单次批量请求并实现乐观更新;大量前端组件接入 i18n 并做若干样式调整。

Changes

Cohort / File(s) 变更摘要
域模型
internal/domain/model.go
新增导出类型 RoutePositionUpdate { ID uint64, Position int }
仓储接口
internal/repository/interfaces.go
RouteRepository 接口新增 BatchUpdatePositions(updates []domain.RoutePositionUpdate) error
后端 - 仓储实现
internal/repository/sqlite/route.go
新增事务性 BatchUpdatePositions 实现:在单个事务中批量 UPDATE 并设置 updated_at,含完整错误传播
后端 - 缓存层
internal/repository/cached/route.go
在缓存仓储新增 BatchUpdatePositions,委托底层实现并在成功后调用 Load() 刷新缓存
后端 - 服务与处理器
internal/service/admin.go, internal/handler/admin.go
服务层新增 BatchUpdateRoutePositions,AdminHandler 新增 PUT /admin/routes/batch-positions 处理并调用服务方法,含请求校验与 JSON 解码
前端 - 类型与传输
web/src/lib/transport/types.ts, web/src/lib/transport/interface.ts, web/src/lib/transport/http-transport.ts, web/src/lib/transport/index.ts
新增前端类型 RoutePositionUpdate 并在 Transport 接口与 HttpTransport 中新增 batchUpdateRoutePositions(updates),调用后端新端点
前端 - 查询与乐观更新
web/src/hooks/queries/use-routes.ts, web/src/components/routes/ClientTypeRoutesContent.tsx
将逐条更新替换为单次批量请求;前端在拖拽结束采用 queryClient 做乐观更新并在失败时失效/回滚;调整拖拽处理以配合批量提交
大规模 i18n 与本地化
web/src/locales/en.json, web/src/locales/zh.json, 多个前端组件/页面 (web/src/**)
在大量页面与组件中接入 react-i18next,替换硬编码文本为 t(...),并统一若干文本色彩/背景样式 token;en/zh 本地化词条大幅扩展(app、apiTokens、provider、routes、requests、modelInput、common 等)

Sequence Diagram(s)

sequenceDiagram
    participant UI as Client UI
    participant QC as QueryClient (react-query)
    participant T as HttpTransport (frontend)
    participant H as AdminHandler (backend)
    participant S as AdminService
    participant R as RouteRepository (sqlite)
    participant DB as Database
    participant C as CachedRepo

    UI->>QC: 用户拖拽完成,触发 reorder
    QC->>UI: 立即应用乐观更新(更新本地 routes 列表)
    UI->>T: PUT /routes/batch-positions {updates[]}
    T->>H: HTTP PUT /admin/routes/batch-positions
    H->>S: 调用 BatchUpdateRoutePositions(updates)
    S->>R: 执行 BatchUpdatePositions(updates)
    R->>DB: 开启事务,执行多次 UPDATE ... WHERE id=?
    DB-->>R: 返回执行结果
    R->>DB: 提交事务
    R-->>S: 返回成功
    S-->>H: 返回成功
    H-->>T: 返回 200 OK
    T-->>QC: 请求成功 -> 可能触发 invalidate / 确认状态
    QC-->>UI: 保持或确认最终数据
    Note over R,C: 若使用缓存层,BatchUpdatePositions 后触发 C.Load() 刷新缓存
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 分钟

Possibly related PRs

Suggested reviewers

  • whhjdi

Poem

🐰 我在代码田野轻轻跳,批量位置一并调,

前端乐观先上阵,后端事务稳又牢,
缓存重载同步到,翻译词条满园抱。 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.73% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题清晰准确地概括了主要变更:添加了路由拖动排序功能和新的批量更新位置API。标题简洁专注,直接反映了核心功能增强。

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a230f7 and 077712b.

📒 Files selected for processing (1)
  • web/src/pages/client-routes/components/provider-row.tsx

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

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: 14

Caution

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

⚠️ Outside diff range comments (12)
web/src/pages/projects/tabs/sessions.tsx (1)

65-74: 遗漏国际化:空状态提示文本未翻译。

第 70 行的 "No sessions for this project" 仍是硬编码的英文字符串,与本文件其他部分的 i18n 实现不一致。

🌐 建议修复
              {projectSessions.length === 0 && (
                <TableRow>
                  <TableCell colSpan={4} className="h-32 text-center text-muted-foreground border-border">
                    <div className="flex flex-col items-center justify-center gap-2">
                      <Users className="h-8 w-8 opacity-20" />
-                      <p>No sessions for this project</p>
+                      <p>{t('sessions.noSessions')}</p>
                    </div>
                  </TableCell>
                </TableRow>
              )}

请确保在翻译文件中添加对应的 sessions.noSessions 键。

web/src/pages/providers/components/provider-edit-flow.tsx (1)

349-391: DeleteConfirmModal 组件中 t 未定义,会导致运行时错误。

t 函数在第 36 行通过 useTranslation() hook 定义,作用域仅限于 ProviderEditFlow 组件。DeleteConfirmModal 是一个独立的函数组件,无法访问该变量,第 385 行使用 t() 会抛出 ReferenceError

🐛 修复方案:在 DeleteConfirmModal 中添加 useTranslation hook
 function DeleteConfirmModal({
   providerName,
   deleting,
   open,
   onConfirm,
   onCancel,
 }: {
   providerName: string
   deleting: boolean
   open: boolean
   onConfirm: () => void
   onCancel: () => void
 }) {
+  const { t } = useTranslation()
   return (
     <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>
web/src/pages/providers/components/antigravity-provider-view.tsx (1)

92-105: ModelQuotaCard 组件中 t 未定义会导致运行时错误。

t 仅在父组件 AntigravityProviderView 中通过 useTranslation() 声明,在 ModelQuotaCard 中直接使用会触发"undefined"错误。应在组件内调用 useTranslation() 获取 t 函数。

function ModelQuotaCard({ model }: { model: AntigravityModelQuota }) {
+  const { t } = useTranslation()
   const displayName = modelDisplayNames[model.name] || model.name
   const color = getQuotaColor(model.percentage)
web/src/pages/requests/detail/RequestSidebar.tsx (1)

105-106: 国际化不完整:存在硬编码字符串

第 105 行的 Request 字符串未翻译。同样,第 135 行的 Upstream Attempts 和第 172 行的 Attempt 也需要国际化处理。

🔧 建议修复
-              {getClientName(request.clientType as ClientType)} Request
+              {getClientName(request.clientType as ClientType)} {t('requests.request')}

第 135 行:

-          <Server size={12} /> Upstream Attempts
+          <Server size={12} /> {t('requests.upstreamAttempts')}

第 172 行:

-                      Attempt {index + 1}
+                      {t('requests.attempt')} {index + 1}
web/src/components/force-project-dialog.tsx (3)

234-240: 国际化不完整:拒绝按钮文本未使用翻译

第 234、239 行的 "拒绝中..." 和 "拒绝" 字符串仍为硬编码中文,应使用 t() 函数以保持与其他翻译一致。

🔧 建议修复
-                    <span className="text-sm font-bold">拒绝中...</span>
+                    <span className="text-sm font-bold">{t('sessions.rejecting')}</span>
-                    <span className="text-sm font-bold">拒绝</span>
+                    <span className="text-sm font-bold">{t('sessions.reject')}</span>

259-271: 国际化不完整:确认按钮文本未使用翻译

第 260、270 行的 "绑定中..." 和 "确认绑定" 字符串仍为硬编码中文。

🔧 建议修复
-                      <span className="text-sm font-bold text-white">
-                        绑定中...
-                      </span>
+                      <span className="text-sm font-bold text-white">
+                        {t('sessions.binding')}
+                      </span>
-                      <span className="text-sm font-bold text-amber-400 group-hover:text-white transition-colors">
-                        确认绑定
-                      </span>
+                      <span className="text-sm font-bold text-amber-400 group-hover:text-white transition-colors">
+                        {t('sessions.confirmBind')}
+                      </span>

278-281: 国际化不完整:提示信息未使用翻译

第 280 行的提示文本 "如果未在规定时间内选择项目,请求将被拒绝。" 应使用翻译函数。

🔧 建议修复
-              <p>如果未在规定时间内选择项目,请求将被拒绝。</p>
+              <p>{t('sessions.timeoutWarning')}</p>
web/src/pages/providers/components/kiro-provider-view.tsx (1)

65-74: QuotaCard 组件中的 t 未定义。
该组件在第 73 行使用了 t('proxy.resetsIn')formatResetDays(quota.days_until_reset, t),但 t 在组件内未被定义,会导致运行时 ReferenceError。需在 QuotaCard 内调用 useTranslation() 来获取 t

🛠️ 建议修改
 function QuotaCard({ quota }: { quota: KiroQuotaData }) {
+  const { t } = useTranslation()
   const percentage = quota.total_limit > 0
     ? Math.round((quota.available / quota.total_limit) * 100)
     : 0
web/src/components/layout/nav-proxy-status.tsx (1)

66-78: 按钮 title 硬编码为英文,与中文界面混用。

应使用已有的 proxy.clickToCopyTooltip 翻译键进行本地化处理。该翻译键已支持 {{url}} 参数。

🛠️ 建议修改
-      title={`Click to copy: ${fullUrl}`}
+      title={t('proxy.clickToCopyTooltip', { url: fullUrl })}
web/src/main.tsx (1)

12-46: 修复 ErrorFallback 中的 Hook 违规问题。

ErrorFallback 包含 useTranslation() 钩子但被作为普通函数调用。TransportProvidercontext.tsx 第 109 行调用 errorFallback(state.error) 时,会违反 React Rules of Hooks,导致运行时错误。

建议改为组件方式调用:

  1. 修改 ErrorFallback 接收 error 作为 props:
    function ErrorFallback({ error }: { error: Error }) {
      const { t } = useTranslation();
      // ...
    }
  2. main.tsx 中传递为组件:errorFallback={<ErrorFallback error={...} />} 或修改 TransportProvider 的类型定义支持组件方式。
web/src/components/cooldown-details-dialog.tsx (1)

21-21: 移除未使用的 CooldownReason 导入以修复 ESLint 失败

CI 已报 CooldownReason 未使用(no-unused-vars),会阻断合并。

🧹 建议修改
-import type { Cooldown, CooldownReason } from '@/lib/transport/types'
+import type { Cooldown } from '@/lib/transport/types'
web/src/pages/providers/components/provider-card.tsx (1)

132-152: CustomProviderCard 中缺少 useTranslation,导致 t 未定义

CustomProviderCard 组件在第 151、176、228、235 行多处使用 t(...) 进行国际化,但组件内未调用 useTranslation() 钩子,导致运行时会抛出"t is not defined"错误。

🛠️ 建议修改
 export function CustomProviderCard({
   provider,
   onClick,
   streamingCount,
 }: ProviderCardProps) {
+  const { t } = useTranslation()
   const {
     getCooldownForProvider,
     formatRemaining,
     clearCooldown,
     isClearingCooldown,
   } = useCooldowns()
🤖 Fix all issues with AI agents
In `@web/src/components/cooldown-details-dialog.tsx`:
- Line 2: 删除未使用的类型导入 CooldownReason,并把 getReasonInfo 的 t 参数类型从 (key: string) =>
string 改为 i18next 的 TFunction(引入或使用 useTranslation 提供的 TFunction 类型),然后在
getReasonInfo 内使用 i18next 的 defaultValue 对象形式(即传入 t(key, { defaultValue: '...'
}) 而非第二个 string 参数);确保相关导入 useTranslation/ TFunction 一并调整并移除多余的 CooldownReason
引入以消除 ESLint 错误。

In `@web/src/components/provider-details-dialog.tsx`:
- Line 2: getReasonInfo currently types its t parameter as (key: string) =>
string which disallows passing the second options argument; change the type to
TFunction from react-i18next (import it alongside useTranslation) and update
calls inside getReasonInfo to pass the defaultValue via the second argument
(e.g. t('key', { defaultValue: '...' })) so TypeScript accepts the two-argument
form and default copy is provided.

In `@web/src/components/ui/model-input.tsx`:
- Around line 130-138: The default parameter for placeholder currently calls
t('modelInput.selectOrEnter') before t is defined, causing a runtime error;
update the ModelInput component by removing the placeholder default that invokes
t and instead compute the placeholder inside the function body after const { t }
= useTranslation() (e.g., derive a local placeholderValue = placeholder ??
t('modelInput.selectOrEnter')) and use that value throughout the component;
reference the ModelInput function and the t symbol when making this change.

In `@web/src/lib/transport/http-transport.ts`:
- Around line 172-174: batchUpdateRoutePositions 使用了 RoutePositionUpdate 类型但未从
types.ts 导入,导致 TS 编译失败;修复方法是在该文件的导入列表中添加 RoutePositionUpdate(从 types.ts 导出),以便
batchUpdateRoutePositions(updates: RoutePositionUpdate[]) 能正确解析类型(定位符:函数名
batchUpdateRoutePositions、类型名 RoutePositionUpdate、来源 types.ts)。

In `@web/src/lib/transport/interface.ts`:
- Line 69: batchUpdateRoutePositions uses the type RoutePositionUpdate which is
not imported; fix by adding RoutePositionUpdate to the existing import list at
the top of the file (the import block that brings in other route-related types)
and import it from the module where RoutePositionUpdate is defined so the
batchUpdateRoutePositions signature compiles correctly.

In `@web/src/locales/en.json`:
- Around line 348-373: The file has duplicated top-level keys (e.g., "routes",
"requests", "routingStrategies", "projects", "sessions", "common") which causes
later entries to overwrite earlier ones; open en.json and merge all fragments
for each duplicated key into a single object per key (for example combine the
"routes" object entries like "modelMapping", "modelMappingHelp", "update",
"create", "customRoutes", etc. into one "routes" object), deduplicate any
repeated nested keys while preserving all unique translation strings, remove the
extra repeated top-level blocks, and validate the resulting JSON to ensure no
duplicate keys remain.
- Around line 375-386: The i18n interpolation in the locale entries uses JS
template syntax instead of the i18n placeholder format, so update the values for
the "requestId" and "attemptId" keys to use the i18n interpolation syntax
(replace "Request #${id}" with "Request #{{id}}" and "Attempt #${id}" with
"Attempt #{{id}}") so the runtime will substitute the id correctly; locate and
edit the "requestId" and "attemptId" entries in the JSON to apply these changes.

In `@web/src/locales/zh.json`:
- Around line 239-464: The file contains duplicate top-level keys (e.g.,
"routes", "requests", "routingStrategies", "projects", "sessions", "common")
which causes earlier translations to be overwritten; locate each repeated key
(such as the second "routes" block and the later "requests",
"routingStrategies", "projects", "sessions", "common") and merge their
properties into the original objects instead of re-declaring the keys, removing
the duplicate key blocks and resolving any property conflicts by keeping unique
translations or combining entries, then validate the JSON to ensure a single
definition per top-level key.
- Around line 375-386: Replace the non-i18next interpolation placeholders
"${id}" used in the translation values for keys like "requestId" and "attemptId"
with i18next-style "{{id}}"; update the strings "请求 #${id}" and "尝试 #${id}" to
use "请求 #{{id}}" and "尝试 #{{id}}" so i18next will interpolate the id correctly.

In `@web/src/pages/api-tokens/index.tsx`:
- Line 2: Replace hardcoded English "Project #" strings in
web/src/pages/api-tokens/index.tsx (occurrences around lines ~2, ~50, and
~164-168) with a translation key via useTranslation (e.g., t('project.label') or
similar) and update the locale JSONs to include that key and localized values;
ensure you import/use useTranslation in the component where "Project #" appears
and update any JSX or helper functions that build those labels to call t(...)
instead of the literal string so translations will be used consistently.

In `@web/src/pages/client-routes/components/provider-row.tsx`:
- Line 25: The runtime failure is caused by t being out of scope: call
useTranslation() inside the ProviderRowContent component to obtain the t
function, remove the unused t declaration from SortableProviderRow, and update
formatResetTime to accept t as a parameter (e.g., formatResetTime(t, ...)) then
pass that t from ProviderRowContent into formatResetTime wherever it's called
(also update any call sites inside this file to pass the t argument).

In `@web/src/pages/projects/index.tsx`:
- Around line 112-148: The CardHeader currently renders project.slug twice
inside the same row (two sibling divs with class "flex items-center gap-1.5
text-xs font-mono ..." each containing <Hash /> and {project.slug}); remove the
duplicate by deleting one of those sibling divs so only a single slug badge
remains, keeping the remaining badge inside the parent container (the div with
class "flex items-center gap-3 mb-2") and ensuring layout/styles are preserved.

In `@web/src/pages/providers/components/provider-row.tsx`:
- Around line 234-236: The component calls formatResetTime(claudeInfo.resetTime,
t) and formatKiroResetDays(kiroInfo.resetDays, t) but `t` is not defined; import
and call useTranslation to get the translation function and pass it as `t` to
those calls. Specifically, add an import for useTranslation from the i18n
library used in the project, call const { t } = useTranslation() (or equivalent
hook) inside the component where formatResetTime and formatKiroResetDays are
used, and replace the undefined `t` reference so both
formatResetTime(claudeInfo.resetTime, t) and
formatKiroResetDays(kiroInfo.resetDays, t) receive the proper (key: string) =>
string function.

In `@web/src/pages/requests/index.tsx`:
- Around line 146-166: The Token column header is hard-coded as "Token" and
should use the translation helper like the other headers; update the TableHead
that currently contains the literal "Token" to call the i18n function (e.g.,
replace "Token" with t('requests.token') or the correct translation key) so it
matches the pattern used in the other TableHead elements (TableHead components
around the Token header).
🧹 Nitpick comments (11)
web/src/pages/providers/components/clients-config-section.tsx (2)

14-14: 缺少分号。

此行缺少语句末尾的分号,与代码库中其他地方的风格不一致。

🔧 建议修复
-  const { t } = useTranslation()
+  const { t } = useTranslation();

42-49: 国际化覆盖不完整:标签未翻译。

placeholder 已使用 t('common.default') 进行国际化,但第 43 行的 "Endpoint Override" 标签仍然是硬编码的英文字符串。作为 i18n 改造的一部分,建议将标签也进行翻译以保持一致性。

♻️ 建议修复
                  <label className="text-xs font-medium text-muted-foreground block mb-1.5 uppercase tracking-wide">
-                     Endpoint Override
+                     {t('providers.endpointOverride')}
                  </label>

请确保在相应的翻译文件中添加 providers.endpointOverride 键值。

web/src/pages/providers/components/provider-edit-flow.tsx (1)

193-211: i18n 覆盖不完整:仍有多处硬编码字符串。

以下 UI 文本尚未使用 t() 进行国际化:

  • 第 194 行: "Edit Provider"
  • 第 197 行: "Update your custom provider settings"
  • 第 207 行: "Delete"
  • 第 210 行: "Cancel"
  • 以及后续的表单标签、描述文字、错误信息和删除确认弹窗内容

建议保持一致性,将所有用户可见文本都纳入 i18n 范围。

web/src/pages/projects/tabs/overview.tsx (1)

87-100: 建议:日期格式化应与 i18n 语言环境保持一致。

当前使用 toLocaleString() 不带参数,会使用浏览器默认语言环境。为了与应用的国际化设置保持一致,建议使用 i18next 的当前语言作为 locale 参数。

♻️ 建议的改进方案
+import { useTranslation } from 'react-i18next';
+
 export function OverviewTab({ project }: OverviewTabProps) {
-  const { t } = useTranslation();
+  const { t, i18n } = useTranslation();
   // ...
   
   <div className="grid grid-cols-2 gap-4 text-sm">
     <div>
       <span className="text-text-secondary">{t('projects.created')}</span>{' '}
       <span className="text-text-primary">
-        {new Date(project.createdAt).toLocaleString()}
+        {new Date(project.createdAt).toLocaleString(i18n.language)}
       </span>
     </div>
     <div>
       <span className="text-text-secondary">{t('projects.updated')}</span>{' '}
       <span className="text-text-primary">
-        {new Date(project.updatedAt).toLocaleString()}
+        {new Date(project.updatedAt).toLocaleString(i18n.language)}
       </span>
     </div>
   </div>
web/src/pages/routing-strategies/index.tsx (2)

84-86: “Global” 仍有硬编码入口,建议统一使用同一翻译键。
Line 85 已改为 t('common.global'),但下拉选项仍显示 “Global”,可能导致语言不一致;建议复用同一 key。


165-205: 表格区翻译完成,但同页仍有硬编码英文。
例如空状态 “No strategies”、页面标题/按钮、表单标题/标签、删除确认提示等未走 t(...),建议补齐以免 i18n 覆盖不全。

web/src/pages/providers/components/antigravity-provider-view.tsx (1)

47-69: 时间单位仍硬编码,i18n 不完整。
目前仅对空值/过期文案做了翻译,d/h/m 仍是英文;建议使用翻译 key(含复数/插值)或 Intl.DurationFormat 统一本地化。

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

127-127: 为 dragHandleListeners 提供明确类型并核对 dnd-kit 接入

dragHandleListeners?: any 会丢失类型约束,建议改为 dnd-kit 的监听器类型(如 DraggableSyntheticListeners)。另外,拖拽把手现在只绑定 listeners,建议再确认 dnd-kit 对 drag handle 的推荐绑定方式(listeners / attributes / activator)是否满足你们的键盘与无障碍需求。

♻️ 建议改进
 import { useSortable } from '@dnd-kit/sortable'
+import type { DraggableSyntheticListeners } from '@dnd-kit/core'
@@
 type ProviderRowContentProps = {
@@
-  dragHandleListeners?: any
+  dragHandleListeners?: DraggableSyntheticListeners
 }

Also applies to: 162-162, 214-214, 301-304

web/src/pages/requests/detail/RequestSidebar.tsx (1)

40-53: 代码重复:EmptyState 组件已存在

此本地 EmptyState 组件与 web/src/pages/requests/detail/components/EmptyState.tsx 中的实现完全相同。建议删除本地定义,直接从 components 目录导入复用。

♻️ 建议修复
+import { EmptyState } from './components'

然后删除第 40-53 行的本地定义。

internal/repository/sqlite/route.go (1)

70-95: 建议检测 RowsAffected 以避免静默失败。
当前 Exec 可能在 id 不存在时返回 0 行更新但不报错;批量接口更容易被误判为成功。

♻️ 建议改动(检测受影响行)
 import (
 	"database/sql"
+	"fmt"
 	"time"
 
 	"github.com/awsl-project/maxx/internal/domain"
 )
@@
 	for _, update := range updates {
-		if _, err := stmt.Exec(update.Position, now, update.ID); err != nil {
-			return err
-		}
+		result, err := stmt.Exec(update.Position, now, update.ID)
+		if err != nil {
+			return err
+		}
+		rows, err := result.RowsAffected()
+		if err != nil {
+			return err
+		}
+		if rows == 0 {
+			return fmt.Errorf("route not found: %d", update.ID)
+		}
 	}
web/src/components/routes/ClientTypeRoutesContent.tsx (1)

170-210: 建议补齐乐观更新的快照回滚与 onSettled 刷新。
当前仅在失败时 invalidate,缺少快照回滚;并发请求或服务端规范化时可能导致 UI 短暂错位或残留旧序。

♻️ 建议的回滚/刷新处理
-      // 乐观更新:立即更新本地缓存
+      // 乐观更新:立即更新本地缓存
+      const previousRoutes =
+        queryClient.getQueryData<typeof allRoutes>(routeKeys.list())
+      void queryClient.cancelQueries({ queryKey: routeKeys.list() })
       queryClient.setQueryData(routeKeys.list(), (oldRoutes: typeof allRoutes) => {
         if (!oldRoutes) return oldRoutes
         return oldRoutes.map(route => {
           const newPosition = updates[route.id]
           if (newPosition !== undefined) {
             return { ...route, position: newPosition }
           }
           return route
         })
       })

       // 发送 API 请求
       updatePositions.mutate(updates, {
         onError: () => {
-          // 失败时回滚:重新获取服务器数据
-          queryClient.invalidateQueries({ queryKey: routeKeys.list() })
+          // 失败时回滚:恢复快照
+          if (previousRoutes) {
+            queryClient.setQueryData(routeKeys.list(), previousRoutes)
+          }
         },
+        onSettled: () => {
+          queryClient.invalidateQueries({ queryKey: routeKeys.list() })
+        },
       })
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 462f46f and da2d46e.

📒 Files selected for processing (47)
  • internal/domain/model.go
  • internal/handler/admin.go
  • internal/repository/cached/route.go
  • internal/repository/interfaces.go
  • internal/repository/sqlite/route.go
  • internal/service/admin.go
  • web/src/App.tsx
  • web/src/components/cooldown-details-dialog.tsx
  • web/src/components/force-project-dialog.tsx
  • web/src/components/layout/header.tsx
  • web/src/components/layout/nav-proxy-status.tsx
  • web/src/components/provider-details-dialog.tsx
  • web/src/components/routes/ClientTypeRoutesContent.tsx
  • web/src/components/ui/model-input.tsx
  • web/src/hooks/queries/use-routes.ts
  • web/src/lib/transport/http-transport.ts
  • web/src/lib/transport/index.ts
  • web/src/lib/transport/interface.ts
  • web/src/lib/transport/types.ts
  • web/src/locales/en.json
  • web/src/locales/zh.json
  • web/src/main.tsx
  • web/src/pages/api-tokens/index.tsx
  • web/src/pages/client-routes/components/provider-row.tsx
  • web/src/pages/projects/index.tsx
  • web/src/pages/projects/tabs/overview.tsx
  • web/src/pages/projects/tabs/routes.tsx
  • web/src/pages/projects/tabs/sessions.tsx
  • web/src/pages/providers/components/antigravity-provider-view.tsx
  • web/src/pages/providers/components/clients-config-section.tsx
  • web/src/pages/providers/components/kiro-provider-view.tsx
  • web/src/pages/providers/components/kiro-token-import.tsx
  • web/src/pages/providers/components/model-mapping-editor.tsx
  • web/src/pages/providers/components/provider-card.tsx
  • web/src/pages/providers/components/provider-create-flow.tsx
  • web/src/pages/providers/components/provider-edit-flow.tsx
  • web/src/pages/providers/components/provider-row.tsx
  • web/src/pages/providers/index.tsx
  • web/src/pages/requests/detail/RequestDetailPanel.tsx
  • web/src/pages/requests/detail/RequestDetailView.tsx
  • web/src/pages/requests/detail/RequestSidebar.tsx
  • web/src/pages/requests/detail/components/CopyButton.tsx
  • web/src/pages/requests/index.tsx
  • web/src/pages/retry-configs/index.tsx
  • web/src/pages/routes/form.tsx
  • web/src/pages/routing-strategies/index.tsx
  • web/src/pages/sessions.tsx
🧰 Additional context used
🧬 Code graph analysis (24)
web/src/lib/transport/types.ts (2)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/index.ts (1)
  • RoutePositionUpdate (19-19)
web/src/lib/transport/http-transport.ts (3)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/index.ts (1)
  • RoutePositionUpdate (19-19)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
internal/repository/cached/route.go (4)
internal/repository/interfaces.go (1)
  • RouteRepository (13-23)
internal/repository/sqlite/route.go (1)
  • RouteRepository (10-12)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
web/src/pages/providers/components/clients-config-section.tsx (4)
web/src/pages/providers/types.ts (1)
  • ClientConfig (137-142)
internal/domain/model.go (1)
  • ClientType (6-6)
web/src/lib/transport/types.ts (1)
  • ClientType (8-8)
web/src/lib/theme.ts (1)
  • ClientType (25-25)
web/src/pages/requests/detail/RequestSidebar.tsx (2)
web/src/pages/requests/detail/components/EmptyState.tsx (1)
  • EmptyState (8-15)
web/src/pages/requests/detail/components/index.ts (1)
  • EmptyState (5-5)
internal/service/admin.go (2)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
web/src/pages/providers/components/provider-create-flow.tsx (3)
web/src/components/ui/button.tsx (1)
  • Button (58-58)
web/src/components/ui/index.ts (2)
  • Button (2-2)
  • Input (30-30)
web/src/components/ui/input.tsx (1)
  • Input (20-20)
web/src/pages/requests/detail/RequestDetailView.tsx (1)
web/src/pages/requests/detail/components/EmptyState.tsx (1)
  • EmptyState (8-15)
web/src/pages/requests/detail/RequestDetailPanel.tsx (2)
web/src/pages/requests/detail/components/CopyButton.tsx (1)
  • CopyButton (11-45)
web/src/pages/requests/detail/components/EmptyState.tsx (1)
  • EmptyState (8-15)
web/src/lib/transport/interface.ts (2)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
web/src/components/layout/header.tsx (1)
web/src/components/theme-provider.tsx (1)
  • useTheme (73-80)
internal/repository/sqlite/route.go (2)
internal/repository/interfaces.go (1)
  • RouteRepository (13-23)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
internal/repository/interfaces.go (3)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/index.ts (1)
  • RoutePositionUpdate (19-19)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
web/src/pages/providers/components/model-mapping-editor.tsx (1)
web/src/components/ui/model-input.tsx (1)
  • ModelInput (130-387)
web/src/pages/api-tokens/index.tsx (6)
web/src/components/ui/badge.tsx (1)
  • Badge (59-59)
web/src/components/ui/index.ts (4)
  • Badge (27-27)
  • Button (2-2)
  • Card (6-6)
  • Input (30-30)
web/src/components/ui/button.tsx (1)
  • Button (58-58)
web/src/lib/transport/http-transport.ts (1)
  • updateSetting (301-304)
web/src/components/ui/card.tsx (1)
  • Card (96-96)
web/src/components/ui/input.tsx (1)
  • Input (20-20)
web/src/pages/providers/components/provider-card.tsx (1)
web/src/components/icons/client-icons.tsx (1)
  • ClientIcon (65-100)
web/src/pages/projects/tabs/routes.tsx (1)
web/src/components/icons/client-icons.tsx (1)
  • getClientName (51-53)
web/src/pages/requests/detail/components/CopyButton.tsx (1)
web/src/pages/requests/detail/components/index.ts (1)
  • CopyButton (1-1)
internal/handler/admin.go (3)
internal/domain/model.go (1)
  • RoutePositionUpdate (156-159)
web/src/lib/transport/index.ts (1)
  • RoutePositionUpdate (19-19)
web/src/lib/transport/types.ts (1)
  • RoutePositionUpdate (102-105)
web/src/components/layout/nav-proxy-status.tsx (1)
web/src/components/layout/index.ts (1)
  • NavProxyStatus (7-7)
web/src/pages/sessions.tsx (3)
web/src/components/ui/index.ts (1)
  • TableHead (20-20)
web/src/components/ui/table.tsx (1)
  • TableHead (95-95)
web/src/lib/transport/http-transport.ts (1)
  • updateSessionProject (183-189)
web/src/pages/projects/tabs/sessions.tsx (3)
web/src/lib/transport/types.ts (1)
  • Project (60-67)
web/src/pages/projects/tabs/index.ts (1)
  • SessionsTab (3-3)
web/src/components/ui/index.ts (1)
  • TableHead (20-20)
web/src/components/routes/ClientTypeRoutesContent.tsx (2)
web/src/lib/query-client.ts (1)
  • queryClient (3-14)
web/src/hooks/queries/use-routes.ts (1)
  • routeKeys (9-15)
web/src/components/ui/model-input.tsx (1)
web/src/components/ui/index.ts (1)
  • DialogTitle (45-45)
🪛 Biome (2.1.2)
web/src/locales/zh.json

[error] 257-258: The key routingStrategies was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🪛 GitHub Actions: Lint
web/src/components/cooldown-details-dialog.tsx

[error] 21-21: ESLint: 'CooldownReason' is defined but never used. (no-unused-vars) @typescript-eslint/no-unused-vars

🪛 GitHub Check: lint
web/src/pages/client-routes/components/provider-row.tsx

[failure] 72-72:
't' is assigned a value but never used

🔇 Additional comments (82)
web/src/pages/projects/tabs/sessions.tsx (2)

5-5: LGTM!

useTranslation hook 的导入和使用方式正确。

Also applies to: 12-12


29-31: LGTM!

页面标题、描述和表头的国际化处理正确,翻译键命名规范清晰。

Also applies to: 40-43

web/src/pages/retry-configs/index.tsx (5)

104-106: 加载态文本样式更新无问题。

仅是文案颜色 token 调整,符合主题迁移方向。


121-125: 标题与描述文字 token 调整 OK。

保持层级与语义一致,样式更清晰。


179-183: 重试次数说明区域样式更新合理。

文本主次层级清楚,无功能影响。

Also applies to: 196-196


205-209: 时间参数区的标签/说明文案样式一致性良好。

统一为 text-text-* 体系,符合整体迁移。

Also applies to: 228-233, 252-256


276-282: 运行时展示区文案 token 更新 OK。

颜色层级与强调信息更一致。

Also applies to: 286-287, 301-303, 309-311

web/src/pages/sessions.tsx (11)

53-57: 标题与副标题语义化文本色使用合理
与主题/ i18n 的文本层级一致,观感更统一。


74-84: 表头文本颜色统一为语义化 token
表头层级清晰且一致。


112-113: 未分配状态文本样式更清晰
text-text-muted + italic 的语义对齐很好。


213-216: 弹窗标题/副标题语义化样式 OK
与主页面风格一致。


233-237: Session ID 标题和值样式一致性提升
层级与可读性更好。


243-244: Created 标签语义化文本色一致
视觉层级更稳定。


253-255: 项目绑定标签样式统一
与其它 label 的语义化规则对齐。


264-266: 选中/未选中按钮主题色切换清晰
主色态与非选中态辨识度更高。


280-282: 项目按钮选中态样式一致且明确
主色方案与整体主题统一。


289-290: 提示文案的语义化弱化样式合适
辅助信息更柔和、不抢焦点。


299-299: i18n 调用格式更简洁
可读性提升,逻辑保持一致。

web/src/pages/providers/components/provider-edit-flow.tsx (2)

3-3: LGTM!

useTranslation 的引入和在 ProviderEditFlow 组件中的使用方式正确。

Also applies to: 36-36


217-225: LGTM!

保存按钮的三态文本(saving / saved / default)正确使用了 i18n 翻译函数。

web/src/pages/projects/tabs/overview.tsx (4)

8-8: LGTM!

useTranslation 的导入和初始化正确,符合 react-i18next 的最佳实践。

Also applies to: 15-15


59-84: LGTM!

项目信息部分的国际化实现正确,翻译键命名遵循一致的命名空间模式 (projects.*),CSS 类名更新与设计系统保持一致。


102-114: LGTM!

保存按钮的国际化实现正确,按钮逻辑保持不变。


117-146: LGTM!

代理配置部分的国际化实现正确,复制功能保持不变,CSS 类名与设计系统一致。

web/src/components/layout/header.tsx (2)

4-12: i18n 接入与样式调整整体 OK。
变更清晰、实现符合预期。


12-12: 翻译键 app.title 已在 web/src/locales/en.json 和 web/src/locales/zh.json 中定义,值为 "Maxx Next",无需修改。

Likely an incorrect or invalid review comment.

web/src/components/ui/model-input.tsx (2)

3-3: 引入 useTranslation 没问题
新增的 i18n hook 导入符合预期。


292-292: i18n 文案替换一致且清晰
这些文本已统一走翻译键,符合整体国际化方向。

Also applies to: 306-306, 354-371, 376-382

web/src/pages/routing-strategies/index.tsx (2)

24-28: useTranslation 引入 OK,但请确认 i18n 已在根部初始化。
如果未通过 I18nextProvider / initReactI18next 配置,t 会回退为 key 文本。请确认应用入口已完成初始化。


145-157: 国际化 keys 已在所有语言包中完整定义。

已确认 common.cancelcommon.savingroutes.updateroutes.create 这四个 key 均存在于英文和简体中文的语言包中,不存在缺失导致显示 key 的问题。

web/src/pages/providers/components/antigravity-provider-view.tsx (3)

13-13: 新增 i18n 导入无问题。
若该页面可能被分包/懒加载,请确认该入口已完成 i18n 初始化覆盖。


127-127: 在容器组件内获取 t 的方式 OK。
当前用法清晰、易读。


360-369: 保存按钮状态文案已完成本地化。
状态切换逻辑清晰。

web/src/pages/providers/components/provider-create-flow.tsx (1)

3-3: LGTM!

国际化实现正确。useTranslation 钩子使用得当,所有 UI 字符串都已正确替换为翻译键。

Also applies to: 25-25

internal/domain/model.go (1)

155-159: LGTM!

RoutePositionUpdate 结构体定义清晰,JSON 标签正确,与前端类型定义一致。

web/src/components/layout/nav-proxy-status.tsx (2)

11-14: 引入 useTranslation 的改动没问题。


55-59: 折叠态 Tooltip 文案已完成本地化。

web/src/pages/providers/components/provider-row.tsx (2)

63-70: 重置时间的本地化处理清晰。


88-90: Kiro 重置天数的本地化逻辑 OK。

web/src/pages/providers/index.tsx (3)

121-121: Header 描述文本改动无问题。


159-159: 加载态样式调整 OK。


222-224: 导入完成提示文本改动 OK。

web/src/pages/providers/components/kiro-provider-view.tsx (3)

13-13: i18n Hook 集成到页面组件 OK。

Also applies to: 135-135


36-39: 重置天数字符串本地化逻辑 OK。


362-369: 保存状态文案本地化处理 OK。

web/src/App.tsx (1)

21-29: 加载态文案替换无问题。

逻辑未变,文本已可本地化,OK。

web/src/pages/providers/components/model-mapping-editor.tsx (1)

21-75: 占位符已接入翻译,OK。

修改仅限文案替换,不影响逻辑。

web/src/pages/providers/components/kiro-token-import.tsx (1)

29-56: 错误提示已本地化,逻辑保持不变。

改动安全,OK。

web/src/components/provider-details-dialog.tsx (1)

2-2: i18n 文案替换与 locale 格式化处理良好

整体替换一致,时间格式化也绑定了当前语言环境,符合预期。

Also applies to: 147-148, 177-184, 191-191, 218-218, 348-375, 388-388

web/src/pages/providers/components/provider-card.tsx (1)

27-29: Antigravity 相关文案 i18n 替换与样式微调 OK

文本替换保持一致,非冷却状态背景由 bg-accent 调整为 bg-muted 也更统一。

Also applies to: 59-59, 111-118, 201-202

web/src/components/cooldown-details-dialog.tsx (1)

89-90: i18n 注入与时间格式化适配良好

语言环境接入与关键文案替换整体一致,倒计时时间格式化也与当前语言对齐。

Also applies to: 121-128, 157-159, 264-265, 281-282, 286-286

web/src/lib/transport/types.ts (1)

102-105: 新增 RoutePositionUpdate 类型定义清晰

结构简单明确,便于批量位置更新传输。

web/src/lib/transport/index.ts (1)

19-19: 导出 RoutePositionUpdate 没问题

公共类型导出补齐,符合预期。

internal/service/admin.go (1)

206-208: 服务层委托实现清晰。
直接调用 routeRepo.BatchUpdatePositions,与其他 Route API 的风格一致。

internal/repository/cached/route.go (1)

76-82: 批量更新后刷新缓存的处理合理。
更新后调用 Load() 以保持缓存与数据库一致。

web/src/hooks/queries/use-routes.ts (1)

97-102: 批量 payload 构造清楚且与新 API 对齐。
Record 转换为 { id, position }[] 并调用批量接口,逻辑简洁。

web/src/pages/routes/form.tsx (2)

2-2: 引入 useTranslation 并初始化 t 的做法合适。
请确认该页面加载了对应的 i18n 资源/命名空间,避免运行期缺失。

Also applies to: 26-26


90-186: 请确认新增的翻译 key 在所有语言包中存在。
否则 UI 可能直接显示 key 文本而非期望文案。

web/src/pages/projects/tabs/routes.tsx (3)

20-20: 引入 i18n hook 没问题。
useTranslation 的引入与本文件的文案替换一致。


45-66: 标题区文案已完成本地化替换。
t(...) 结合 count 的呈现清晰,语义也更直观。


81-86: 空状态文案本地化到位。
使用 client 占位符可读性好。

internal/handler/admin.go (2)

51-55: 路由分发到 batch-positions 端点清晰。
路径分支逻辑易读且不影响现有 routes 处理。


325-344: 批量更新处理流程完整。
方法校验、解码、调用 service 与响应都覆盖到位。

web/src/pages/requests/detail/RequestDetailView.tsx (5)

13-13: i18n hook 接入正确。
后续文案替换依赖该 hook 的使用方式一致。

Also applies to: 46-46


70-93: 请求头与标签页文案已本地化。
对齐 requests.* 的键值使用规范。


120-123: Copy 按钮文案统一为通用翻译键。
便于后续复用与统一维护。

Also applies to: 146-157


185-186: 空状态提示已使用翻译键。
两处 EmptyState 文案保持一致性很好。

Also applies to: 282-283


217-221: 响应区 Copy 文案已本地化。
与请求区保持一致体验。

Also applies to: 243-254

internal/repository/interfaces.go (1)

21-22: 新增批量更新接口已完整同步。

已确认所有 RouteRepository 实现均已同步更新:

  • SQLite 实现(internal/repository/sqlite/route.go:70-95):完整的事务处理逻辑
  • Cached 实现(internal/repository/cached/route.go:76-82):正确委托并重加载缓存

接口定义与所有实现方法对齐完整,无遗漏。

web/src/pages/requests/detail/components/CopyButton.tsx (1)

4-12: 文案回退与复制状态提示清晰。
未传入 label 时自动回退到默认文案,行为一致。

Also applies to: 35-40

web/src/pages/projects/index.tsx (1)

79-86: 表单标签层级调整 OK。

web/src/pages/api-tokens/index.tsx (4)

180-247: 页面头部与鉴权状态文案替换后更一致。


255-374: 表格与空状态文案替换到位。


389-649: 创建/编辑/删除及新 Token 弹窗文案统一。


659-695: 项目选择弹窗文案替换 OK。

web/src/pages/requests/index.tsx (3)

132-139: 空状态颜色调整无误。


220-237: 分页栏样式与文案调整清晰。


480-482: 客户端徽标颜色调整 OK。

web/src/pages/requests/detail/RequestDetailPanel.tsx (2)

13-13: 空状态与尝试编号文案已统一。

Also applies to: 72-99, 120-122


180-186: 对比按钮与请求/响应空状态文案替换一致。

Also applies to: 230-272, 302-303, 363-364

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

237-237: 把 items 显式映射为 id 列表是正确的(更符合 dnd-kit 预期)。
这能减少 DnD 内部比较的歧义,LGTM。


33-58: queryKey 一致性已验证,无需修改。

useRoutes() 的 queryKey 为 routeKeys.list(),其值为 ['routes', 'list'];所有变更操作(如 useCreateRouteuseUpdateRouteuseDeleteRoute 等)的 invalidation 使用 routeKeys.lists(),同样返回 ['routes', 'list']。两者完全一致,setQueryDatainvalidateQueries 会正确更新缓存。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@@ -1,4 +1,5 @@
import { useEffect, useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "cooldown-details-dialog.tsx" -type f

Repository: awsl-project/maxx

Length of output: 109


🏁 Script executed:

cat -n ./web/src/components/cooldown-details-dialog.tsx

Repository: awsl-project/maxx

Length of output: 12924


🏁 Script executed:

# 查找是否还有其他地方导入 CooldownReason 但未使用
rg "CooldownReason" --type tsx --type ts -A 3 -B 1

Repository: awsl-project/maxx

Length of output: 88


🏁 Script executed:

# 查找 CooldownReason 的使用情况
rg "CooldownReason" -A 3 -B 1

Repository: awsl-project/maxx

Length of output: 11192


🏁 Script executed:

# 查看 i18next 的 TFunction 定义和用法
rg "TFunction|useTranslation" -A 5 web/src --type-add "tsx:*.tsx" | head -100

Repository: awsl-project/maxx

Length of output: 4993


🏁 Script executed:

# 查看 provider-details-dialog.tsx 中 CooldownReason 的使用
cat -n web/src/components/provider-details-dialog.tsx | grep -A 20 -B 5 "CooldownReason"

Repository: awsl-project/maxx

Length of output: 1003


🏁 Script executed:

# 查看 provider-details-dialog.tsx 中是否实际使用了 CooldownReason
rg "CooldownReason" web/src/components/provider-details-dialog.tsx -A 10 -B 3

Repository: awsl-project/maxx

Length of output: 523


🏁 Script executed:

# 检查 i18next 的完整使用模式,查看其他地方如何处理默认值
rg "t\(" web/src/components -A 1 | grep -E "(defaultValue|,\s*['\"])" | head -20

Repository: awsl-project/maxx

Length of output: 1566


🏁 Script executed:

# 查看 provider-details-dialog.tsx 中 getReasonInfo 的完整实现
cat -n web/src/components/provider-details-dialog.tsx | grep -A 50 "getReasonInfo"

Repository: awsl-project/maxx

Length of output: 4180


🏁 Script executed:

# 验证 i18next 的 TFunction 类型和正确用法
cd web && npm list react-i18next i18next 2>/dev/null | head -10

Repository: awsl-project/maxx

Length of output: 106


🏁 Script executed:

# 查看 web 的 package.json 中的依赖
cat web/package.json | grep -A 20 '"dependencies"'

Repository: awsl-project/maxx

Length of output: 711


🏁 Script executed:

# 检查 cooldown-details-dialog.tsx 中 CooldownReason 是否被使用(除了导入)
grep -n "CooldownReason" web/src/components/cooldown-details-dialog.tsx

Repository: awsl-project/maxx

Length of output: 133


🏁 Script executed:

# 验证 i18next 文档中 TFunction 的正确用法和 defaultValue 参数
python3 << 'EOF'
# i18next 的 t 函数支持的参数形式:
# t(key) - 返回翻译
# t(key, defaultValue) - 如果没有翻译,返回 defaultValue
# t(key, { defaultValue: '...' }) - 同上,但用对象形式
# 两种方式都支持,但第二种方式需要使用对象

# 根据 i18next 的文档,当传入第二个参数时:
# - 如果是字符串,则被视为 defaultValue
# - 如果是对象,则是选项对象,可以包含 defaultValue

# 因此,以下两种调用方式都有效:
# t('key', 'defaultValue')
# t('key', { defaultValue: 'defaultValue' })

print("i18next TFunction 支持两种默认值语法:")
print("1. t('key', 'defaultValue') - 简洁形式")
print("2. t('key', { defaultValue: 'defaultValue' }) - 对象形式")
print("\n实际上,第一种形式已经被广泛使用,效果相同。")
print("但使用对象形式时,类型定义需要是 TFunction 而不是 (key: string) => string")
EOF

Repository: awsl-project/maxx

Length of output: 147


🏁 Script executed:

# 确认 TypeScript 类型检查会对这个问题报错
# 查看是否有 tsconfig 或其他 TS 配置
find web -name "tsconfig.json" -o -name "vitest.config.ts" -o -name "vite.config.ts" | head -5

Repository: awsl-project/maxx

Length of output: 97


删除未使用的 CooldownReason 导入并修正 getReasonInfo 的 t 参数类型

此文件存在两个问题:

  1. 第 21 行导入的 CooldownReason 类型未被使用,导致 ESLint 报错。
  2. getReasonInfot 参数定义为单参函数 (key: string) => string,但实际调用时传入了默认值文案作为第二参数,这会导致 TypeScript 类型检查失败。

建议改为 TFunction 类型并使用 defaultValue 对象形式。

建议修改
-import type { Cooldown, CooldownReason } from '@/lib/transport/types'
+import type { TFunction } from 'i18next'
+import type { Cooldown } from '@/lib/transport/types'

-const getReasonInfo = (t: (key: string) => string) => ({
+const getReasonInfo = (t: TFunction) => ({
   server_error: {
     label: t('provider.reasons.serverError'),
-    description: t('provider.reasons.serverErrorDesc', '上游服务器返回 5xx 错误,系统自动进入冷却保护'),
+    description: t('provider.reasons.serverErrorDesc', { defaultValue: '上游服务器返回 5xx 错误,系统自动进入冷却保护' }),
     icon: Server,
     color: 'text-red-400',
     bgColor: 'bg-red-400/10 border-red-400/20',
   },
   network_error: {
     label: t('provider.reasons.networkError'),
-    description: t('provider.reasons.networkErrorDesc', '无法连接到上游服务器,可能是网络故障或服务器宕机'),
+    description: t('provider.reasons.networkErrorDesc', { defaultValue: '无法连接到上游服务器,可能是网络故障或服务器宕机' }),
     icon: Wifi,
     color: 'text-amber-400',
     bgColor: 'bg-amber-400/10 border-amber-400/20',
   },
   quota_exhausted: {
     label: t('provider.reasons.quotaExhausted'),
-    description: t('provider.reasons.quotaExhaustedDesc', 'API 配额已用完,等待配额重置'),
+    description: t('provider.reasons.quotaExhaustedDesc', { defaultValue: 'API 配额已用完,等待配额重置' }),
     icon: AlertCircle,
     color: 'text-red-400',
     bgColor: 'bg-red-400/10 border-red-400/20',
   },
   rate_limit_exceeded: {
     label: t('provider.reasons.rateLimitExceeded'),
-    description: t('provider.reasons.rateLimitExceededDesc', '请求速率超过限制,触发了速率保护'),
+    description: t('provider.reasons.rateLimitExceededDesc', { defaultValue: '请求速率超过限制,触发了速率保护' }),
     icon: Zap,
     color: 'text-yellow-400',
     bgColor: 'bg-yellow-400/10 border-yellow-400/20',
   },
   concurrent_limit: {
     label: t('provider.reasons.concurrentLimit'),
-    description: t('provider.reasons.concurrentLimitDesc', '并发请求数超过限制'),
+    description: t('provider.reasons.concurrentLimitDesc', { defaultValue: '并发请求数超过限制' }),
     icon: Ban,
     color: 'text-orange-400',
     bgColor: 'bg-orange-400/10 border-orange-400/20',
   },
   unknown: {
     label: t('provider.reasons.unknown'),
-    description: t('provider.reasons.unknownDesc', '因未知原因进入冷却状态'),
+    description: t('provider.reasons.unknownDesc', { defaultValue: '因未知原因进入冷却状态' }),
     icon: HelpCircle,
     color: 'text-muted-foreground',
     bgColor: 'bg-muted border-border',
   },
 })
🤖 Prompt for AI Agents
In `@web/src/components/cooldown-details-dialog.tsx` at line 2, 删除未使用的类型导入
CooldownReason,并把 getReasonInfo 的 t 参数类型从 (key: string) => string 改为 i18next 的
TFunction(引入或使用 useTranslation 提供的 TFunction 类型),然后在 getReasonInfo 内使用 i18next 的
defaultValue 对象形式(即传入 t(key, { defaultValue: '...' }) 而非第二个 string 参数);确保相关导入
useTranslation/ TFunction 一并调整并移除多余的 CooldownReason 引入以消除 ESLint 错误。

@@ -1,4 +1,5 @@
import { useEffect, useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

修正 getReasonInfo 的 t 类型以支持默认文案参数

当前 getReasonInfot 限定为 (key: string) => string,但实际传入了第二参数,TS 会直接报 “Expected 1 arguments”。建议改用 TFunction 并通过 defaultValue 传默认文案。

🛠️ 建议修改
-import { useTranslation } from 'react-i18next'
+import { useTranslation } from 'react-i18next'
+import type { TFunction } from 'i18next'

-const getReasonInfo = (t: (key: string) => string) => ({
+const getReasonInfo = (t: TFunction) => ({
   server_error: {
     label: t('provider.reasons.serverError'),
-    description: t('provider.reasons.serverErrorDesc', '上游服务器返回 5xx 错误,系统自动进入冷却保护'),
+    description: t('provider.reasons.serverErrorDesc', { defaultValue: '上游服务器返回 5xx 错误,系统自动进入冷却保护' }),
     icon: Server,
     color: 'text-rose-500 dark:text-rose-400',
     bgColor:
       'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25',
   },
   network_error: {
     label: t('provider.reasons.networkError'),
-    description: t('provider.reasons.networkErrorDesc', '无法连接到上游服务器,可能是网络故障或服务器宕机'),
+    description: t('provider.reasons.networkErrorDesc', { defaultValue: '无法连接到上游服务器,可能是网络故障或服务器宕机' }),
     icon: Wifi,
     color: 'text-amber-600 dark:text-amber-400',
     bgColor:
       'bg-amber-500/10 dark:bg-amber-500/15 border-amber-500/30 dark:border-amber-500/25',
   },
   quota_exhausted: {
     label: t('provider.reasons.quotaExhausted'),
-    description: t('provider.reasons.quotaExhaustedDesc', 'API 配额已用完,等待配额重置'),
+    description: t('provider.reasons.quotaExhaustedDesc', { defaultValue: 'API 配额已用完,等待配额重置' }),
     icon: AlertCircle,
     color: 'text-rose-500 dark:text-rose-400',
     bgColor:
       'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25',
   },
   rate_limit_exceeded: {
     label: t('provider.reasons.rateLimitExceeded'),
-    description: t('provider.reasons.rateLimitExceededDesc', '请求速率超过限制,触发了速率保护'),
+    description: t('provider.reasons.rateLimitExceededDesc', { defaultValue: '请求速率超过限制,触发了速率保护' }),
     icon: Zap,
     color: 'text-yellow-600 dark:text-yellow-400',
     bgColor:
       'bg-yellow-500/10 dark:bg-yellow-500/15 border-yellow-500/30 dark:border-yellow-500/25',
   },
   concurrent_limit: {
     label: t('provider.reasons.concurrentLimit'),
-    description: t('provider.reasons.concurrentLimitDesc', '并发请求数超过限制'),
+    description: t('provider.reasons.concurrentLimitDesc', { defaultValue: '并发请求数超过限制' }),
     icon: Ban,
     color: 'text-orange-600 dark:text-orange-400',
     bgColor:
       'bg-orange-500/10 dark:bg-orange-500/15 border-orange-500/30 dark:border-orange-500/25',
   },
   unknown: {
     label: t('provider.reasons.unknown'),
-    description: t('provider.reasons.unknownDesc', '因未知原因进入冷却状态'),
+    description: t('provider.reasons.unknownDesc', { defaultValue: '因未知原因进入冷却状态' }),
     icon: HelpCircle,
     color: 'text-muted-foreground',
     bgColor: 'bg-muted/50 border-border',
   },
 })

Also applies to: 51-100

🤖 Prompt for AI Agents
In `@web/src/components/provider-details-dialog.tsx` at line 2, getReasonInfo
currently types its t parameter as (key: string) => string which disallows
passing the second options argument; change the type to TFunction from
react-i18next (import it alongside useTranslation) and update calls inside
getReasonInfo to pass the defaultValue via the second argument (e.g. t('key', {
defaultValue: '...' })) so TypeScript accepts the two-argument form and default
copy is provided.

@@ -1,4 +1,5 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
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

项目回退名称仍是英文硬编码。
既然已统一文案来源,建议把 Project # 也改为翻译 key,并补充 locale。

建议修复(翻译回退文案)
-    return project?.name || `Project #${projectId}`
+    return project?.name || t('apiTokens.projectFallback', { id: projectId })

Also applies to: 50-50, 164-168

🤖 Prompt for AI Agents
In `@web/src/pages/api-tokens/index.tsx` at line 2, Replace hardcoded English
"Project #" strings in web/src/pages/api-tokens/index.tsx (occurrences around
lines ~2, ~50, and ~164-168) with a translation key via useTranslation (e.g.,
t('project.label') or similar) and update the locale JSONs to include that key
and localized values; ensure you import/use useTranslation in the component
where "Project #" appears and update any JSX or helper functions that build
those labels to call t(...) instead of the literal string so translations will
be used consistently.

lxw15337674 and others added 2 commits January 16, 2026 23:24
- Remove unused CooldownReason import from cooldown-details-dialog.tsx
- Remove unused CooldownReason import from provider-details-dialog.tsx
- Remove unused t and useTranslation from provider-row.tsx
- Remove unused ArrowRight import from projects/index.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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