refactor: 拆分 unified-edit-dialog 为专用对话框组件#539
Conversation
将 1120 行的 unified-edit-dialog.tsx 拆分为 4 个单一职责组件: - CreateUserDialog: 创建用户 + 首个密钥 - EditUserDialog: 编辑用户信息 - AddKeyDialog: 添加新密钥 - EditKeyDialog: 编辑单个密钥 主要改动: - 提取共享逻辑到 hooks/ (翻译、模型建议) - 提取工具函数到 utils/ (表单处理、provider group) - 新增 getModelSuggestionsByProviderGroup action - 修复 expiresAt 清除时传参问题 (使用空字符串) - 添加单元测试覆盖新组件 BREAKING CHANGE: 删除 unified-edit-dialog.tsx,引用需更新为新组件
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthrough本 PR 将统一的用户/密钥编辑对话框拆分为独立的创建/编辑组件,新增密钥创建成功显示与模型建议 API,并在多语言消息、表单逻辑、UI 交互和单元测试中做相应调整与补充。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Summary of ChangesHello @NieiR, 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! 本次拉取请求旨在通过将一个庞大且功能集中的对话框组件拆分为多个职责明确、易于维护的独立组件,显著提升用户和密钥管理界面的模块化和可维护性。此重构不仅优化了代码结构,还通过引入新的 hooks 和工具函数增强了逻辑复用,并添加了一个新的服务器端动作以支持模型建议功能。同时,还解决了一个关于过期时间参数传递的关键 bug,确保了数据处理的准确性。 Highlights
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
这次重构做得非常出色,成功地将一个庞大的 unified-edit-dialog.tsx 组件拆分成了四个职责单一的专用对话框组件。这极大地提升了代码的可读性和可维护性。将共享逻辑提取到自定义 Hooks 和工具函数中也是一个很好的实践。此外,本次提交还修复了 expiresAt 清除时的传参问题,并增加了单元测试,这对于保证代码质量至关重要。整体来看,这是一次高质量的重构。我发现了几处可以使用更严格的类型来代替 as any 的地方,以增强类型安全,具体请看我的评论。
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Code Review Summary
This is a well-structured refactoring PR that splits a large 1120-line unified dialog component into focused, single-responsibility components. The refactoring improves maintainability and includes comprehensive test coverage (18 tests). After thorough analysis across all review perspectives, no significant issues were identified that warrant inline comments.
PR Size: XL
- Lines changed: 3783 (2559 additions + 1224 deletions)
- Files changed: 24
Split Suggestions for XL PR:
While the PR is large, the changes are cohesive and splitting would be impractical as:
- The new components depend on each other and shared utilities
- Removing the old
unified-edit-dialog.tsxrequires all new components to be present - The integration changes in
users-page-client.tsxanduser-management-table.tsxdepend on the new components
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 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Review Notes
Positive observations (not for praise, but for context):
-
Error Handling: All catch blocks properly log errors via
console.error()with descriptive prefixes (e.g.,[CreateUserDialog]) and show user feedback viatoast.error(). No silent failures detected. -
Rollback Logic: The
CreateUserDialogincludes proper rollback when key creation fails after user creation (lines 186-192), preventing orphaned users. -
Type Safety: New Zod schemas (
CreateUserSchema,CreateKeySchema,EditUserSchema) properly extend base schemas and handle edge cases like theexpiresAttransformation. -
Model Suggestions Hook: The
useModelSuggestionshook has an intentionally silent.catch()(line 21-23 inuse-model-suggestions.ts) which is appropriate here since model suggestions are an optional enhancement - the form works without them. -
Test Coverage: New tests cover dialog rendering, prop passing, success callbacks, and accessibility (dialog titles).
Potential Future Considerations
-
Hardcoded Chinese Strings: Lines 201-202, 218-219 in
add-key-form.tsxcontain hardcoded Chinese strings for Cache TTL labels. These should use i18n translations for consistency, but this is pre-existing code not modified in this PR. -
Console logging in production: The
console.errorcalls are appropriate for debugging but could be replaced with a proper logger in production. This is consistent with existing patterns in the codebase.
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate (18 new tests)
- Code clarity - Good
Automated review by Claude AI
There was a problem hiding this comment.
Code Review Summary
This is a well-structured refactoring PR that splits a large 1120-line unified dialog component into focused, single-responsibility components. The refactoring improves maintainability and includes comprehensive test coverage (18 tests). After thorough analysis across all review perspectives, no significant issues were identified that warrant inline comments.
PR Size: XL
- Lines changed: 3783 (2559 additions + 1224 deletions)
- Files changed: 24
Split Suggestions for XL PR:
While the PR is large, the changes are cohesive and splitting would be impractical as:
- The new components depend on each other and shared utilities
- Removing the old
unified-edit-dialog.tsxrequires all new components to be present - The integration changes in
users-page-client.tsxanduser-management-table.tsxdepend on the new components
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 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Review Notes
Positive observations (not for praise, but for context):
-
Error Handling: All catch blocks properly log errors via
console.error()with descriptive prefixes (e.g.,[CreateUserDialog]) and show user feedback viatoast.error(). No silent failures detected. -
Rollback Logic: The
CreateUserDialogincludes proper rollback when key creation fails after user creation (lines 186-192), preventing orphaned users. -
Type Safety: New Zod schemas (
CreateUserSchema,CreateKeySchema,EditUserSchema) properly extend base schemas and handle edge cases like theexpiresAttransformation. -
Model Suggestions Hook: The
useModelSuggestionshook has an intentionally silent.catch()(line 21-23 inuse-model-suggestions.ts) which is appropriate here since model suggestions are an optional enhancement - the form works without them. -
Test Coverage: New tests cover dialog rendering, prop passing, success callbacks, and accessibility (dialog titles).
Potential Future Considerations
-
Hardcoded Chinese Strings: Lines 201-202, 218-219 in
add-key-form.tsxcontain hardcoded Chinese strings for Cache TTL labels. These should use i18n translations for consistency, but this is pre-existing code not modified in this PR. -
Console logging in production: The
console.errorcalls are appropriate for debugging but could be replaced with a proper logger in production. This is consistent with existing patterns in the codebase.
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate (18 new tests)
- Code clarity - Good
Automated review by Claude AI
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
201-221: Cache TTL 部分存在硬编码的中文字符串此处的 Label、SelectItem 和描述文本使用了硬编码的中文字符串,违反了项目的国际化规范。根据 coding guidelines,应使用
next-intl进行国际化处理。🔎 建议的修复方案
+ const tKeyEdit = useTranslations("dashboard.userManagement.keyEditSection.fields"); <div className="space-y-2"> - <Label>Cache TTL 覆写</Label> + <Label>{tKeyEdit("cacheTtl.label")}</Label> <Select value={form.values.cacheTtlPreference} onValueChange={(val) => form.setValue("cacheTtlPreference", val as "inherit" | "5m" | "1h") } > <SelectTrigger> <SelectValue placeholder="inherit" /> </SelectTrigger> <SelectContent> - <SelectItem value="inherit">不覆写(跟随供应商/客户端)</SelectItem> - <SelectItem value="5m">5m</SelectItem> - <SelectItem value="1h">1h</SelectItem> + <SelectItem value="inherit">{tKeyEdit("cacheTtl.options.inherit")}</SelectItem> + <SelectItem value="5m">{tKeyEdit("cacheTtl.options.5m")}</SelectItem> + <SelectItem value="1h">{tKeyEdit("cacheTtl.options.1h")}</SelectItem> </SelectContent> </Select> - <p className="text-xs text-muted-foreground"> - 强制为包含 cache_control 的请求设置 Anthropic prompt cache TTL。 - </p> + <p className="text-xs text-muted-foreground">{tKeyEdit("cacheTtl.description")}</p> </div>根据 coding guidelines,使用 next-intl 进行国际化,支持 5 种语言 (en, ja, ru, zh-CN, zh-TW)。
🧹 Nitpick comments (12)
src/app/[locale]/dashboard/_components/user/utils/form-utils.ts (1)
1-9: 代码逻辑正确,实现清晰!该工具函数正确处理了表单错误的优先级和边界情况:
- 优先返回
_form错误- 正确过滤空字符串(通过真值检查)
- 适当使用可选链和空值回退
可选改进:增强类型不可变性
为了强化函数的不可变语义,可以考虑将参数类型标记为只读:
-export function getFirstErrorMessage(errors: Record<string, string>): string | null { +export function getFirstErrorMessage(errors: Readonly<Record<string, string>>): string | null {这样可以在类型层面保证函数不会修改输入对象,但这是一个可选的改进。
tests/unit/user-dialogs.test.tsx (1)
187-345: Mock 消息对象结构完整Mock messages 对象涵盖了测试所需的所有翻译路径,与实际的 i18n 消息文件结构一致。
可选优化:如果未来有更多组件测试需要类似的 mock messages,可以考虑将其提取为共享的测试工具函数,以减少重复。
src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts (1)
14-24: 考虑添加竞态条件处理当
providerGroup快速变化时,可能会出现竞态条件:旧的请求返回后覆盖新请求的结果。建议使用 cleanup 函数或 abort 标志来忽略过时的响应。🔎 建议的修复方案
useEffect(() => { + let isCancelled = false; getModelSuggestionsByProviderGroup(providerGroup) .then((res) => { - if (res.ok && res.data) { + if (!isCancelled && res.ok && res.data) { setModelSuggestions(res.data); } }) .catch(() => { // Silently fail - model suggestions are optional enhancement }); + return () => { + isCancelled = true; + }; }, [providerGroup]);src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (2)
153-185: 异步处理函数中的错误处理可以改进
handleDisableUser、handleEnableUser和handleDeleteUser函数在失败时抛出错误,但调用方(如 Line 220-225 的onToggleEnabled)没有 try-catch 包装。这可能导致未捕获的 Promise rejection。🔎 建议的修复方案
const handleDisableUser = async () => { - const res = await toggleUserEnabled(user.id, false); - if (!res.ok) { - throw new Error(res.error || t("editDialog.operationFailed")); + try { + const res = await toggleUserEnabled(user.id, false); + if (!res.ok) { + toast.error(res.error || t("editDialog.operationFailed")); + return; + } + toast.success(t("editDialog.userDisabled")); + onSuccess?.(); + queryClient.invalidateQueries({ queryKey: ["users"] }); + router.refresh(); + } catch (error) { + console.error("[EditUserDialog] disable user failed", error); + toast.error(t("editDialog.operationFailed")); } - toast.success(t("editDialog.userDisabled")); - onSuccess?.(); - queryClient.invalidateQueries({ queryKey: ["users"] }); - router.refresh(); };对
handleEnableUser和handleDeleteUser也应用相同的模式。
128-151: 字段映射逻辑较为复杂,建议简化
handleUserChange函数中 "description" 到 "note" 的映射逻辑分散在多处,增加了维护成本。考虑提取一个独立的映射函数来集中处理字段名转换。🔎 建议的简化方案
+const mapFieldName = (field: string): string => (field === "description" ? "note" : field); + const handleUserChange = (field: string | Record<string, any>, value?: any) => { const prev = form.values || defaultValues; const next = { ...prev } as EditUserValues; if (typeof field === "object") { Object.entries(field).forEach(([key, val]) => { - const mappedField = key === "description" ? "note" : key; + const mappedField = mapFieldName(key); (next as any)[mappedField] = mappedField === "expiresAt" ? (val ?? undefined) : val; }); } else { - const mappedField = field === "description" ? "note" : field; + const mappedField = mapFieldName(field); if (mappedField === "expiresAt") { (next as any)[mappedField] = value ?? undefined; } else { (next as any)[mappedField] = value; } } // ... };tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx (1)
91-98: 元素选择器较为脆弱使用
input[placeholder*="key"]作为选择器依赖于 placeholder 文本内容,当翻译文件更改时可能会导致测试失败。建议使用更稳定的选择器,如data-testid或基于name属性。src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx (1)
51-55: 按需隐藏到期时间/限额区域的改造合理,但可以懒计算limitRules和到期 UI新增的
showExpireTime/showLimitRules默认值为true,对现有调用是后向兼容的;CreateUserDialog 这类场景也能按需隐藏子区域,这点很好。如果后续发现该组件在大列表中频繁渲染、性能吃紧,可以考虑在
showLimitRules === false时跳过limitRules/existingLimitTypes的计算,在showExpireTime === false时延迟相关 UI 的计算与渲染,进一步减少不必要的 work。Also applies to: 143-145, 379-431
src/actions/providers.ts (1)
3094-3168: 按分组聚合 allowedModels 的实现正确,但参数目前未绑定到会话权限
- 分组解析 & 匹配逻辑(
parseGroupString+checkProviderGroupMatch)和PROVIDER_GROUP.DEFAULT/PROVIDER_GROUP.ALL语义是一致的,默认走default分组也合理。- 使用
findAllProviders()一次性拉取供应商,再在内存中过滤、去重模型列表,避免了 N+1,结构上没问题。一点可以考虑的加强点:
getModelSuggestionsByProviderGroup目前直接信任调用方传入的providerGroup,只要 session 存在,就可以传入"*"拿到所有分组的模型名。模型名通常不是敏感信息,但如果希望这个接口严格反映“当前用户可用的模型”,可以改为从 session / 用户记录中读取其真实 providerGroup,再据此计算建议列表,而不是简单使用参数。src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)
4-4: 行内 Key 编辑弹窗与“添加 Key” 按钮整体设计合理,有两点类型/可空性可以微调
- 基于
editingKeyId条件渲染EditKeyDialog的逻辑很清晰:既支持行内「编辑」/「查看详情」入口,又在未找到 key 时安全返回null,刷新后也会自动关闭,用户体验不错。- 展开区域右下角的 “Add Key” 按钮通过
onAddKey?控制是否显示,事件里stopPropagation()也避免了误触展开/收起,交互上考虑周全。可以考虑的小改动:
EditKeyDialog的userprop 目前通过内联对象再as any传入,后续如果 EditKeyDialog 的类型签名调整,很难在编译期发现问题。建议抽出一个明确的EditKeyUser类型(例如从UserDisplay派生Pick),让两边都用这个类型,去掉as any。translations.actions.addKey在 JSX 中直接渲染,但类型上是可选的;既然已经在 messages 各 locale 中新增了对应 key,可以把它改为必填字段,避免某些 locale 漏配时按钮文案渲染为undefined的隐蔽问题。Also applies to: 20-21, 35-37, 143-145, 465-469, 480-495, 499-545
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
120-160: CreateUser 调用目前忽略 providerGroup / isEnabled 字段,需确认是否为刻意简化在提交
createUserOnly时,只传了:
- name / note / tags / expiresAt / rpm / 各种 limit 字段 / allowedClients / allowedModels,
但没有传:
providerGroup(尽管 schema 和默认值里都存在);isEnabled(UpdateUserSchema通常包含此字段)。目前 UI 里
UserEditSection是以showProviderGroup={false}+isEnabled={true}的形式使用的,因此:
- providerGroup 在创建对话框里确实不可编辑,缺省走后端默认
default,忽略也不会影响体验;- isEnabled 若在
UserEditSection中有可见的开关,则该开关的值目前不会反映到后端,用户可能误以为可以创建“初始即禁用”的用户。建议:
- 如果设计上创建用户时不允许修改启用状态/分组,可以在
UserEditSection中隐藏或禁用对应控件,避免产生“可以改但其实不生效”的错觉;- 如果预期后续会放开这两个字段的编辑,则最好现在就把
providerGroup/isEnabled一并传给createUserOnly,保持与 EditUser 流程行为一致。src/app/[locale]/dashboard/users/users-page-client.tsx (2)
676-686: 避免使用as any类型断言。这里使用
as any绕过了 TypeScript 类型检查,降低了类型安全性。建议定义一个明确的类型或使用AddKeyDialog组件期望的userprop 类型。此外,相同的对象构造模式在 Lines 699-709 重复出现,可以提取为辅助函数以减少重复。
🔎 建议的重构方案
+ // 在组件顶部定义辅助函数 + const buildAddKeyUser = (user: UserDisplay) => ({ + id: user.id, + name: user.name, + providerGroup: user.providerGroup ?? null, + limit5hUsd: user.limit5hUsd ?? undefined, + limitWeeklyUsd: user.limitWeeklyUsd ?? undefined, + limitMonthlyUsd: user.limitMonthlyUsd ?? undefined, + limitTotalUsd: user.limitTotalUsd ?? undefined, + limitConcurrentSessions: user.limitConcurrentSessions ?? undefined, + }); {/* 使用辅助函数 */} <AddKeyDialog open={showCreateDialog} onOpenChange={handleCreateDialogClose} userId={selfUser.id} - user={ - { - id: selfUser.id, - name: selfUser.name, - providerGroup: selfUser.providerGroup ?? null, - limit5hUsd: selfUser.limit5hUsd ?? undefined, - limitWeeklyUsd: selfUser.limitWeeklyUsd ?? undefined, - limitMonthlyUsd: selfUser.limitMonthlyUsd ?? undefined, - limitTotalUsd: selfUser.limitTotalUsd ?? undefined, - limitConcurrentSessions: selfUser.limitConcurrentSessions ?? undefined, - } as any - } + user={buildAddKeyUser(selfUser)} isAdmin={false} onSuccess={handleKeyCreated} />
668-714: 对话框渲染逻辑清晰。管理员与非管理员的对话框流程分离合理:
- 管理员使用
CreateUserDialog创建用户- 非管理员使用
AddKeyDialog创建密钥- 额外的
AddKeyDialog(从用户列表触发)支持管理员为特定用户添加密钥建议添加注释说明两个
AddKeyDialog的不同用途,以提高代码可读性。
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
📒 Files selected for processing (24)
messages/en/dashboard.jsonmessages/ja/dashboard.jsonmessages/ru/dashboard.jsonmessages/zh-CN/dashboard.jsonmessages/zh-TW/dashboard.jsonsrc/actions/providers.tssrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxsrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsxsrc/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/utils/provider-group.tssrc/app/[locale]/dashboard/users/users-page-client.tsxtests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxtests/unit/user-dialogs.test.tsx
💤 Files with no reviewable changes (1)
- src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx,js,jsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Use 2-space indentation in all code files
Files:
src/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/actions/providers.tstests/unit/user-dialogs.test.tsxsrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tstests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxmessages/zh-TW/dashboard.jsonmessages/ja/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxmessages/ru/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxmessages/zh-CN/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsxmessages/en/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias@/*to reference files from./src/*directory
**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias@/*to reference files in./src/*directory
Files:
src/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/actions/providers.tstests/unit/user-dialogs.test.tsxsrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tstests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsxsrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures
Files:
src/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/actions/providers.tstests/unit/user-dialogs.test.tsxsrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tstests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsxsrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages
Files:
src/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/actions/providers.tssrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tssrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/app/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Implement Content-Security-Policy headers for XSS prevention
Files:
src/app/[locale]/dashboard/_components/user/utils/form-utils.tssrc/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.tssrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.tssrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{tsx,jsx}: Uselucide-reactfor icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities
Files:
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsx
**/*.{tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW
Files:
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxtests/unit/user-dialogs.test.tsxsrc/app/[locale]/dashboard/_components/user/add-key-dialog.tsxtests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxsrc/app/[locale]/dashboard/_components/user/create-user-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxmessages/zh-TW/dashboard.jsonmessages/ja/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxmessages/ru/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsxmessages/zh-CN/dashboard.jsonsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/user-management-table.tsxmessages/en/dashboard.json
src/actions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/actions/**/*.ts: Validate all user inputs with Zod schemas before processing
Use Server Actions innext-safe-actionwith OpenAPI generation for admin API endpoints
Use Next.js API Routes and Server Actions for admin operations and REST endpoints
Files:
src/actions/providers.ts
src/**/*provider*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Set provider circuit breaker failure threshold, open duration, and half-open success threshold in configuration
Files:
src/actions/providers.tssrc/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/{repository,actions}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Avoid N+1 queries by using eager loading and batch queries for statistics
Files:
src/actions/providers.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches
Files:
tests/unit/user-dialogs.test.tsxtests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
messages/**/*.json
📄 CodeRabbit inference engine (CLAUDE.md)
Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in
messages/{locale}/*.jsonStore message translations in
messages/{locale}/*.jsonfiles
Files:
messages/zh-TW/dashboard.jsonmessages/ja/dashboard.jsonmessages/ru/dashboard.jsonmessages/zh-CN/dashboard.jsonmessages/en/dashboard.json
🧠 Learnings (7)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.{tsx,json} : Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW
Applied to files:
src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/i18n/**/*.ts : Configure i18n routing in `src/i18n/`
Applied to files:
src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.tssrc/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Validate all user inputs with Zod schemas before processing
Applied to files:
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches
Applied to files:
tests/unit/user-dialogs.test.tsx
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use `lucide-react` for icons instead of custom SVGs
Applied to files:
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.{tsx,jsx} : Use `lucide-react` for icons, no custom SVGs
Applied to files:
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/components/**/*.{tsx,jsx} : Use shadcn/ui component library for high-quality, accessible UI components
Applied to files:
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsx
🧬 Code graph analysis (11)
src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts (1)
scripts/sync-settings-keys.js (1)
t(72-72)
src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts (1)
src/actions/providers.ts (1)
getModelSuggestionsByProviderGroup(3128-3168)
src/actions/providers.ts (4)
src/lib/constants/provider.constants.ts (1)
PROVIDER_GROUP(25-30)src/actions/types.ts (1)
ActionResult(31-31)src/lib/auth.ts (1)
getSession(116-128)src/repository/provider.ts (1)
findAllProviders(197-261)
tests/unit/user-dialogs.test.tsx (5)
src/components/ui/dialog.tsx (6)
Dialog(119-119)DialogContent(121-121)DialogHeader(124-124)DialogTitle(127-127)DialogDescription(122-122)DialogFooter(123-123)src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)
EditUserDialog(262-268)src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
EditKeyDialog(38-64)src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)
AddKeyDialog(34-128)src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
CreateUserDialog(419-425)
tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx (1)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
AddKeyForm(33-348)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (6)
src/lib/validation/schemas.ts (3)
CreateUserSchema(30-156)UpdateUserSchema(161-279)KeyFormSchema(284-339)src/lib/constants/provider.constants.ts (1)
PROVIDER_GROUP(25-30)src/actions/users.ts (2)
createUserOnly(899-1063)removeUser(1205-1228)src/actions/keys.ts (1)
addKey(84-278)src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
normalizeProviderGroup(10-23)src/app/[locale]/dashboard/_components/user/utils/form-utils.ts (1)
getFirstErrorMessage(5-9)
src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (2)
src/components/ui/label.tsx (1)
Label(21-21)src/components/ui/switch.tsx (1)
Switch(28-28)
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
EditKeyDialog(38-64)
src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx (6)
src/components/ui/calendar.tsx (1)
Calendar(179-179)src/components/form/date-picker-field.tsx (1)
DatePickerField(55-164)src/app/[locale]/dashboard/_components/user/forms/quick-expire-picker.tsx (1)
QuickExpirePicker(38-67)src/components/ui/button.tsx (1)
Button(58-58)src/app/[locale]/dashboard/_components/user/forms/limit-rules-display.tsx (1)
LimitRulesDisplay(45-90)src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx (1)
LimitRulePicker(85-303)
src/app/[locale]/dashboard/_components/user/user-management-table.tsx (3)
src/types/user.ts (1)
UserDisplay(130-156)src/app/[locale]/dashboard/_components/user/user-key-manager.tsx (1)
UserDisplay(129-129)src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)
EditUserDialog(262-268)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
src/lib/constants/provider.constants.ts (1)
PROVIDER_GROUP(25-30)
⏰ 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). (6)
- GitHub Check: pr-review
- GitHub Check: Greptile Review
- GitHub Check: 🌐 API Tests
- GitHub Check: pr-label
- GitHub Check: Docker Build Test
- GitHub Check: pr-description
🔇 Additional comments (29)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
10-23: 实现正确,逻辑清晰函数正确处理了各种边界情况:
- null/undefined/非字符串输入返回默认值
- 空字符串和仅包含空白的输入返回默认值
- 正确地去重、排序和规范化有效输入
实现符合 PR 目标中提到的 provider group 规范化需求。
messages/zh-CN/dashboard.json (1)
773-778: 翻译新增内容符合 UI 需求新增的翻译条目支持了拆分后的对话框组件:
addKeyForm.successTitle/successDescription/generatedKey: 密钥创建成功提示userManagement.table.actions.addKey: 新增密钥操作标签editDialog.title/description: 简化为仅关注用户编辑createDialog.successTitle/successDescription/generatedKey/keyHint: 用户创建成功流程keyEditSection.balanceQueryPage.descriptionDisabled: 更新为反映受限 Web UI 的使用场景措辞清晰,与 PR 目标中的新对话框组件结构一致。
Also applies to: 1153-1155, 1223-1227, 1323-1327, 1544-1544
messages/zh-TW/dashboard.json (1)
753-758: 繁体中文翻译与简体中文保持一致新增的翻译条目与
messages/zh-CN/dashboard.json中的结构完全对应:
- 密钥创建成功提示
- 新增密钥操作
- 编辑对话框简化
- 用户创建成功流程
- 余额查询页面描述更新
建议人工验证繁体中文措辞是否符合目标用户的语言习惯。
Also applies to: 1128-1130, 1193-1194, 1293-1297, 1514-1514
tests/unit/user-dialogs.test.tsx (3)
1-12: 测试环境配置正确使用
happy-dom环境进行 React 组件测试是正确的选择。虽然代码规范中提到使用 Node 环境,但该建议主要针对纯逻辑单元测试。对于需要 DOM 操作的 UI 组件测试,happy-dom或jsdom是必需的。
20-118: Mock 结构完善,测试隔离良好测试文件的 mock 策略合理:
- 完整模拟了 next/navigation、路由、server actions、toast 等依赖
- Dialog 和 Form 组件使用简化的 mock 实现,保留了 data-testid 用于查询
- Server action mocks 返回适当的响应数据,支持成功和失败场景
这种结构确保了测试的独立性和可维护性。
390-752: 测试覆盖全面,场景合理测试套件涵盖了 4 个对话框组件的关键场景:
- EditUserDialog: 开启/关闭渲染、用户 ID 传递、按钮存在性、prop 变化重渲染
- EditKeyDialog: 密钥数据传递、关闭回调
- AddKeyDialog: userId 传递、成功回调和 UI 更新
- CreateUserDialog: 开启/关闭渲染、按钮存在性
- 集成测试: 可访问性标题验证
测试实践良好:
- 使用
act()包裹状态更新- 每个测试后正确清理(unmount 和 DOM 清空)
- 通过 data-testid 而非实现细节进行断言
与 PR 目标中的新组件结构对应,测试充分验证了组件拆分后的行为。
messages/ru/dashboard.json (1)
755-760: 俄语翻译结构与其他语言版本一致新增的翻译条目在结构上与中文版本完全对应:
- 密钥创建成功消息
- 新增密钥操作标签
- 编辑对话框标题简化
- 用户创建成功流程
- 余额查询页面描述更新
翻译内容与 PR 中的新对话框组件功能匹配。建议由母语使用者验证俄语措辞的准确性。
Also applies to: 1130-1132, 1195-1196, 1295-1298, 1393-1393
src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (1)
174-193: LGTM!反转开关逻辑实现正确,注释清晰地解释了设计意图。
checked={!form.values.canLoginWebUi}和onCheckedChange={(checked) => form.setValue("canLoginWebUi", !checked)}的组合确保了正确的状态管理。动态描述文本根据当前状态显示,提升了用户体验。src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
155-170: LGTM!反转开关逻辑与
edit-key-form.tsx保持一致,注释清晰地说明了设计意图。默认值从true改为false符合新的业务逻辑。src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts (1)
1-136: LGTM!该 hook 的实现良好:
- 使用
useMemo正确优化了翻译对象的创建KeyEditTranslations接口提供了清晰的类型定义- 翻译键结构与 UI 组件的使用模式一致
- 遵循了项目的 next-intl 国际化规范
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)
262-268: LGTM! 条件渲染优化使用
{props.open ? <EditUserDialogInner key={props.user.id} {...props} /> : null}确保对话框关闭时内部组件被卸载,结合key={props.user.id}确保切换用户时组件状态被重置。这是一个良好的性能和状态管理实践。src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
53-63: LGTM!组件实现简洁清晰:
- 职责单一,仅负责对话框的展示和关闭逻辑
sr-only类确保了对屏幕阅读器的可访问性handleSuccess正确处理了成功回调和对话框关闭- Props 接口定义完善,与
EditKeyForm的接口保持一致src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts (1)
6-190: 集中管理用户编辑文案的结构清晰,可复用性好
UserEditTranslations的层级设计和useUserTranslations的封装都很干净,showProviderGroup通过 optional 字段控制也比较安全,默认值和依赖数组处理正确,看不到明显问题。src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)
20-127: 新增 AddKeyDialog 流程清晰,状态管理和复制逻辑都比较健壮
- 通过
generatedKey切换表单态/成功态的方式简单直接,关闭时统一重置本地状态也避免了下次打开时的残留。navigator.clipboard错误路径有 toast 和console.error,对 HTTP / 权限不足等场景也有比较好的降级体验。Dialog目前由外部open控制,内部onOpenChange={handleClose}专门处理关闭即可,和现有用法是兼容的。整体实现看起来可以直接合入。
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
331-395: 复用 UserEditSection / KeyEditSection 的方式不错,隐藏 Key 的限额与到期设置符合“创建时简化表单”的目标
- User 部分通过
UserEditSection复用已有编辑 UI,利用modelSuggestions和useUserTranslations保持体验一致,这样后续文案或字段调整时也更易维护。- Key 部分用
KeyEditSection且传入showLimitRules={false}、showExpireTime={false},只保留基础字段(名称、Provider 分组、缓存 TTL 等),让首次创建用户的表单更精简,这个拆分是合理的。后续如果产品上希望在创建阶段就设置 Key 的限额/到期时间,也可以只通过这两个布尔开关打开对应 UI,无需再拆新组件。
messages/ja/dashboard.json (1)
753-758: 日文文案与新增对话框/按钮结构匹配,多语言键已同步完成日文翻译的结构和内容正确:
addKeyForm.successTitle/successDescription/generatedKey.*与AddKeyDialog成功态展示对应userManagement.table.actions.addKey与UserKeyTableRow新增按钮相呼应createDialog.success*/generatedKey/keyHint对应CreateUserDialog成功态提示keyEditSection.balanceQueryPage.descriptionDisabled的表达清晰简洁验证确认:所有 5 个语言包(en/ja/ru/zh-CN/zh-TW)均已包含这些键,多语言同步完整无缺失。
src/app/[locale]/dashboard/_components/user/user-management-table.tsx (5)
15-15: LGTM!导入
EditUserDialog组件,使用了正确的@/路径别名,符合代码规范。
29-29: LGTM!新增
onAddKey可选属性,类型定义正确,与UserDisplay类型匹配。在组件内正确解构使用。Also applies to: 114-114
208-211: LGTM!正确地将
addKey翻译键添加到rowTranslations.actions中,使用了展开运算符合并现有翻译。
329-332: LGTM!
openEditDialog简化为仅接受userId,符合 PR 目标中将用户编辑与密钥编辑流程解耦的设计。onAddKey正确创建闭包传递用户上下文。Also applies to: 572-573
597-603: LGTM!
EditUserDialog的条件渲染逻辑正确,仅在editingUser存在且为管理员时显示。Props 与EditUserDialogProps接口匹配。src/app/[locale]/dashboard/users/users-page-client.tsx (3)
8-10: LGTM!导入变更正确,添加了
useQueryClient用于缓存失效,移除了未使用的Key图标导入。
230-260: LGTM!新增的 AddKeyDialog 状态管理和处理函数设计合理:
handleAddKeyDialogClose在关闭时正确清理addKeyUser状态handleKeyCreated正确使用queryClient.invalidateQueries刷新缓存useCallback依赖项设置正确
499-505: LGTM!"创建用户"按钮正确限制为仅管理员可见,符合权限控制要求。
messages/en/dashboard.json (5)
1151-1154: LGTM!新增
table.actions.addKey翻译键,与user-management-table.tsx中的tUserMgmt("table.actions.addKey")调用匹配。
1222-1223: LGTM!
editDialog的标题和描述更新反映了重构后的单一职责设计 ——EditUserDialog现在仅专注于用户信息编辑,不再包含密钥设置。
1322-1325: LGTM!
createDialog下新增的成功消息与addKeyForm中的模式保持一致,支持密钥创建成功后的用户反馈流程。
1420-1420: LGTM!
descriptionDisabled的文案更新移除了关于修改 provider group 的描述,简化了用户理解。两处相同文案分别位于不同的上下文路径中,属于合理的结构设计。Also applies to: 1545-1545
772-777: 新增的国际化键已正确同步到所有5个语言文件。已验证
addKeyForm.successTitle、addKeyForm.successDescription和addKeyForm.generatedKey在所有语言版本 (en, ja, ru, zh-CN, zh-TW) 中都存在且结构一致。消息键的命名规范清晰,符合现有约定,完全满足 next-intl 国际化需求。
- 新增 KeyDialogUserContext 类型,替代过于宽泛的 User 类型 - 移除 4 处 as any 类型断言 - 清理 key-list-header.tsx 中伪造的字段 Addresses Gemini Code Review feedback on type safety
Type Safety Fix for
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/app/[locale]/dashboard/users/users-page-client.tsx (1)
668-708: 消除重复的用户对象构造逻辑在两处(lines 676-684 和 696-704)使用了相同的用户对象构造逻辑。这种重复增加了维护成本,如果
KeyDialogUserContext的字段发生变化,需要在多处更新。建议提取为辅助函数以消除重复。
🔎 提取辅助函数的建议
在文件顶部或独立的 utils 文件中添加:
function mapUserDisplayToKeyDialogContext(user: UserDisplay): KeyDialogUserContext { return { id: user.id, providerGroup: user.providerGroup ?? null, limit5hUsd: user.limit5hUsd ?? undefined, limitWeeklyUsd: user.limitWeeklyUsd ?? undefined, limitMonthlyUsd: user.limitMonthlyUsd ?? undefined, limitTotalUsd: user.limitTotalUsd ?? undefined, limitConcurrentSessions: user.limitConcurrentSessions ?? undefined, }; }然后在两处使用:
<AddKeyDialog open={showCreateDialog} onOpenChange={handleCreateDialogClose} userId={selfUser.id} - user={{ - id: selfUser.id, - providerGroup: selfUser.providerGroup ?? null, - limit5hUsd: selfUser.limit5hUsd ?? undefined, - limitWeeklyUsd: selfUser.limitWeeklyUsd ?? undefined, - limitMonthlyUsd: selfUser.limitMonthlyUsd ?? undefined, - limitTotalUsd: selfUser.limitTotalUsd ?? undefined, - limitConcurrentSessions: selfUser.limitConcurrentSessions ?? undefined, - }} + user={mapUserDisplayToKeyDialogContext(selfUser)} isAdmin={false} onSuccess={handleKeyCreated} /><AddKeyDialog open={showAddKeyDialog} onOpenChange={handleAddKeyDialogClose} userId={addKeyUser.id} - user={{ - id: addKeyUser.id, - providerGroup: addKeyUser.providerGroup ?? null, - limit5hUsd: addKeyUser.limit5hUsd ?? undefined, - limitWeeklyUsd: addKeyUser.limitWeeklyUsd ?? undefined, - limitMonthlyUsd: addKeyUser.limitMonthlyUsd ?? undefined, - limitTotalUsd: addKeyUser.limitTotalUsd ?? undefined, - limitConcurrentSessions: addKeyUser.limitConcurrentSessions ?? undefined, - }} + user={mapUserDisplayToKeyDialogContext(addKeyUser)} isAdmin={isAdmin} onSuccess={handleKeyCreated} />src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (2)
480-495: 添加密钥按钮缺少无障碍标签。"添加密钥"按钮应该添加
aria-label属性以提升可访问性,特别是对于使用屏幕阅读器的用户。🔎 建议的改进
<Button type="button" variant="outline" size="sm" + aria-label={translations.actions.addKey ?? "Add Key"} onClick={(e) => { e.stopPropagation(); onAddKey(); }} >
499-542: 密钥编辑对话框渲染逻辑可优化。当前实现使用 IIFE 模式在每次渲染时查找编辑中的密钥。虽然功能正确,但可以通过以下方式提升可读性和性能:
- 使用
useMemo缓存密钥查找结果,避免在每次渲染时重复查找。- 提取为独立的渲染函数或组件,提升代码可维护性。
另外,根据提交信息,已消除
as any类型断言并使用了精确的KeyDialogUserContext类型(第 525-533 行),这是很好的改进。🔎 可选的重构建议
+ const editingKey = useMemo( + () => (editingKeyId !== null ? user.keys.find((k) => k.id === editingKeyId) : null), + [editingKeyId, user.keys] + ); + return ( <div className="border-b"> {/* ... 现有代码 ... */} {/* Key 编辑 Dialog */} - {editingKeyId !== null && - (() => { - const editingKey = user.keys.find((k) => k.id === editingKeyId); - if (!editingKey) return null; - return ( + {editingKey && ( <EditKeyDialog open={true} onOpenChange={(open) => { if (!open) setEditingKeyId(null); }} {/* ... 其余 props ... */} /> - ); - })()} + )} </div> );
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
📒 Files selected for processing (8)
src/app/[locale]/dashboard/_components/user/add-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/types/user.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
- src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Use 2-space indentation in all code files
Files:
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/types/user.tssrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias@/*to reference files from./src/*directory
**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias@/*to reference files in./src/*directory
Files:
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/types/user.tssrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
src/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{tsx,jsx}: Uselucide-reactfor icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities
Files:
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures
Files:
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/types/user.tssrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW
Files:
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsxsrc/app/[locale]/dashboard/_components/user/forms/add-key-form.tsxsrc/app/[locale]/dashboard/_components/user/key-list-header.tsxsrc/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages
Files:
src/types/user.ts
🧠 Learnings (4)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Validate all user inputs with Zod schemas before processing
Applied to files:
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/components/**/*.{tsx,jsx} : Use shadcn/ui component library for high-quality, accessible UI components
Applied to files:
src/app/[locale]/dashboard/users/users-page-client.tsxsrc/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use `lucide-react` for icons instead of custom SVGs
Applied to files:
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.{tsx,jsx} : Use `lucide-react` for icons, no custom SVGs
Applied to files:
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
🧬 Code graph analysis (4)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (3)
src/types/user.ts (1)
KeyDialogUserContext(162-170)src/components/ui/dialog.tsx (5)
Dialog(119-119)DialogContent(121-121)DialogHeader(124-124)DialogTitle(127-127)DialogDescription(122-122)src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (1)
EditKeyForm(48-370)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
src/types/user.ts (1)
KeyDialogUserContext(162-170)
src/app/[locale]/dashboard/users/users-page-client.tsx (4)
src/types/user.ts (1)
UserDisplay(130-156)src/app/[locale]/dashboard/_components/user/user-key-manager.tsx (1)
UserDisplay(129-129)src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
CreateUserDialog(419-425)src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)
AddKeyDialog(34-128)
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
EditKeyDialog(38-64)
⏰ 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: 🌐 API Tests
- GitHub Check: Docker Build Test
🔇 Additional comments (10)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
1-64: 实现简洁且类型安全该组件的实现质量很高:
- 使用
KeyDialogUserContext类型提高了类型安全性handleSuccess正确地链接了回调并关闭对话框- 使用
sr-only类提供了良好的无障碍支持src/app/[locale]/dashboard/_components/user/key-list-header.tsx (1)
318-336: 用户对象构造符合新类型契约传递给
AddKeyForm的用户对象现在正确地符合KeyDialogUserContext类型。providerGroup的空值合并到null、limitTotalUsd的空值合并到undefined都是合理的。这解决了之前审查中提到的
as any类型问题,提高了类型安全性。src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (3)
56-56: 确认canLoginWebUi默认值变更
canLoginWebUi的默认值从true更改为false。这是一个行为变更,意味着新创建的密钥默认情况下将使用独立的余额查询页面,而不是完整的 Web UI。请确认此更改是有意为之,并且与产品需求一致。
155-171: 反转逻辑设计合理且文档完善余额查询页面切换的反转逻辑(
canLoginWebUi的反向绑定)设计清晰:
- 注释清楚地解释了
canLoginWebUi=true表示完整 Web UI 访问(开关关闭)canLoginWebUi=false表示使用独立余额页面(开关打开)- UI 标签和行为与用户期望一致
虽然反转逻辑可能带来维护复杂性,但完善的注释有助于理解。
77-78: 后端正确处理了空字符串作为"无过期时间"的表示验证发现,
src/lib/validation/schemas.ts中的KeyFormSchema对expiresAt字段的定义为:expiresAt: z .string() .optional() .default("") .transform((val) => (val === "" ? undefined : val)),该方案正确地将前端发送的空字符串(
"")转换为undefined,避免了 Server Action 序列化时undefined丢失的问题。后端接收到的实际数据为undefined,数据库处理无需修改。此实现模式是处理 Server Action 序列化限制的标准做法。src/app/[locale]/dashboard/users/users-page-client.tsx (2)
258-260: 缓存失效处理得当使用
queryClient.invalidateQueries在密钥创建后刷新数据是正确的做法,确保 UI 显示最新状态。
668-708: 解决了之前审查中的类型安全问题根据之前的审查意见,此处代码通过使用
KeyDialogUserContext类型替代as any断言,成功提高了类型安全性。内联对象构造现在具有完整的类型检查。基于 learnings,这正是之前建议的改进方向。
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (3)
4-4: 导入的图标和组件符合规范。使用
lucide-react提供的Plus图标以及正确使用路径别名导入EditKeyDialog组件,符合项目编码规范。Also applies to: 20-20
143-144: 编辑密钥状态管理正确。使用
editingKeyId状态跟踪当前正在编辑的密钥,类型定义准确(number | null),初始化合理。
465-465: 密钥编辑和查看详情统一使用 EditKeyDialog。将
onEdit和onViewDetails回调统一为打开EditKeyDialog,符合 PR 目标:为单个密钥提供专用编辑对话框。实现简洁且一致。Also applies to: 468-468
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup) - Replace read-only Badge display with editable ProviderGroupSelect component - Move modelSuggestions hook after form declaration to support dynamic updates Regression from ding113#539
- 在 add-key-form.tsx 和 edit-key-form.tsx 中添加 handleProviderGroupChange - 当选择多个分组时自动移除 default 分组 - 修复 DialogFormLayout 中输入框 focus ring 左边被裁剪的样式问题 此问题由 PR ding113#539 重构引入,原有逻辑在 key-edit-section.tsx 中存在但未迁移到新表单组件
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup) - Replace read-only Badge display with editable ProviderGroupSelect component - Move modelSuggestions hook after form declaration to support dynamic updates Regression from ding113#539
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup) - Replace read-only Badge display with editable ProviderGroupSelect component - Move modelSuggestions hook after form declaration to support dynamic updates Regression from ding113#539
* fix: enable provider group editing in edit user dialog - Always show providerGroup field in edit mode (was hidden when user had no providerGroup) - Replace read-only Badge display with editable ProviderGroupSelect component - Move modelSuggestions hook after form declaration to support dynamic updates Regression from #539 * fix: add complete translations for ProviderGroupSelect in edit user dialog Pass full translations object to ProviderGroupSelect including: - tagInputErrors for validation messages (empty, duplicate, too_long, etc.) - errors.loadFailed for API error handling - providersSuffix for provider count display This fixes untranslated error messages when users input invalid provider group tags. * feat(prices): 添加手动模型价格管理功能 - 新增 source 字段区分 litellm/manual 来源 - 支持手动添加、编辑、删除模型价格 - LiteLLM 同步时自动跳过手动价格,避免覆盖 - 添加冲突检测和解决 UI,支持批量处理 - 完整的单元测试覆盖 closes #405 * fix: 修复 CI 检查问题 - 移除未使用的 ModelPriceSource 导入 - 修复 useEffect 依赖数组 (fetchPrices) - 修复 fetchPrices 声明前使用问题 - 添加价格非负数验证 - 格式化代码 * fix: wrap upsertModelPrice in transaction for data integrity
Summary
Closes #413
将 1120 行的
unified-edit-dialog.tsx拆分为 4 个单一职责组件,提取共享逻辑到 hooks 和 utils,修复 expiresAt 清除时传参问题,并定义精确类型消除as any。Changes
新增组件
CreateUserDialogEditUserDialogAddKeyDialogEditKeyDialog提取的共享代码
hooks/use-key-translations.ts- 密钥表单翻译hooks/use-model-suggestions.ts- 模型建议列表hooks/use-user-translations.ts- 用户表单翻译utils/form-utils.ts- 表单工具函数utils/provider-group.ts- Provider Group 处理新增类型定义
KeyDialogUserContext(src/types/user.ts) - Key Dialog 所需的精简用户上下文类型新增 Server Action
getModelSuggestionsByProviderGroup- 根据 Provider Group 获取模型建议Bug 修复
类型安全改进
KeyDialogUserContext类型,移除 4 处as any断言(响应 Code Review 反馈)Test plan
tests/unit/user-dialogs.test.tsx(18 tests)tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsxBreaking Changes
unified-edit-dialog.tsx,如有外部引用需更新为新组件