Conversation
- Add time range filter improvements with preset options - Enhance provider chain popover with better error display - Improve provider vendor view circuit breaker UI - Add availability dashboard and provider endpoints display name tests - Update i18n messages for provider chain and settings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ibility The file has "use server" directive, which requires all exported functions to be async. This fixes the Next.js Turbopack build error: "Server Actions must be async functions." CI Run: https://github.com/ding113/claude-code-hub/actions/runs/21357337034 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Detect when TagInput is inside a Dialog and portal suggestions to dialog-content - Use absolute positioning relative to dialog container instead of fixed viewport positioning - Fix scroll/resize event listeners to target the correct scroll container - Add unit tests for TagInput behavior inside Dialog Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthrough新增多个本地化键与提示;引入并传递 serverTimeZone 到日志/使用界面,添加时区感知的时间范围工具;provider-chain 支持解析/去重并渲染多徽章;端点支持启用切换;将若干 message_request token 列改为 bigint,SQL 聚合改为 double precision;并新增/调整大量测试与布局对齐。 Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers a suite of UI improvements primarily focused on the dashboard's logs and provider settings. It introduces crucial timezone handling for log data, enhances the display of provider chain details, and refines the user experience for managing provider endpoints. Additionally, it includes general UI tweaks for better readability and ensures component compatibility within complex layouts. 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
This pull request introduces several new translation keys across multiple languages for provider chain reasons and a vendor aggregation rule. It enhances date and time handling in log and usage dashboards by implementing server-side timezone awareness, fetching the server's timezone, and passing it down to various date/time filter components for accurate display and calculation. The ProviderChainPopover now displays individual, deduped group tags as badges with tooltips, and the cache token display in log tables has been restyled for better alignment. Additionally, the 'Probe All' floating button has been removed from the availability dashboard, and the provider endpoint management has been refined by removing the 'label' field, adding an enable/disable switch for endpoints, and improving the logic for displaying vendor cards. A new unit test for TagInput within a Dialog was added, and the deriveDisplayNameFromDomain utility was updated for more robust domain name parsing.
| try { | ||
| const session = await getSession(); | ||
| if (!session) { | ||
| return { ok: false, error: "未授权" }; |
| return { ok: true, data: { timeZone: TZ } }; | ||
| } catch (error) { | ||
| logger.error("获取时区失败:", error); | ||
| return { ok: false, error: "获取时区失败" }; |
| let name = parts[parts.length - 2]; | ||
| if (name === "api" && parts.length >= 3) { | ||
| name = parts[parts.length - 3]; | ||
| } |
There was a problem hiding this comment.
logic for detecting api prefix changed from checking first part to checking second-to-last part - breaks for domains like api.openai.com where original expected parts[0] === "api" but new checks parts[parts.length - 2] which is "openai" not "api"
| let name = parts[parts.length - 2]; | |
| if (name === "api" && parts.length >= 3) { | |
| name = parts[parts.length - 3]; | |
| } | |
| let name = parts[0]; | |
| if (name === "api" && parts.length >= 2) { | |
| name = parts[1]; | |
| } else if (parts.length > 1) { | |
| name = parts[parts.length - 2]; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 245:248
Comment:
logic for detecting `api` prefix changed from checking first part to checking second-to-last part - breaks for domains like `api.openai.com` where original expected `parts[0] === "api"` but new checks `parts[parts.length - 2]` which is "openai" not "api"
```suggestion
let name = parts[0];
if (name === "api" && parts.length >= 2) {
name = parts[1];
} else if (parts.length > 1) {
name = parts[parts.length - 2];
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Code Review Summary
No significant issues identified in this PR. The changes implement dashboard UI improvements including server timezone support, cache badge alignment fixes, group tag badge enhancements, and tag input dialog positioning fixes.
PR Size: XL
- Lines changed: 980 (858 additions, 122 deletions)
- Files changed: 37
Split Suggestion: This XL PR touches multiple independent features. Consider splitting future similar PRs into:
- Time zone support changes (time-range utils, filters, date pickers)
- UI layout fixes (cache badge alignment, group tag badges)
- Provider vendor view enhancements (toggle switch, info tooltip)
- Tag input dialog fix (separate component change)
Changes Overview
| Category | Description |
|---|---|
| i18n | Added 5 new translation keys across all 5 languages for provider chain reasons |
| Time Zone | Server timezone now passed through to date/time filters for accurate date calculations |
| UI/UX | Cache TTL badge moved to left side, group tags now display as separate badges with tooltips |
| Components | TagInput dropdown now correctly positions inside Dialog components |
| Provider View | Added inline toggle switch for endpoint enable/disable, info tooltip for vendor aggregation |
| Tests | Added comprehensive tests for new functionality |
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate (new tests added for key changes)
- Code clarity - Good
Notes
- All i18n strings properly added to all 5 supported languages (zh-CN, zh-TW, en, ja, ru)
- The
deriveDisplayNameFromDomainfunction was changed to async but contains no async operations - this is a minor design choice that doesn't affect functionality - Removed FloatingProbeButton from availability dashboard (intentional UI simplification)
- Removed endpoint label field from add/edit dialogs (intentional simplification)
Automated review by Claude AI
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/app/[locale]/my-usage/_components/statistics-summary-card.tsx (1)
31-45: 默认日期应与 serverTimeZone 对齐。
目前初始 dateRange 仍使用客户端本地日期;当 serverTimeZone 与本地不一致时,首次加载统计范围可能偏移一天,且与 LogsDateRangePicker 显示不一致。建议初始化时基于 serverTimeZone 计算 today(可复用日志时间范围工具或与 picker 同源逻辑)。Also applies to: 129-134
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx (1)
46-68: Quick Filters 仍使用本地时区,可能与 serverTimeZone 不一致。
虽然已将 serverTimeZone 透传到 TimeFilters,但 "today/this-week" 预设仍使用本地 startOfDay/startOfWeek。serverTimeZone 与客户端时区不同会导致预设范围错误,并与日期选择器产生不一致。建议改用时区感知的 helper(例如 getQuickDateRange(period, serverTimeZone))统一计算。Also applies to: 264-268
src/app/[locale]/dashboard/logs/_components/filters/time-filters.tsx (1)
50-54:displayEndDate未使用serverTimeZone进行时区转换。
displayEndDate直接使用format(new Date(inclusiveEndTime), "yyyy-MM-dd"),而displayStartDate(Line 41)使用了timestampToDateString函数(该函数会考虑serverTimeZone)。这种不一致可能导致在非本地时区下,开始日期和结束日期的显示格式不同。🐛 建议修复:统一使用 timestampToDateString
// Memoized endDate calculation: endTime is exclusive, use endTime-1s to infer inclusive display end date const displayEndDate = useMemo(() => { if (!filters.endTime) return undefined; const inclusiveEndTime = inclusiveEndTimestampFromExclusive(filters.endTime); - return format(new Date(inclusiveEndTime), "yyyy-MM-dd"); - }, [filters.endTime]); + return timestampToDateString(inclusiveEndTime); + }, [filters.endTime, timestampToDateString]);
🤖 Fix all issues with AI agents
In `@messages/en/settings/providers/strings.json`:
- Line 68: Remove the trailing period from the string value for the key
"vendorAggregationRule" in messages/en/settings/providers/strings.json so it
matches the punctuation style of sibling short labels; update the value "Grouped
by website domain." to "Grouped by website domain" without changing the key
name.
In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-sections.tsx:
- Line 3: 当前缺少对 TZ 环境变量的 IANA 时区验证,导致无效 TZ 值会传到 date-fns-tz(例如
toZonedTime、formatInTimeZone)并抛出 RangeError,从而在 time-range.ts 和 time-filters.tsx
中未被捕获而使页面崩溃;请在 env.schema.ts 中为 TZ(当前由 getEnvConfig() 提供)添加 IANA
白名单或正则校验并设默认值,或者在所有调用 date-fns-tz 的位置(例如在 time-range.ts 的相关函数和 time-filters.tsx
中使用 formatInTimeZone/toZonedTime 的代码段)用 try-catch 包裹并在捕获异常时回退到安全值(undefined 或
"UTC")并记录错误。
In `@src/components/ui/__tests__/tag-input-dialog.test.tsx`:
- Around line 50-59: 将测试中对用户可见文本的硬编码替换为 i18n 资源:把 DialogTitle、DialogDescription
以及 TagInput 的 suggestions 中的 label 和 value 从硬编码字符串改为从项目 i18n 翻译函数或资源对象读取(例如通过
t('...') 或导入的 locales),并更新断言以基于翻译后的输出或更稳健地使用 data-testid 查询(例如 为
DialogTitle、DialogDescription 或 suggestion 项添加 data-testid 并在测试中使用这些 id
进行断言);修改点集中在组件使用处(DialogTitle、DialogDescription、TagInput 的 suggestions
属性)和相应的测试断言。
In `@tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx`:
- Around line 44-49: Mock's totalTokens value is inconsistent with the cache
fields; update the test fixture in
tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx so
totalTokens equals the sum of cacheCreationInputTokens and cacheReadInputTokens
(and also include cacheCreation5mInputTokens and cacheCreation1hInputTokens if
those represent additional token counts) instead of the current hardcoded 2;
locate the object containing totalTokens, cacheCreationInputTokens,
cacheReadInputTokens, cacheCreation5mInputTokens and cacheCreation1hInputTokens
and set totalTokens to the correct summed value.
In `@tests/unit/dashboard-logs-warmup-ui.test.tsx`:
- Around line 108-128: The test fixture cacheLog (UsageLogRow) has totalTokens
set to 15 but that omits cache-related tokens; update the cacheLog object
(identifier: cacheLog) so totalTokens includes cacheCreationInputTokens and
cacheReadInputTokens in addition to inputTokens and outputTokens (i.e., compute
totalTokens = inputTokens + outputTokens + cacheCreationInputTokens +
cacheReadInputTokens) so the test matches the expected statistical counting for
cache creation/read.
In `@tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx`:
- Around line 61-77: The test for AvailabilityDashboard is incomplete and races
with the component's async fetchData in useEffect and leaves global stubs in
place; update tests that exercise key behaviors (tab switching, time-range
selection, loading state, error handling, auto-refresh and the "Probe All"
button visibility) by adding additional test cases for those flows and in each
async test await the component updates (use waitFor or findBy* against the
rendered AvailabilityDashboard) so assertions run after fetchData completes, and
ensure any global mocks created with vi.stubGlobal or fetch are restored after
each test (use vi.restoreAllMocks or vi.unstubGlobal) to avoid cross-test
pollution; reference the AvailabilityDashboard component and its
fetchData/useEffect behavior when implementing these changes.
🧹 Nitpick comments (7)
tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx (1)
22-59:renderWithIntl辅助函数实现良好,但建议小幅改进。此辅助函数正确设置了
NextIntlClientProvider和清理逻辑。两点建议:
- 可以考虑将此辅助函数提取到共享的测试工具文件中,以便其他测试复用。
- 建议添加
act的异步版本以更好地处理组件内的异步操作。src/components/ui/__tests__/tag-input-dialog.test.tsx (1)
1-3: 建议将测试文件移动到源文件同级目录
当前文件位于src/components/ui/__tests__/,但规范要求 source-adjacent 测试放在src/**/*.test.ts(x)与源文件同级。建议移动至例如src/components/ui/tag-input-dialog.test.tsx。As per coding guidelines, ...tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx (1)
164-175: 断言建议避免依赖 innerHTML 字符串。可直接查询带有 class 的元素,降低误匹配与脆弱性。
修改建议
- expect(container.innerHTML).toContain("ml-auto"); + expect(container.querySelector(".ml-auto")).not.toBeNull();tests/unit/dashboard-logs-warmup-ui.test.tsx (1)
152-154: 建议使用 DOM 查询替代 innerHTML 字符串断言。这样更稳健、也更精准。
修改建议
- expect(container.innerHTML).toContain("ml-auto"); + expect(container.querySelector(".ml-auto")).not.toBeNull();tests/unit/dashboard-logs-time-range-utils.test.ts (1)
48-60: 时区边界测试用例设计精妙!测试巧妙地选择了 UTC 时间
2024-01-02T02:00:00Z,在America/Los_Angeles时区(UTC-8)仍是2024-01-01,有效验证了跨日期边界的时区处理。建议考虑补充
last7days和last30days的测试用例,以提高覆盖率:📝 可选:补充测试用例
test("getQuickDateRange calculates last7days/last30days with timezone", () => { const now = new Date("2024-01-10T02:00:00Z"); const tz = "America/Los_Angeles"; expect(getQuickDateRange("last7days", tz, now)).toEqual({ startDate: "2024-01-03", endDate: "2024-01-09", }); expect(getQuickDateRange("last30days", tz, now)).toEqual({ startDate: "2023-12-11", endDate: "2024-01-09", }); });src/app/[locale]/my-usage/page.tsx (1)
44-46: 时区获取缺少错误处理反馈。当前实现在
getServerTimeZone失败时静默忽略错误。虽然时区是可选功能,但建议考虑在调试模式下记录错误,以便排查问题。♻️ 可选改进:添加错误日志
void getServerTimeZone().then((tzResult) => { - if (tzResult.ok) setServerTimeZone(tzResult.data.timeZone); + if (tzResult.ok) { + setServerTimeZone(tzResult.data.timeZone); + } else if (process.env.NODE_ENV === "development") { + console.warn("Failed to fetch server timezone:", tzResult.error); + } });src/app/[locale]/dashboard/logs/_components/logs-date-range-picker.tsx (1)
230-238: 考虑:导航禁用条件可能与服务器时区不一致第 234 行的
formatDate(new Date())使用客户端本地时间判断是否可以导航到"下一个"周期。如果serverTimeZone与客户端时区不同,可能会出现边界情况:服务器时区的"今天"可能与客户端的"今天"不一致。对于导航 UI 来说,这是一个次要的边界情况,当前行为是可接受的。如果需要严格一致性,可以考虑使用
serverTimeZone来计算禁用条件。
| @@ -1,5 +1,6 @@ | |||
| import { cache } from "react"; | |||
| import { ActiveSessionsCards } from "@/components/customs/active-sessions-cards"; | |||
| import { getEnvConfig } from "@/lib/config"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and examine the getEnvConfig implementation
fd -t f -e ts -e tsx "config" | grep -E "(lib|config)" | head -20Repository: ding113/claude-code-hub
Length of output: 795
🏁 Script executed:
# Check the specific file mentioned in the review
cat -n src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx | head -50Repository: ding113/claude-code-hub
Length of output: 1394
🏁 Script executed:
# Search for getEnvConfig definition and TZ usage
rg "getEnvConfig\s*=" -g '*.ts*' -A 5 -B 2Repository: ding113/claude-code-hub
Length of output: 49
🏁 Script executed:
# Search for TZ validation/normalization
rg "TZ|timezone" -g '*.ts*' -i | head -30Repository: ding113/claude-code-hub
Length of output: 3449
🏁 Script executed:
# Check getEnvConfig implementation
cat src/lib/config/config.tsRepository: ding113/claude-code-hub
Length of output: 276
🏁 Script executed:
# Find and check UsageLogsViewVirtualized
fd -t f "usage-logs-view-virtualized"Repository: ding113/claude-code-hub
Length of output: 142
🏁 Script executed:
# Check how time zone functions handle invalid values
rg "toZonedTime|formatInTimeZone|fromZonedTime" src/app/[locale]/dashboard/logs/_utils/time-range.ts -B 2 -A 2Repository: ding113/claude-code-hub
Length of output: 1336
🏁 Script executed:
# Check if there's any validation or error handling in time-range.ts
cat src/app/[locale]/dashboard/logs/_utils/time-range.tsRepository: ding113/claude-code-hub
Length of output: 3426
🏁 Script executed:
# Find env.schema.ts
fd -t f "env.schema"Repository: ding113/claude-code-hub
Length of output: 95
🏁 Script executed:
# Check UsageLogsViewVirtualized implementation
cat src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx | head -80Repository: ding113/claude-code-hub
Length of output: 3057
🏁 Script executed:
# Also check if there are any environment variable definitions
rg "TZ" -g '*.env*' -g '*.ts*' | grep -E "(env|ENV)" | head -20Repository: ding113/claude-code-hub
Length of output: 138
🏁 Script executed:
# Look for any zod schema or validation
rg "z\..*timezone|z\..*TZ" -g '*.ts*'Repository: ding113/claude-code-hub
Length of output: 49
🏁 Script executed:
# Check full env.schema.ts file
cat src/lib/config/env.schema.tsRepository: ding113/claude-code-hub
Length of output: 4914
🏁 Script executed:
# Check if there's any error handling or try-catch around date-fns-tz usage
rg "try|catch" src/app/[locale]/dashboard/logs/_utils/time-range.tsRepository: ding113/claude-code-hub
Length of output: 49
🏁 Script executed:
# Search for how serverTimeZone is used in the component
rg "serverTimeZone" src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx -A 2 -B 2Repository: ding113/claude-code-hub
Length of output: 515
🏁 Script executed:
# Check if date-fns-tz has any documentation or tests about timezone validation
fd -t f "*.test.ts*" | xargs rg "toZonedTime|formatInTimeZone" 2>/dev/null | head -20Repository: ding113/claude-code-hub
Length of output: 2328
确认 TZ 环境变量的 IANA 时区验证缺失。
当前 getEnvConfig() 中 TZ 字段仅定义为 z.string().default("Asia/Shanghai"),无任何 IANA 时区格式验证。若环境变量显式设置为无效值(如 TZ=InvalidTZ),会直接透传到 date-fns-tz 的 toZonedTime、formatInTimeZone 等函数,这些函数会抛出 RangeError。time-range.ts 和 time-filters.tsx 中对这些函数的调用均无 try-catch 保护,将导致日志页面崩溃。
建议:
- 在 env.schema.ts 中为 TZ 添加 IANA 时区白名单或正则验证;或
- 在调用 date-fns-tz 函数处增加 try-catch,并在捕获异常时回退到
undefined或"UTC"。
🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-sections.tsx at line
3, 当前缺少对 TZ 环境变量的 IANA 时区验证,导致无效 TZ 值会传到 date-fns-tz(例如
toZonedTime、formatInTimeZone)并抛出 RangeError,从而在 time-range.ts 和 time-filters.tsx
中未被捕获而使页面崩溃;请在 env.schema.ts 中为 TZ(当前由 getEnvConfig() 提供)添加 IANA
白名单或正则校验并设默认值,或者在所有调用 date-fns-tz 的位置(例如在 time-range.ts 的相关函数和 time-filters.tsx
中使用 formatInTimeZone/toZonedTime 的代码段)用 try-catch 包裹并在捕获异常时回退到安全值(undefined 或
"UTC")并记录错误。
| <DialogTitle>Tag Input</DialogTitle> | ||
| <DialogDescription>Tag input dialog test</DialogDescription> | ||
| </DialogHeader> | ||
| <TagInput | ||
| value={value} | ||
| onChange={setValue} | ||
| suggestions={[ | ||
| { value: "tag1", label: "Tag 1" }, | ||
| { value: "tag2", label: "Tag 2" }, | ||
| ]} |
There was a problem hiding this comment.
对话框与建议项的展示文案请改为 i18n 资源
DialogTitle、DialogDescription 以及建议项的 label/value 都是用户可见文本,需从 i18n 资源中读取,避免硬编码。断言可改为基于 i18n 输出或使用 data-testid。As per coding guidelines, ...
🤖 Prompt for AI Agents
In `@src/components/ui/__tests__/tag-input-dialog.test.tsx` around lines 50 - 59,
将测试中对用户可见文本的硬编码替换为 i18n 资源:把 DialogTitle、DialogDescription 以及 TagInput 的
suggestions 中的 label 和 value 从硬编码字符串改为从项目 i18n 翻译函数或资源对象读取(例如通过 t('...') 或导入的
locales),并更新断言以基于翻译后的输出或更稳健地使用 data-testid 查询(例如 为 DialogTitle、DialogDescription
或 suggestion 项添加 data-testid 并在测试中使用这些 id
进行断言);修改点集中在组件使用处(DialogTitle、DialogDescription、TagInput 的 suggestions
属性)和相应的测试断言。
| cacheCreationInputTokens: 10, | ||
| cacheReadInputTokens: 5, | ||
| cacheCreation5mInputTokens: 10, | ||
| cacheCreation1hInputTokens: 0, | ||
| cacheTtlApplied: null, | ||
| cacheTtlApplied: "1h", | ||
| totalTokens: 2, |
There was a problem hiding this comment.
mock 中 totalTokens 与 cache 相关字段不一致。
现在 cacheCreationInputTokens=10、cacheReadInputTokens=5,totalTokens 应包含这些值;否则与真实统计口径不符。
修改建议
- totalTokens: 2,
+ totalTokens: 17,Based on learnings, totalTokens 需要包含 cacheCreationInputTokens 与 cacheReadInputTokens。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cacheCreationInputTokens: 10, | |
| cacheReadInputTokens: 5, | |
| cacheCreation5mInputTokens: 10, | |
| cacheCreation1hInputTokens: 0, | |
| cacheTtlApplied: null, | |
| cacheTtlApplied: "1h", | |
| totalTokens: 2, | |
| cacheCreationInputTokens: 10, | |
| cacheReadInputTokens: 5, | |
| cacheCreation5mInputTokens: 10, | |
| cacheCreation1hInputTokens: 0, | |
| cacheTtlApplied: "1h", | |
| totalTokens: 17, |
🤖 Prompt for AI Agents
In `@tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx` around
lines 44 - 49, Mock's totalTokens value is inconsistent with the cache fields;
update the test fixture in
tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx so
totalTokens equals the sum of cacheCreationInputTokens and cacheReadInputTokens
(and also include cacheCreation5mInputTokens and cacheCreation1hInputTokens if
those represent additional token counts) instead of the current hardcoded 2;
locate the object containing totalTokens, cacheCreationInputTokens,
cacheReadInputTokens, cacheCreation5mInputTokens and cacheCreation1hInputTokens
and set totalTokens to the correct summed value.
| const cacheLog: UsageLogRow = { | ||
| id: 2, | ||
| createdAt: new Date(), | ||
| sessionId: "session_cache", | ||
| requestSequence: 1, | ||
| userName: "user", | ||
| keyName: "key", | ||
| providerName: "provider", | ||
| model: "claude-sonnet-4-5-20250929", | ||
| originalModel: "claude-sonnet-4-5-20250929", | ||
| endpoint: "/v1/messages", | ||
| statusCode: 200, | ||
| inputTokens: 10, | ||
| outputTokens: 5, | ||
| cacheCreationInputTokens: 10, | ||
| cacheReadInputTokens: 5, | ||
| cacheCreation5mInputTokens: 10, | ||
| cacheCreation1hInputTokens: 0, | ||
| cacheTtlApplied: "1h", | ||
| totalTokens: 15, | ||
| costUsd: "0.000001", |
There was a problem hiding this comment.
cacheLog 的 totalTokens 需要包含缓存读写 token。
当前 totalTokens=15 只反映 input/output,建议按统计口径补上 cacheCreation/cacheRead。
修改建议
- totalTokens: 15,
+ totalTokens: 30,Based on learnings, totalTokens 需要包含 cacheCreationInputTokens 与 cacheReadInputTokens。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const cacheLog: UsageLogRow = { | |
| id: 2, | |
| createdAt: new Date(), | |
| sessionId: "session_cache", | |
| requestSequence: 1, | |
| userName: "user", | |
| keyName: "key", | |
| providerName: "provider", | |
| model: "claude-sonnet-4-5-20250929", | |
| originalModel: "claude-sonnet-4-5-20250929", | |
| endpoint: "/v1/messages", | |
| statusCode: 200, | |
| inputTokens: 10, | |
| outputTokens: 5, | |
| cacheCreationInputTokens: 10, | |
| cacheReadInputTokens: 5, | |
| cacheCreation5mInputTokens: 10, | |
| cacheCreation1hInputTokens: 0, | |
| cacheTtlApplied: "1h", | |
| totalTokens: 15, | |
| costUsd: "0.000001", | |
| const cacheLog: UsageLogRow = { | |
| id: 2, | |
| createdAt: new Date(), | |
| sessionId: "session_cache", | |
| requestSequence: 1, | |
| userName: "user", | |
| keyName: "key", | |
| providerName: "provider", | |
| model: "claude-sonnet-4-5-20250929", | |
| originalModel: "claude-sonnet-4-5-20250929", | |
| endpoint: "/v1/messages", | |
| statusCode: 200, | |
| inputTokens: 10, | |
| outputTokens: 5, | |
| cacheCreationInputTokens: 10, | |
| cacheReadInputTokens: 5, | |
| cacheCreation5mInputTokens: 10, | |
| cacheCreation1hInputTokens: 0, | |
| cacheTtlApplied: "1h", | |
| totalTokens: 30, | |
| costUsd: "0.000001", |
🤖 Prompt for AI Agents
In `@tests/unit/dashboard-logs-warmup-ui.test.tsx` around lines 108 - 128, The
test fixture cacheLog (UsageLogRow) has totalTokens set to 15 but that omits
cache-related tokens; update the cacheLog object (identifier: cacheLog) so
totalTokens includes cacheCreationInputTokens and cacheReadInputTokens in
addition to inputTokens and outputTokens (i.e., compute totalTokens =
inputTokens + outputTokens + cacheCreationInputTokens + cacheReadInputTokens) so
the test matches the expected statistical counting for cache creation/read.
| describe("AvailabilityDashboard UI", () => { | ||
| test("does not render Probe All floating button", async () => { | ||
| vi.stubGlobal( | ||
| "fetch", | ||
| vi.fn(async () => ({ | ||
| ok: true, | ||
| json: async () => ({ providers: [], systemAvailability: 0 }), | ||
| })) | ||
| ); | ||
|
|
||
| const { container, unmount } = renderWithIntl(<AvailabilityDashboard />); | ||
|
|
||
| expect(container.textContent).not.toContain("Probe All"); | ||
|
|
||
| unmount(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
测试覆盖率不足,且异步操作处理存在问题。
-
测试覆盖率:当前只有一个测试用例,
AvailabilityDashboard组件包含多个功能(标签切换、时间范围选择、加载状态、错误处理、自动刷新等),这可能无法满足 80% 覆盖率要求。 -
异步操作未正确等待:组件在
useEffect中调用fetchData,但测试在渲染后立即检查 DOM,没有等待异步操作完成。建议使用waitFor或findBy等待状态更新。 -
缺少测试清理:
vi.stubGlobal在测试后未恢复,可能污染其他测试。
建议的修复方案
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
-import { describe, expect, test, vi } from "vitest";
describe("AvailabilityDashboard UI", () => {
+ beforeEach(() => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn(async () => ({
+ ok: true,
+ json: async () => ({ providers: [], systemAvailability: 0 }),
+ }))
+ );
+ });
+
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
test("does not render Probe All floating button", async () => {
- vi.stubGlobal(
- "fetch",
- vi.fn(async () => ({
- ok: true,
- json: async () => ({ providers: [], systemAvailability: 0 }),
- }))
- );
-
const { container, unmount } = renderWithIntl(<AvailabilityDashboard />);
+ // 等待异步 fetch 完成
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+
expect(container.textContent).not.toContain("Probe All");
unmount();
});
+
+ // 建议添加更多测试用例以提高覆盖率
+ test("renders provider and endpoint tabs", async () => {
+ const { container, unmount } = renderWithIntl(<AvailabilityDashboard />);
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+
+ expect(container.querySelector('[data-testid="overview-section"]')).toBeTruthy();
+ expect(container.textContent).toContain("Provider");
+ expect(container.textContent).toContain("Endpoint");
+
+ unmount();
+ });
});根据 coding guidelines,所有新功能必须有至少 80% 的单元测试覆盖率。
🤖 Prompt for AI Agents
In `@tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx` around
lines 61 - 77, The test for AvailabilityDashboard is incomplete and races with
the component's async fetchData in useEffect and leaves global stubs in place;
update tests that exercise key behaviors (tab switching, time-range selection,
loading state, error handling, auto-refresh and the "Probe All" button
visibility) by adding additional test cases for those flows and in each async
test await the component updates (use waitFor or findBy* against the rendered
AvailabilityDashboard) so assertions run after fetchData completes, and ensure
any global mocks created with vi.stubGlobal or fetch are restored after each
test (use vi.restoreAllMocks or vi.unstubGlobal) to avoid cross-test pollution;
reference the AvailabilityDashboard component and its fetchData/useEffect
behavior when implementing these changes.
Change token-related columns in message_request table from integer to bigint to prevent overflow when storing large token counts. Also update all SQL aggregation queries to use double precision instead of ::int casting to avoid overflow during SUM operations. Changes: - Schema: inputTokens, outputTokens, cacheCreationInputTokens, cacheReadInputTokens, cacheCreation5mInputTokens, cacheCreation1hInputTokens now use bigint - Schema: providerVendorId is now NOT NULL - Queries: All token SUM operations use ::double precision - Tests: Add unit tests to verify no ::int casting in token aggregations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/drizzle/schema.ts`:
- Around line 155-159: The schema adds a NOT NULL constraint to providerVendorId
(references providerVendors.id) but the migration 0057_conscious_quicksilver.sql
doesn't apply that change; update that migration to make the DB match the code
by (a) handling existing NULLs in the providers table (e.g., UPDATE providers
SET provider_vendor_id = <valid_id> OR delete/abort if business rules require)
and then (b) executing ALTER TABLE providers ALTER COLUMN provider_vendor_id SET
NOT NULL (and ensure the foreign key exists with ON DELETE RESTRICT), or
alternatively remove .notNull() from providerVendorId in the schema if you
intend to allow NULLs—modify either the migration or the schema accordingly so
providerVendorId and providerVendors.id stay consistent.
| providerVendorId: integer('provider_vendor_id') | ||
| .notNull() | ||
| .references(() => providerVendors.id, { | ||
| onDelete: 'restrict', | ||
| }), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f '0057_conscious_quicksilver.sql' --search-path .Repository: ding113/claude-code-hub
Length of output: 107
🏁 Script executed:
find . -name '*0057*' -type fRepository: ding113/claude-code-hub
Length of output: 141
🏁 Script executed:
find . -path '*/drizzle/*' -name '*.sql' -type f | sortRepository: ding113/claude-code-hub
Length of output: 2248
🏁 Script executed:
cat ./drizzle/0057_conscious_quicksilver.sqlRepository: ding113/claude-code-hub
Length of output: 729
🏁 Script executed:
cat -n ./src/drizzle/schema.ts | head -200 | tail -60Repository: ding113/claude-code-hub
Length of output: 2683
🏁 Script executed:
rg -A 5 -B 5 "providerVendorId" ./src/drizzle/schema.tsRepository: ding113/claude-code-hub
Length of output: 1332
迁移文件缺少 providerVendorId NOT NULL 约束
providerVendorId 的 .notNull() 约束是破坏性变更,但迁移文件 0057_conscious_quicksilver.sql 中仅包含 message_request 表的变更,未包含对 providers 表的任何修改。
需要更新迁移文件以包含 provider_vendor_id 列的 NOT NULL 约束,或者在约束生效前确保所有现有记录都已关联 vendor(无 NULL 值)。
🤖 Prompt for AI Agents
In `@src/drizzle/schema.ts` around lines 155 - 159, The schema adds a NOT NULL
constraint to providerVendorId (references providerVendors.id) but the migration
0057_conscious_quicksilver.sql doesn't apply that change; update that migration
to make the DB match the code by (a) handling existing NULLs in the providers
table (e.g., UPDATE providers SET provider_vendor_id = <valid_id> OR
delete/abort if business rules require) and then (b) executing ALTER TABLE
providers ALTER COLUMN provider_vendor_id SET NOT NULL (and ensure the foreign
key exists with ON DELETE RESTRICT), or alternatively remove .notNull() from
providerVendorId in the schema if you intend to allow NULLs—modify either the
migration or the schema accordingly so providerVendorId and providerVendors.id
stay consistent.
Extend deriveDisplayNameFromDomain to skip common API prefixes (api, v1, v2, v3, www) when extracting display names from domains. This fixes handling of multi-prefix domains like v1.api.anthropic.com. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| let name = parts[parts.length - 2]; | ||
| if (apiPrefixes.has(name) && parts.length >= 3) { | ||
| name = parts[parts.length - 3]; | ||
| } |
There was a problem hiding this comment.
Changed from checking first part to second-to-last part - this was noted in a previous thread as potentially breaking for api.openai.com where the original logic expected parts[0] === "api" but new logic checks parts[parts.length - 2] which would be "openai" not "api". Verify this is the intended behavior and all existing provider endpoints still derive correct display names after this change.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 246:249
Comment:
Changed from checking first part to second-to-last part - this was noted in a previous thread as potentially breaking for `api.openai.com` where the original logic expected `parts[0] === "api"` but new logic checks `parts[parts.length - 2]` which would be "openai" not "api". Verify this is the intended behavior and all existing provider endpoints still derive correct display names after this change.
How can I resolve this? If you propose a fix, please make it concise.- Refactor DashboardBento to use two-column layout with fixed-width sidebar (300px) for LiveSessionsPanel on admin view - Update DashboardMain to remove max-w-7xl constraint on dashboard home page for wider content area - Simplify ActiveSessionsSkeleton to match compact list style - Add showTokensCost prop to SessionListItem and ActiveSessionsList for conditional token/cost display - Fix router import to use i18n routing in ActiveSessionsList - Add unit tests for layout behavior and SessionListItem props Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| key: varchar('key').notNull(), | ||
| providerVendorId: integer('provider_vendor_id').references(() => providerVendors.id, { | ||
| onDelete: 'restrict', | ||
| }), | ||
| providerVendorId: integer('provider_vendor_id') | ||
| .notNull() | ||
| .references(() => providerVendors.id, { |
There was a problem hiding this comment.
Check that all existing providers have valid providerVendorId values before deploying - the new .notNull() constraint will fail if any NULL values exist in production.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/drizzle/schema.ts
Line: 154:157
Comment:
Check that all existing providers have valid `providerVendorId` values before deploying - the new `.notNull()` constraint will fail if any NULL values exist in production.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx:
- Around line 22-23: The pathname check in isDashboardHomePage is overly
permissive; replace the endsWith check with a strict equality: use the
normalizedPathname exact match only (change the condition in the
isDashboardHomePage declaration that currently references
normalizedPathname.endsWith("/dashboard") so it only compares normalizedPathname
=== "/dashboard"). This simplifies the logic tied to usePathname() behavior and
the routing setup in src/i18n/routing.ts without altering other code paths.
🧹 Nitpick comments (5)
src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx (1)
260-313: 使用cn()工具函数来组合 className,保持项目一致性。项目已在
src/lib/utils/cn.ts中定义了cn()工具函数,并在整个代码库(UI 组件、设置页面等)中广泛使用。当前代码使用.join(" ")与项目约定不一致,建议导入并使用cn()函数。♻️ 可选重构
+import { cn } from "@/lib/utils"; + export function DashboardBento({- <div - className={[ - "grid gap-4 md:gap-6", - "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", - "auto-rows-[minmax(140px,auto)]", - ].join(" ")} - > + <div + className={cn( + "grid gap-4 md:gap-6", + "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", + "auto-rows-[minmax(140px,auto)]" + )} + >tests/unit/dashboard/dashboard-home-layout.test.tsx (3)
138-146:findClosestWithClasses函数存在空指针风险。在
while循环中,current在条件判断后可能为null,但current.classList的访问没有进行空值检查。虽然while (current)确保了循环体内current不为null,但 TypeScript 的类型推断可能会导致问题。♻️ 建议的改进以提高类型安全性
function findClosestWithClasses(element: Element | null, classes: string[]) { let current = element?.parentElement ?? null; while (current) { - const hasAll = classes.every((cls) => current.classList.contains(cls)); + const el = current; + const hasAll = classes.every((cls) => el.classList.contains(cls)); if (hasAll) return current; current = current.parentElement; } return null; }
218-219: 测试选择器过于脆弱。使用完整的 Tailwind 类名
lg:grid-cols-[minmax(0,1fr)_300px]作为选择器会使测试对样式实现细节高度敏感。如果布局类名稍有变化,测试就会失败。建议在被测组件中添加
data-testid属性来提高测试稳定性。♻️ 建议的改进
在
DashboardBento组件中添加:<div data-testid="dashboard-grid" className="lg:grid-cols-[minmax(0,1fr)_300px] ...">然后在测试中使用:
- const grid = findByClassToken(container, "lg:grid-cols-[minmax(0,1fr)_300px]"); + const grid = container.querySelector('[data-testid="dashboard-grid"]'); expect(grid).toBeTruthy();
173-204: 建议增加测试覆盖率。当前测试覆盖了
/dashboard和/dashboard/logs路径,但缺少以下场景的测试:
- 带尾部斜杠的路径:验证
normalizedPathname逻辑,如/dashboard/- session messages 页面:验证
isSessionMessagesPage分支,如/dashboard/sessions/123/messages♻️ 建议添加的测试用例
test("pathname /dashboard/ with trailing slash removes max-w-7xl", () => { routingMocks.usePathname.mockReturnValue("/dashboard/"); const { container, unmount } = renderSimple( <DashboardMain> <div data-testid="content" /> </DashboardMain> ); const main = container.querySelector("main"); expect(main?.className).toContain("px-6"); expect(main?.className).not.toContain("max-w-7xl"); unmount(); }); test("pathname /dashboard/sessions/[id]/messages uses overflow-hidden layout", () => { routingMocks.usePathname.mockReturnValue("/dashboard/sessions/abc123/messages"); const { container, unmount } = renderSimple( <DashboardMain> <div data-testid="content" /> </DashboardMain> ); const main = container.querySelector("main"); expect(main?.className).toContain("overflow-hidden"); expect(main?.className).not.toContain("max-w-7xl"); unmount(); });tests/unit/components/session-list-item.test.tsx (1)
41-49: 可移除重复类型与强转,降低类型漂移风险。直接使用组件即可,无需本地 props 类型与强制类型转换。
Proposed refactor
-type SessionListItemProps = { - session: ActiveSessionInfo; - currencyCode?: CurrencyCode; - showTokensCost?: boolean; -}; - -const SessionListItemTest = SessionListItem as unknown as ( - props: SessionListItemProps -) => JSX.Element; +const SessionListItemTest = SessionListItem; @@ - const html = renderToStaticMarkup( - <SessionListItemTest session={session} showTokensCost={options?.showTokensCost} /> - ); + const html = renderToStaticMarkup( + <SessionListItemTest session={session} showTokensCost={options?.showTokensCost} /> + );Also applies to: 74-75
| const isDashboardHomePage = | ||
| normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard"); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the dashboard-main.tsx file
cat -n src/app/[locale]/dashboard/_components/dashboard-main.tsx | head -40Repository: ding113/claude-code-hub
Length of output: 1518
🏁 Script executed:
# Check how usePathname is used in the codebase and what it returns
rg "usePathname" --type=ts --type=tsx -B 2 -A 5 | head -80Repository: ding113/claude-code-hub
Length of output: 94
🏁 Script executed:
# Look for the next-intl hook to understand what it returns
fd -e ts -e tsx | xargs grep -l "from.*next-intl" | head -10Repository: ding113/claude-code-hub
Length of output: 590
🏁 Script executed:
# Let's find the routing configuration to understand the actual routes
cat -n src/i18n/routing.ts 2>/dev/null || cat -n src/i18n/routing.tsx 2>/dev/null || echo "File not found"Repository: ding113/claude-code-hub
Length of output: 1622
🏁 Script executed:
# Check the app directory structure to see what dashboard routes exist
fd -path "*/dashboard/*" -type f | head -20Repository: ding113/claude-code-hub
Length of output: 235
🏁 Script executed:
# Search for all files in the dashboard directory to understand the structure
find src/app/\[locale\]/dashboard -type f -name "*.ts*" | sortRepository: ding113/claude-code-hub
Length of output: 12527
🏁 Script executed:
# Look for route segments that might match endsWith("/dashboard")
rg "dashboard" src/app --type=ts --type=tsx -lRepository: ding113/claude-code-hub
Length of output: 94
🏁 Script executed:
# Let's check if there are any actual routes that end with /dashboard besides /dashboard itself
find src/app/\[locale\]/dashboard -type d | sed 's|src/app/\[locale\]||g' | grep -v "/_"Repository: ding113/claude-code-hub
Length of output: 407
🏁 Script executed:
# Let's look at the exact route structure to confirm what paths usePathname would return
cat -n src/app/\[locale\]/dashboard/page.tsx | head -20Repository: ding113/claude-code-hub
Length of output: 947
🏁 Script executed:
# Let's check if there are any other segment patterns that could produce paths ending in /dashboard
rg "/dashboard" src/app --type=ts -B 2 -A 2 | head -60Repository: ding113/claude-code-hub
Length of output: 5210
路径匹配条件可以简化为精确匹配。
normalizedPathname.endsWith("/dashboard") 在当前的路由结构中是冗余的。根据 src/i18n/routing.ts 的配置和实际的路由结构,usePathname() 返回不含 locale 前缀的路径,且没有其他路由以 "/dashboard" 结尾(所有子页面形式为 /dashboard/availability、/dashboard/sessions 等)。因此可以简化为仅使用精确匹配:
♻️ 建议的改进
const isDashboardHomePage =
- normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard");
+ normalizedPathname === "/dashboard";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const isDashboardHomePage = | |
| normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard"); | |
| const isDashboardHomePage = | |
| normalizedPathname === "/dashboard"; |
🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx around lines 22 -
23, The pathname check in isDashboardHomePage is overly permissive; replace the
endsWith check with a strict equality: use the normalizedPathname exact match
only (change the condition in the isDashboardHomePage declaration that currently
references normalizedPathname.endsWith("/dashboard") so it only compares
normalizedPathname === "/dashboard"). This simplifies the logic tied to
usePathname() behavior and the routing setup in src/i18n/routing.ts without
altering other code paths.
Add overflow constraints to LogicTraceTab and StepCard components: - Add min-w-0 to grid containers to allow content shrinking - Add break-all to font-mono text for long IDs and URLs - Add flex-wrap and truncate for provider name badges - Add overflow-hidden to StepCard details container Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add max-w-7xl constraint to dashboard home container for consistent alignment with header - Restructure dashboard-bento into 3 independent sections: metrics, chart, leaderboards - Remove nested grid structure that caused misalignment on desktop - Change admin layout from 2-column (content + sidebar) to 4-column grid (3 leaderboards + live sessions) - Remove colSpan/rowSpan from StatisticsChartCard for full-width independent rendering - Add min-h-[280px] to LeaderboardCard for consistent card heights - Update tests to match new layout structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
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/bento/dashboard-bento.tsx (1)
260-309:data-testid不会被转发到 DOM
LiveSessionsPanel组件的LiveSessionsPanelProps接口(第 17-22 行)仅定义了sessions、isLoading、maxItems、className这四个属性。组件在渲染BentoCard时(第 165-176 行)也只显式传入这些属性,未使用 spread 操作符转发其他 props。因此,从父组件传入的data-testid="dashboard-home-sidebar"会被忽略,不会出现在 DOM 中,导致该测试选择器失效。建议:
- 在
LiveSessionsPanelProps中添加data-testid属性并转发到BentoCard;或- 将
data-testid放在调用LiveSessionsPanel的外层div上
🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx:
- Around line 29-33: The conditional using isDashboardHomePage is redundant
because both branches return identical JSX; either remove the conditional and
return a single <main> with the common classes/children (delete the
isDashboardHomePage check and its branch), or implement the intended homepage
variant by changing the true-branch to the proper JSX/styles (modify the code
where isDashboardHomePage is evaluated so the true branch renders the special
homepage layout/styling and the false branch renders the existing default
<main>); locate the isDashboardHomePage check in dashboard-main.tsx and update
the return(s) accordingly.
🧹 Nitpick comments (2)
tests/unit/dashboard/dashboard-home-layout.test.tsx (2)
78-130: 建议抽取共享挂载逻辑以减少重复。
renderSimple与renderWithProviders里创建/卸载容器逻辑一致,可以提取到一个小 helper。可选重构示例
+function createTestRoot() { + const container = document.createElement("div"); + document.body.appendChild(container); + const root = createRoot(container); + const unmount = () => { + act(() => root.unmount()); + container.remove(); + }; + return { container, root, unmount }; +} + function renderSimple(node: ReactNode) { - const container = document.createElement("div"); - document.body.appendChild(container); - const root = createRoot(container); + const { container, root, unmount } = createTestRoot(); act(() => { root.render(node); }); return { container, - unmount: () => { - act(() => root.unmount()); - container.remove(); - }, + unmount, }; } function renderWithProviders(node: ReactNode) { - const container = document.createElement("div"); - document.body.appendChild(container); - const root = createRoot(container); + const { container, root, unmount } = createTestRoot(); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false, @@ return { container, - unmount: () => { - act(() => root.unmount()); - container.remove(); - }, + unmount, }; }
138-146: 未使用的 helper 可考虑移除。
findClosestWithClasses目前未被使用,若无计划用途可删掉以保持测试文件简洁。可选清理示例
-function findClosestWithClasses(element: Element | null, classes: string[]) { - let current = element?.parentElement ?? null; - while (current) { - const hasAll = classes.every((cls) => current.classList.contains(cls)); - if (hasAll) return current; - current = current.parentElement; - } - return null; -}
- Remove redundant isDashboardHomePage conditional branch (now same as default) - Remove trailing period from vendorAggregationRule for style consistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| function deriveDisplayNameFromDomain(domain: string): string { | ||
| const parts = domain.split("."); | ||
| const name = parts[0] === "api" && parts[1] ? parts[1] : parts[0]; | ||
| export async function deriveDisplayNameFromDomain(domain: string): Promise<string> { |
There was a problem hiding this comment.
function changed to async but doesn't use any async operations - consider removing async/await unless planning future async operations
| export async function deriveDisplayNameFromDomain(domain: string): Promise<string> { | |
| export function deriveDisplayNameFromDomain(domain: string): string { |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 234:234
Comment:
function changed to `async` but doesn't use any async operations - consider removing async/await unless planning future async operations
```suggestion
export function deriveDisplayNameFromDomain(domain: string): string {
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Summary
This PR enhances the dashboard UI with multiple improvements including server timezone support for logs filtering, provider chain display enhancements, TagInput dropdown positioning fixes inside dialogs, and provider vendor view UX improvements.
Changes
Core Changes
Server Timezone Support for Logs Filtering
getServerTimeZone()server action to expose the server'sTZenvironment variabledate-fns-tzserverTimeZoneprop through logs filter components (TimeFilters,LogsDateRangePicker,UsageLogsFilters)Provider Chain Popover Improvements
excluded,format_type_mismatch,type_mismatch,model_not_allowed,context_1m_disabledvendorAggregationRulei18n string explaining vendor grouping logicTagInput Dropdown Positioning Fix
[data-slot="dialog-content"]containerProvider Vendor View Enhancements
deriveDisplayNameFromDomainto properly extract display name from domains likeco.yes.vgCache Badge Alignment Fix
1h,5m) to the left side of the cellml-autoclassUsageLogsTable,VirtualizedLogsTable, and my-usage tableAvailability Dashboard Cleanup
FloatingProbeButtoncomponent from availability dashboardSupporting Changes
i18n Updates (5 languages: en, ja, ru, zh-CN, zh-TW)
vendorAggregationRuletranslation for provider settingsTest Coverage
provider-chain-popover.test.tsx: Added test for multiple deduped group badgestag-input-dialog.test.tsx: New test file for TagInput inside Dialog scenariosdashboard-logs-time-range-utils.test.ts: Added test for timezone-aware quick date rangesdashboard-logs-virtualized-special-settings-ui.test.tsx: Added cache badge alignment testdashboard-logs-warmup-ui.test.tsx: Added cache badge alignment testavailability-dashboard-ui.test.tsx: New test verifying probe button removalprovider-endpoints-display-name.test.ts: New test for domain name derivationprovider-vendor-view-circuit-ui.test.tsx: Added tests for vendor list filtering and endpoint toggleBreaking Changes
None. All changes are backward compatible.
Testing
Automated Tests
Manual Testing
TZto a different timezone, verify "Today" filter shows correct dateChecklist
Description enhanced by Claude AI
Greptile Overview
Greptile Summary
This PR enhances the dashboard UI with multiple well-implemented improvements focused on internationalization, timezone handling, and UX refinements.
Key Changes
Server Timezone Support: Added timezone-aware date/time conversion using
date-fns-tzfor accurate logs filtering. The implementation properly usestoZonedTimeandfromZonedTimeto handle server timezone differences, ensuring "Today" and "Yesterday" filters work correctly regardless of server location.TagInput Dialog Positioning Fix: Solved dropdown positioning issues when TagInput is rendered inside Dialog components by detecting the dialog container and switching from fixed to absolute positioning with proper scroll event handling.
Domain Name Derivation Improvement: Enhanced
deriveDisplayNameFromDomainto handle complex TLDs (likeco.yes.vg) by parsing from right-to-left and supporting multiple API prefix patterns (api,v1,v2,v3,www). Test coverage confirms backward compatibility with existing domains.Token Overflow Prevention: Changed token count columns from
integertobigintto prevent overflow for large token values, addressing a real production concern for high-volume usage.Provider Vendor UX: Added inline endpoint toggle switches, removed unnecessary label fields, added vendor aggregation tooltips, and hide empty vendor cards for cleaner UI.
Cache Badge Alignment: Improved visual consistency by moving TTL badges to the left with token numbers right-aligned.
i18n Coverage: Added translations for 5 new provider exclusion reasons across all 5 supported languages.
Test Coverage
Excellent test coverage with new test files for all major changes:
tag-input-dialog.test.tsx- Dialog positioning scenariosprovider-endpoints-display-name.test.ts- Domain derivation logicdashboard-logs-time-range-utils.test.ts- Timezone-aware date rangesAll changes are backward compatible with no breaking changes.
Confidence Score: 5/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant Client as Browser Client participant Server as Next.js Server participant Action as System Config Action participant TimeUtils as Time Range Utils participant DB as PostgreSQL DB Note over Client,DB: Server Timezone Support Flow Client->>Server: Load Dashboard Page Server->>Action: getServerTimeZone() Action->>Action: Read TZ env variable Action-->>Server: Return timezone string Server-->>Client: Render with serverTimeZone prop Client->>Client: User selects "Today" filter Client->>TimeUtils: getQuickDateRange("today", serverTimeZone) TimeUtils->>TimeUtils: toZonedTime(now, timeZone) TimeUtils->>TimeUtils: formatInTimeZone(date, timeZone) TimeUtils-->>Client: Return {startDate, endDate} Client->>TimeUtils: dateStringWithClockToTimestamp(date, clock, timezone) TimeUtils->>TimeUtils: fromZonedTime(date, timeZone) TimeUtils-->>Client: Return timestamp Client->>DB: Query logs with timestamp range DB-->>Client: Return filtered logs Note over Client,DB: Tag Input in Dialog Flow Client->>Client: Open Dialog with TagInput Client->>Client: User focuses input Client->>Client: Detect [data-slot="dialog-content"] Client->>Client: Calculate dropdown position alt Inside Dialog Client->>Client: Use absolute positioning Client->>Client: Listen to dialog scroll events else Outside Dialog Client->>Client: Use fixed positioning Client->>Client: Listen to window scroll events end Client->>Client: Render dropdown suggestions Note over Client,DB: Provider Vendor Display Flow Client->>DB: Fetch provider endpoints DB-->>Client: Return endpoints with vendorId alt Vendor has providers Client->>Client: Show vendor card Client->>Client: Render endpoint rows with inline toggle else Vendor has no providers Client->>Client: Hide vendor card end Client->>Client: User toggles endpoint switch Client->>Server: editProviderEndpoint(endpointId, isEnabled) Server->>DB: UPDATE provider_endpoints DB-->>Server: Success Server-->>Client: Invalidate cache & refresh