Skip to content

Conversation

@YangQing-Lin
Copy link
Collaborator

@YangQing-Lin YangQing-Lin commented Jan 14, 2026

背景 / 目标

本 PR 覆盖 /dashboard/logs(Usage Logs)页面的两类增强,并根据 2026-01-15 的 Code Review 结论修复关键回归点:

  1. 时间筛选从“按天”升级为“精确到秒”,并保留现有日期快捷按钮与原有 URL 语义(startTime/endTime 仍为毫秒;后端仍使用半开区间 created_at < endTime)。
  2. 新增 sessionId 精确筛选:前端提供联想下拉(模糊候选),但最终查询条件是精确匹配(trim 后非空才生效)。
  3. 表格新增 “Session ID” 列(截断 + tooltip + 点击复制),虚拟/非虚拟两套表格均同步。
  4. 修复两处会导致用户明显踩坑的回归:Apply Filter 的分页 page 泄漏、联想请求去重忽略 scope 变化。

主要改动

后端(Repository / Actions)

  • Repo 层统一追加 sessionId 精确筛选(trim 后非空才生效):batch/details/stats 三条链路保持一致。
  • 新增 sessionId 联想查询:ILIKE '%term%' 模糊匹配 + MIN(created_at) 排序 + limit clamp;非 admin 强制 userId 边界,admin 可跟随 userId/keyId/providerId 收敛候选集。
  • 联想查询优化:仅当 keyId 过滤需要生效时才 join keysTable,避免无意义 join 与潜在候选集丢失。

前端(Dashboard Logs)

  • Filters 面板:
    • 新增 sessionId 输入联想;修复输入 focus 行为。
    • 增加“到秒”的 start/end time 输入,仍保留日期范围选择器;UI 的“包含式 end”在落到后端前转换为排他上界(+1s)。
    • 修复 Apply 时 filters 污染导致的 page 泄漏:在 handleApply 输出前做字段白名单化,丢弃多余字段。
    • 修复联想请求去重:去重 key 纳入 scope(term|userId|keyId|providerId|isAdmin),scope 变化会触发重新请求刷新列表。
  • URL:抽出统一的 parse/build 工具,虚拟/非虚拟两条 view 同步使用;并统一数字字段语义(避免 0 被吞掉、page 参数脏值穿透)。
  • 表格:虚拟/非虚拟两套表格插入 “Session ID” 列(截断、tooltip、点击复制 toast)。
image

文档 & 测试

  • 新增 logs 页面调用链盘点文档,便于 Review 与后续定位:docs/dashboard-logs-callchain.md
  • 新增专项覆盖率配置与脚本:
    • vitest.logs-sessionid-time-filter.config.ts
    • package.jsontest:coverage:logs-sessionid-time-filter
    • .gitignore 忽略 coverage-logs-sessionid-time-filter
  • 补齐/增强单测(repo/utils/UI 行为),并修复“空 sessionId 不追加条件”的假绿断言。

修复的回归点(Code Review 2026-01-15)

  1. Apply Filter 不再携带旧 page(修根因:filters 运行时对象可能携带 page,需白名单化输出)。
  2. sessionId 联想在 term 不变但 scope 变化(userId/keyId/providerId/isAdmin)时会重新请求并刷新。

兼容性 / 风险

  • 兼容性(保持不变):
    • startTime/endTime 仍为毫秒时间戳;后端语义仍为 created_at < endTime(半开区间)。
    • sessionId 筛选仍为精确匹配;联想只是候选来源。
  • 风险点:
    • 联想查询使用 ILIKE '%term%',在大数据量下不可扩展;本 PR 仅通过 debounce/minLen/limit 与按需 join 减压,非根治。
    • CSV 导出新增 “Session ID” 列会改变列数/列顺序,可能影响依赖固定列序的外部脚本(需要在发布/文档里明确)。

验证与测试

已运行(本地):

  • 覆盖率(目标 ≥90%):bunx vitest run --config vitest.logs-sessionid-time-filter.config.ts --coverage(All files 95.52%)
  • Build:bun run build
  • Unit tests(全量):bun run test(142 files passed,910 tests passed)
  • Unit tests(使用 .env.local 加载环境变量):bun run test(142 files passed,910 tests passed)
  • Type check:bun run typecheck
  • Lint:bun run lint

CI 建议:

  • 复用以上命令;如 CI 注入了 Redis/DB,关注相关初始化日志是否变化(不应影响本次改动结论)。

回滚方案

本 PR 可整体 revert;若只需回滚“回归修复”部分,可按提交拆分回滚(commit 列表见分支 dev..HEAD)。

参考

  • 调用链盘点:docs/dashboard-logs-callchain.md

Greptile Summary

This PR adds second-precision time filtering and Session ID exact filtering/suggestions to the Dashboard Logs page, while fixing two critical regressions discovered during code review.

Key Enhancements

1. Second-Precision Time Filters

  • upgraded from day-level to second-level time inputs while preserving existing URL semantics (startTime/endTime remain millisecond timestamps)
  • UI displays "inclusive end" but converts to "exclusive upper bound" (+1s) for backend half-open interval (created_at < endTime)
  • preserves date range picker shortcuts alongside new time inputs

2. Session ID Filtering

  • exact match filter (trimmed, empty/whitespace ignored)
  • suggestion dropdown with prefix-match autocomplete (LIKE 'term%' ESCAPE '\\')
  • escapeLike() utility prevents SQL injection via LIKE wildcards
  • new prefix index idx_message_request_session_id_prefix with varchar_pattern_ops for efficient prefix queries
  • non-admin users constrained to their own userId scope
  • session ID column added to both virtualized and non-virtualized tables with truncation, tooltip, and copy-to-clipboard

Critical Regression Fixes

1. Page Param Leak (commit 60bf8f2)

  • Root cause: runtime filters object contained leaked page field that propagated through Apply Filter
  • Fix: whitelist field extraction in handleApply() to drop non-filter fields
  • Test coverage: tests/unit/dashboard-logs-filters-time-range.test.tsx:97-127

2. Suggestion Scope Change (commit d011c74)

  • Root cause: deduplication key only checked term, ignored scope changes (userId/keyId/providerId/isAdmin)
  • Fix: composite key term|userId|keyId|providerId|isAdmin triggers reload when scope changes
  • Test coverage: tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx:256-321

Implementation Quality

  • comprehensive test coverage (95.52% via dedicated vitest config)
  • unified URL parsing/building utilities eliminate duplication
  • query optimization: skip keys join when keyId filter not present
  • CSV export updated with Session ID column (may affect column-order-dependent scripts)
  • thorough documentation in docs/dashboard-logs-callchain.md

Risks & Scalability

  • suggestion query uses ILIKE 'term%' which doesn't scale to massive datasets; mitigated by debounce, min length, limit, and prefix index
  • prefix index with varchar_pattern_ops requires pattern to be anchored at start (no substring matching)

Confidence Score: 5/5

  • This PR is safe to merge with excellent implementation quality and comprehensive test coverage
  • Score 5 reflects systematic approach to feature development with proactive regression fixes, security-conscious SQL escaping, proper index optimization, extensive test coverage (95.52%), clear documentation, and careful attention to edge cases (empty sessionId, scope changes, time precision). Both regression fixes are properly tested and whitelist approach prevents future param leaks.
  • No files require special attention

Important Files Changed

Filename Overview
src/repository/usage-logs.ts added sessionId exact filter to batch/details/stats queries with trim validation; added sessionId prefix suggestion query with escapeLike for safe LIKE pattern; optimized to skip keys join when keyId not needed
src/actions/usage-logs.ts added sessionId suggestion action with min length validation, term truncation, and admin/non-admin userId scoping; CSV export now includes sessionId column
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx added sessionId suggestion input with debounce; added second-precision time inputs; fixed page param leak via whitelist in handleApply; fixed scope change detection for suggestion reload using composite key
src/repository/_shared/like.ts added escapeLike utility to safely escape LIKE wildcards (%, _, \) for literal matching; prevents SQL injection via LIKE patterns
drizzle/0055_neat_stepford_cuckoos.sql added idx_message_request_session_id_prefix with varchar_pattern_ops for efficient prefix LIKE queries; filters warmup records in partial index
tests/unit/dashboard-logs-filters-time-range.test.tsx tests second-precision time defaults and critical regression fix for page param leak; validates whitelist filtering removes leaked runtime fields
tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx tests sessionId suggestion debounce, min length, input focus retention, and critical scope change reload fix when provider/key/user filters change

Sequence Diagram

sequenceDiagram
    participant User as User Browser
    participant Filters as UsageLogsFilters
    participant Action as usage-logs.ts (Action)
    participant Repo as usage-logs.ts (Repo)
    participant DB as PostgreSQL

    Note over User,DB: SessionId Suggestion Flow
    User->>Filters: types "ab" in sessionId input
    Filters->>Filters: debounce 300ms
    Filters->>Filters: check min length (>=2)
    Filters->>Filters: build scope key (term|userId|keyId|providerId|isAdmin)
    Filters->>Action: getUsageLogSessionIdSuggestions({term, userId, keyId, providerId})
    Action->>Action: trim & truncate term to max length
    Action->>Action: enforce userId for non-admin
    Action->>Repo: findUsageLogSessionIdSuggestions(filters)
    Repo->>Repo: escapeLike(term) → escape %, _, \
    Repo->>Repo: build pattern: "escapedTerm%"
    Repo->>DB: SELECT DISTINCT session_id WHERE session_id LIKE 'ab%' ESCAPE '\\'
    Note over DB: Uses idx_message_request_session_id_prefix (varchar_pattern_ops)
    DB-->>Repo: matching session_ids (sorted by MIN(created_at) DESC)
    Repo-->>Action: sessionIds[]
    Action-->>Filters: sessionIds[]
    Filters->>Filters: update availableSessionIds state
    Filters->>User: display suggestions in dropdown

    Note over User,DB: Apply Filter with SessionId & Time Range
    User->>Filters: enters sessionId "abc", start "2026-01-01 10:30:15", end "2026-01-02 14:45:30"
    User->>Filters: clicks Apply Filter
    Filters->>Filters: handleApply() - whitelist fields (drops 'page' if leaked)
    Filters->>Filters: convert inclusive end time to exclusive (+1s)
    Filters->>Action: onChange({sessionId: "abc", startTime, endTime, ...})
    Note over Action: URL updated via router.push
    
    Note over User,DB: Load Logs with Filters
    Action->>Repo: findUsageLogsBatch({sessionId, startTime, endTime, ...})
    Repo->>Repo: trim sessionId → "abc"
    Repo->>Repo: build WHERE conditions
    Repo->>DB: SELECT * WHERE session_id = 'abc' AND created_at >= start AND created_at < end
    Note over DB: Uses idx_message_request_session_id for exact match
    DB-->>Repo: log records
    Repo-->>Action: logs with sessionId column
    Action-->>User: display virtualized table with sessionId (truncated + tooltip + copy)
Loading

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

为 Dashboard Usage Logs 引入 Session ID 支持:添加 UI 文案与组件(自动补全、复制)、URL 查询与时间范围工具、后端 repository/动作支持、数据库前缀索引、剪贴板与 LIKE 工具、公测配置与大量单元测试。

Changes

Cohort / File(s) 摘要
国际化与本地化
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
新增 Session ID 相关文案键(过滤器标签、搜索占位符、未找到提示、列标题)。
过滤器、URL 与时间处理工具
src/app/[locale]/dashboard/logs/_utils/logs-query.ts, src/app/[locale]/dashboard/logs/_utils/time-range.ts
新增 LogsUrlFilters 类型与 parseLogsUrlFilters / buildLogsUrlQuery,并添加时钟感知的时间工具(解析/格式化时钟、日期+时钟转 timestamp、包容性结束时间计算)。
前端组件(Filters / Table / View / Virtualized)
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx, .../usage-logs-table.tsx, .../virtualized-logs-table.tsx, .../usage-logs-view.tsx, .../usage-logs-view-virtualized.tsx, .../usage-logs-stats-panel.tsx
在过滤器中加入 sessionId 自动补全(建议、最小长度、去重、弹出窗口、debounce);在表格与虚拟表格新增 Session ID 列与复制按钮;将 sessionId 纳入 filters 流和 statsPanel 缓存 key;时间输入/时钟同步改造。
后端动作与仓库
src/actions/usage-logs.ts, src/repository/usage-logs.ts, src/repository/_shared/like.ts
新增 sessionId 过滤支持于查询路径(batch/details/stats);新增 findUsageLogSessionIdSuggestions 及其输入/过滤类型;新增 escapeLike 用于 ILIKE 检索。
常量与工具
src/lib/constants/usage-logs.constants.ts, src/lib/utils/clipboard.ts, src/lib/utils/clipboard.test.ts, tests/unit/lib/constants/usage-logs.constants.test.ts
新增 Session ID 建议阈值常量(MIN_LEN/MAX_LEN/LIMIT);重构/新增剪贴板工具 copyTextToClipboard(含 fallback execCommand),并增加相关测试。
数据库索引与 Drizzle 元数据
drizzle/0055_neat_stepford_cuckoos.sql, drizzle/meta/0055_snapshot.json, drizzle/meta/_journal.json, src/drizzle/schema.ts
message_request.session_id 添加前缀搜索友好的 partial btree 索引(varchar_pattern_ops),索引 WHERE 排除已删除与 warmup blocked 行;更新 Drizzle schema 与 meta 快照。
测试套件
tests/unit/* (新增多文件,包括 filters/time-range、query utils、sessionid 建议 UI、time-range utils、repository sessionId 过滤与建议测试、escape-like、table 测试等)
大量单元测试覆盖:URL 解析/构建、时间范围语义、sessionId 建议 UI、repository 查询行为、复制到剪贴板、LIKE 转义等(多个新增测试文件)。
Vitest 配置与 npm 脚本
vitest.logs-sessionid-time-filter.config.ts, vitest.usage-logs-sessionid-search.config.ts, vitest.include-session-id-in-errors.config.ts, package.json, .gitignore
添加针对性 Vitest 配置文件与两个 npm 测试脚本(coverage 运行),并扩展 .gitignore 规则。
文档
docs/dashboard-logs-callchain.md, docs/error-session-id-guide.md
添加 Dashboard Logs 调用链文档与错误中包含 Session ID 的操作指南。
代理/错误处理(session-id 注入)
src/app/v1/_lib/codex/chat-completions-handler.ts, src/app/v1/_lib/proxy-handler.ts, src/app/v1/_lib/proxy/error-handler.ts, src/app/v1/_lib/proxy/error-session-id.ts
新增 attachSessionIdToErrorResponse / attachSessionIdToErrorMessage,并在代理/错误处理路径统一将 sessionId 注入响应头 x-cch-session-id 与(在 JSON error.message 中)附加 session id 文本;相关处理路径改为使用该函数。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题清晰准确地总结了主要变更:秒级时间筛选、Session ID 精确筛选/联想/展示以及回归修复,与 raw_summary 中的大量文件变更内容相符。
Description check ✅ Passed PR 描述详细阐述了背景、目标、主要改动、修复的回归点、兼容性风险和测试验证,与实际变更内容高度相关。

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

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

本次拉取请求旨在全面增强 Dashboard Logs 页面的用户体验和功能性。通过引入秒级时间筛选和 Session ID 精确筛选及联想功能,用户现在可以更精细、高效地定位和分析日志数据。此外,PR 还解决了在近期代码审查中发现的关键回归问题,提升了系统的健壮性和可用性,确保了筛选逻辑的准确性和一致性。

Highlights

  • 时间筛选升级: Dashboard Logs 页面现在支持精确到秒的时间筛选,同时保留了日期快捷按钮和 URL 语义(startTime/endTime 仍为毫秒时间戳,后端使用半开区间 created_at < endTime)。
  • Session ID 精确筛选与联想: 新增了 sessionId 精确筛选功能,前端提供模糊匹配的联想下拉列表,但最终查询条件为精确匹配。同时,日志表格中也新增了 'Session ID' 列,支持截断显示、Tooltip 提示和点击复制。
  • 回归修复: 修复了两处关键回归问题:一是 Apply Filter 时可能导致分页 page 参数泄漏的问题,现在通过字段白名单化输出解决;二是 sessionId 联想请求在搜索词不变但筛选范围(如 userId/keyId/providerId/isAdmin)变化时未能重新请求刷新列表的问题。
  • 新增文档与测试: 新增了 docs/dashboard-logs-callchain.md 文档,盘点 Logs 页面的调用链,并为本次改动增加了专项覆盖率配置和单元测试,确保功能稳定性和代码质量。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

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

Choose a reason for hiding this comment

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

Code Review

本 PR 实现了仪表盘日志页面的多项重要功能增强,包括秒级时间筛选和 Session ID 的精确筛选与联想功能,并修复了分页参数泄漏和联想请求去重等关键回归问题。整体实现质量很高,代码结构清晰,特别是新增了 URL 参数解析和构建的统一工具函数,以及详尽的单元测试和专项覆盖率配置,显著提升了代码的可维护性和健壮性。

我提出了一些建议,主要集中在代码复用、性能优化和可维护性方面,希望能帮助进一步提升代码质量。

EXCLUDE_WARMUP_CONDITION,
sql`${messageRequest.sessionId} IS NOT NULL`,
sql`length(${messageRequest.sessionId}) > 0`,
sql`${messageRequest.sessionId} ILIKE ${`%${trimmedTerm}%`}`,

Choose a reason for hiding this comment

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

high

正如 PR 描述中提到的,使用 ILIKE '%...%' 进行模糊搜索在数据量大时可能导致严重的性能问题,因为它会触发全表扫描。虽然本次提交通过去抖和长度限制等方式进行了一定缓解,但我们应考虑一个更具扩展性的长期解决方案。可行的选项包括:

  • 使用 trigram 索引(例如 CREATE INDEX ON message_request USING GIN (session_id gin_trgm_ops);),这可以显著加速 ILIKE 查询。
  • 将搜索限制为仅从字符串开头匹配(ILIKE '${trimmedTerm}%'),这样可以利用标准的 B-tree 索引。
  • 为此功能使用专门的搜索引擎,如 Elasticsearch。

考虑到潜在的影响,添加 trigram 索引似乎是很好的下一步。


// 硬编码常用状态码(首次渲染时显示,无需等待加载)
const COMMON_STATUS_CODES: number[] = [200, 400, 401, 429, 500];
const SESSION_ID_SUGGESTION_MIN_LEN = 2;

Choose a reason for hiding this comment

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

medium

常量 SESSION_ID_SUGGESTION_MIN_LENsrc/actions/usage-logs.tssrc/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx 中均有定义。这种重复可能导致在需要更改值时不一致。更好的做法是在一个地方(例如,从 actions 文件中导出)定义此常量,并在需要时导入它。

Comment on lines 95 to 135
const filters = useMemo<VirtualizedLogsTableFilters & { page?: number }>(() => {
const parsed = parseLogsUrlFilters({
userId: searchParams.userId,
keyId: searchParams.keyId,
providerId: searchParams.providerId,
sessionId: searchParams.sessionId,
startTime: searchParams.startTime,
endTime: searchParams.endTime,
statusCode: searchParams.statusCode,
model: searchParams.model,
endpoint: searchParams.endpoint,
minRetry: searchParams.minRetry,
page: searchParams.page,
});
return {
userId: parsed.userId,
keyId: parsed.keyId,
providerId: parsed.providerId,
sessionId: parsed.sessionId,
startTime: parsed.startTime,
endTime: parsed.endTime,
statusCode: parsed.statusCode,
excludeStatusCode200: parsed.excludeStatusCode200,
model: parsed.model,
endpoint: parsed.endpoint,
minRetryCount: parsed.minRetryCount,
page: parsed.page,
};
}, [
searchParams.userId,
searchParams.keyId,
searchParams.providerId,
searchParams.sessionId,
searchParams.startTime,
searchParams.endTime,
searchParams.statusCode,
searchParams.model,
searchParams.endpoint,
searchParams.minRetry,
searchParams.page,
]);

Choose a reason for hiding this comment

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

medium

用于解析筛选器的 useMemo hook 可以被简化。parseLogsUrlFilters 函数已经返回了具有所需结构的对象,因此手动映射每个属性是多余的,可以直接返回其结果。

  const filters = useMemo<VirtualizedLogsTableFilters & { page?: number }>(() =>
    parseLogsUrlFilters({
      userId: searchParams.userId,
      keyId: searchParams.keyId,
      providerId: searchParams.providerId,
      sessionId: searchParams.sessionId,
      startTime: searchParams.startTime,
      endTime: searchParams.endTime,
      statusCode: searchParams.statusCode,
      model: searchParams.model,
      endpoint: searchParams.endpoint,
      minRetry: searchParams.minRetry,
      page: searchParams.page,
    }),
  [
    searchParams.userId,
    searchParams.keyId,
    searchParams.providerId,
    searchParams.sessionId,
    searchParams.startTime,
    searchParams.endTime,
    searchParams.statusCode,
    searchParams.model,
    searchParams.endpoint,
    searchParams.minRetry,
    searchParams.page,
  ]);

Comment on lines 71 to 99
const handleCopySessionIdClick = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
const sessionId = event.currentTarget.dataset.sessionId;
if (!sessionId) return;

const clipboard = navigator.clipboard;
if (clipboard) {
void clipboard
.writeText(sessionId)
.then(() => toast.success(t("actions.copied")))
.catch(() => {});
return;
}

try {
const textarea = document.createElement("textarea");
textarea.value = sessionId;
textarea.setAttribute("readonly", "");
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
if (ok) toast.success(t("actions.copied"));
} catch {}
},
[t]
);

Choose a reason for hiding this comment

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

medium

handleCopySessionIdClick 这个回调函数与 usage-logs-table.tsx 中的实现完全相同。为了提高代码的可维护性并减少重复,建议将此逻辑提取到一个自定义 Hook(例如 useClipboard)或共享的工具函数中。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/repository/usage-logs.ts`:
- Around line 578-584: The ILIKE pattern currently interpolates trimmedTerm
directly, allowing literal '%' or '_' in trimmedTerm to act as wildcards; add an
escape function (e.g., escapeLike(s: string)) that escapes %, _, and backslash,
build pattern = `%${escapeLike(trimmedTerm)}%`, and replace the
sql`${messageRequest.sessionId} ILIKE ${`%${trimmedTerm}%`}` entry in the
conditions array with sql`${messageRequest.sessionId} ILIKE ${pattern} ESCAPE
'\\'` so the pattern matches literals correctly and uses the ESCAPE clause.
🧹 Nitpick comments (12)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx (1)

194-235: 测试覆盖了剪贴板 API 路径,但未覆盖 fallback 路径。

当前测试仅验证了 navigator.clipboard.writeText 可用时的情况。建议补充以下测试场景以提高覆盖率:

  1. navigator.clipboard 不可用时的 execCommand("copy") fallback 路径
  2. clipboard.writeText 失败时的静默处理

此外,Object.defineProperty(navigator, "clipboard", ...) 修改了全局对象,建议在测试后恢复原始值以避免测试间干扰。

建议添加 cleanup 和 fallback 测试
  test("copies sessionId on click and shows toast", async () => {
+   const originalClipboard = navigator.clipboard;
    const writeText = vi.fn(async () => {});
    Object.defineProperty(navigator, "clipboard", {
      value: { writeText },
      configurable: true,
    });

    // ... existing test code ...

    await act(async () => {
      root.unmount();
    });
    container.remove();
+   
+   // Restore original clipboard
+   Object.defineProperty(navigator, "clipboard", {
+     value: originalClipboard,
+     configurable: true,
+   });
  });
+
+ test("uses execCommand fallback when clipboard API unavailable", async () => {
+   const originalClipboard = navigator.clipboard;
+   Object.defineProperty(navigator, "clipboard", {
+     value: undefined,
+     configurable: true,
+   });
+   const execCommandSpy = vi.spyOn(document, "execCommand").mockReturnValue(true);
+
+   // ... render and click test ...
+
+   expect(execCommandSpy).toHaveBeenCalledWith("copy");
+   expect(toastMocks.success).toHaveBeenCalledWith("actions.copied");
+
+   execCommandSpy.mockRestore();
+   Object.defineProperty(navigator, "clipboard", {
+     value: originalClipboard,
+     configurable: true,
+   });
+ });
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)

71-99: handleCopySessionIdClickusage-logs-table.tsx 中的实现重复。

根据 relevant code snippets,usage-logs-table.tsx (lines 66-91) 中存在完全相同的复制逻辑。建议将此函数抽取为共享 hook 或工具函数,避免代码重复。

此外,Line 96 的空 catch {} 会静默吞掉所有错误,建议至少记录到 console 以便调试。

建议抽取共享的复制逻辑

创建共享 hook,例如 src/hooks/use-copy-to-clipboard.ts

import { useCallback } from "react";
import { toast } from "sonner";
import { useTranslations } from "next-intl";

export function useCopyToClipboard() {
  const t = useTranslations("dashboard");

  return useCallback(async (text: string) => {
    if (navigator.clipboard) {
      try {
        await navigator.clipboard.writeText(text);
        toast.success(t("actions.copied"));
        return;
      } catch {
        // Fall through to execCommand fallback
      }
    }

    try {
      const textarea = document.createElement("textarea");
      textarea.value = text;
      textarea.setAttribute("readonly", "");
      textarea.style.position = "absolute";
      textarea.style.left = "-9999px";
      document.body.appendChild(textarea);
      textarea.select();
      const ok = document.execCommand("copy");
      document.body.removeChild(textarea);
      if (ok) toast.success(t("actions.copied"));
    } catch (err) {
      console.warn("Copy to clipboard failed:", err);
    }
  }, [t]);
}

然后在两个组件中使用:

- const handleCopySessionIdClick = useCallback(
-   (event: MouseEvent<HTMLButtonElement>) => {
-     const sessionId = event.currentTarget.dataset.sessionId;
-     if (!sessionId) return;
-     // ... duplicated logic ...
-   },
-   [t]
- );
+ const copyToClipboard = useCopyToClipboard();
+ const handleCopySessionIdClick = useCallback(
+   (event: MouseEvent<HTMLButtonElement>) => {
+     const sessionId = event.currentTarget.dataset.sessionId;
+     if (sessionId) void copyToClipboard(sessionId);
+   },
+   [copyToClipboard]
+ );
tests/unit/repository/usage-logs-sessionid-suggestions.test.ts (1)

133-146: 建议补充 limit 下界测试

当前测试验证了 limit: 500 被 clamp 到 50(上界),但未验证 limit: 0limit: -1 被 clamp 到 1(下界)。参考 src/repository/usage-logs.ts 中的实现 Math.min(50, Math.max(1, filters.limit ?? 20)),建议补充下界测试。

建议添加下界测试用例
  test("limit 应被 clamp 到 [1, 50]", async () => {
    vi.resetModules();

    const limitArgs: unknown[] = [];
    const selectMock = vi.fn(() => createThenableQuery([], { limitArgs }));
    vi.doMock("@/drizzle/db", () => ({
      db: { select: selectMock },
    }));

    const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs");
    await findUsageLogSessionIdSuggestions({ term: "abc", limit: 500 });

    expect(limitArgs).toEqual([50]);
  });
+
+  test("limit 下界应 clamp 到 1", async () => {
+    vi.resetModules();
+
+    const limitArgs: unknown[] = [];
+    const selectMock = vi.fn(() => createThenableQuery([], { limitArgs }));
+    vi.doMock("@/drizzle/db", () => ({
+      db: { select: selectMock },
+    }));
+
+    const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs");
+    await findUsageLogSessionIdSuggestions({ term: "abc", limit: 0 });
+
+    expect(limitArgs).toEqual([1]);
+  });
tests/unit/dashboard-logs-filters-time-range.test.tsx (1)

82-85: 建议使用 data-testid 替代文本匹配

当前通过按钮文本 "Apply Filter" 查找元素,如果 i18n 翻译变更或组件使用不同 locale 测试,可能导致测试失败。建议在组件中添加 data-testid="apply-filter-btn" 并使用其定位。

-    const applyBtn = Array.from(container.querySelectorAll("button")).find(
-      (b) => (b.textContent || "").trim() === "Apply Filter"
-    );
+    const applyBtn = container.querySelector("[data-testid='apply-filter-btn']");
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (1)

66-94: 剪贴板复制的空 catch 块应考虑用户反馈

Line 76 和 Line 91 的空 catch 块会静默吞掉错误。虽然复制失败不是关键功能,但建议至少在 fallback 失败时给用户一个提示。

建议添加失败提示
      if (clipboard) {
        void clipboard
          .writeText(sessionId)
          .then(() => toast.success(t("actions.copied")))
-          .catch(() => {});
+          .catch(() => toast.error(t("actions.copyFailed")));
        return;
      }

      try {
        // ... fallback code ...
        if (ok) toast.success(t("actions.copied"));
-      } catch {}
+      } catch {
+        toast.error(t("actions.copyFailed"));
+      }

注意:需要在 i18n 文件中添加 actions.copyFailed 翻译 key。

src/actions/usage-logs.ts (1)

307-322: 建议将 limit 提取为常量

limit: 20 在两个分支中重复硬编码。建议提取为常量以便统一管理,与 SESSION_ID_SUGGESTION_MIN_LEN 等保持一致的风格。

建议提取 limit 常量
 const SESSION_ID_SUGGESTION_MIN_LEN = 2;
 const SESSION_ID_SUGGESTION_MAX_LEN = 128;
+const SESSION_ID_SUGGESTION_DEFAULT_LIMIT = 20;

 // ... in getUsageLogSessionIdSuggestions ...
     const finalFilters =
       session.user.role === "admin"
         ? {
             term: trimmedTerm,
             userId: input.userId,
             keyId: input.keyId,
             providerId: input.providerId,
-            limit: 20,
+            limit: SESSION_ID_SUGGESTION_DEFAULT_LIMIT,
           }
         : {
             term: trimmedTerm,
             userId: session.user.id,
             keyId: input.keyId,
             providerId: input.providerId,
-            limit: 20,
+            limit: SESSION_ID_SUGGESTION_DEFAULT_LIMIT,
           };
src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx (1)

95-135: 考虑简化 useMemo 依赖项。

当前实现手动列举所有 searchParams 字段并逐一传递给 parseLogsUrlFilters,与 usage-logs-view.tsx 直接传递 searchParams 的方式不一致。如果后续新增筛选字段,需要同时修改多处。

可以考虑使用 JSON.stringify(searchParams) 作为稳定依赖键:

♻️ 建议的简化方案
  const filters = useMemo<VirtualizedLogsTableFilters & { page?: number }>(() => {
-   const parsed = parseLogsUrlFilters({
-     userId: searchParams.userId,
-     keyId: searchParams.keyId,
-     providerId: searchParams.providerId,
-     sessionId: searchParams.sessionId,
-     startTime: searchParams.startTime,
-     endTime: searchParams.endTime,
-     statusCode: searchParams.statusCode,
-     model: searchParams.model,
-     endpoint: searchParams.endpoint,
-     minRetry: searchParams.minRetry,
-     page: searchParams.page,
-   });
+   const parsed = parseLogsUrlFilters(searchParams);
    return {
      userId: parsed.userId,
      keyId: parsed.keyId,
      providerId: parsed.providerId,
      sessionId: parsed.sessionId,
      startTime: parsed.startTime,
      endTime: parsed.endTime,
      statusCode: parsed.statusCode,
      excludeStatusCode200: parsed.excludeStatusCode200,
      model: parsed.model,
      endpoint: parsed.endpoint,
      minRetryCount: parsed.minRetryCount,
      page: parsed.page,
    };
- }, [
-   searchParams.userId,
-   searchParams.keyId,
-   searchParams.providerId,
-   searchParams.sessionId,
-   searchParams.startTime,
-   searchParams.endTime,
-   searchParams.statusCode,
-   searchParams.model,
-   searchParams.endpoint,
-   searchParams.minRetry,
-   searchParams.page,
- ]);
+ }, [searchParams]);
src/app/[locale]/dashboard/logs/_utils/time-range.ts (1)

7-19: 建议添加时间值范围校验。

parseClockString 当前仅检查 Number.isFinite,但未验证 hours (0-23)、minutes (0-59)、seconds (0-59) 的有效范围。超出范围的值可能导致 new Date() 产生意外的日期偏移。

♻️ 建议的改进
 export function parseClockString(clockStr: string): ClockParts {
   const [hoursRaw, minutesRaw, secondsRaw] = clockStr.split(":");
 
   const hours = Number(hoursRaw);
   const minutes = Number(minutesRaw);
   const seconds = Number(secondsRaw ?? "0");
 
+  const clampHours = Number.isFinite(hours) && hours >= 0 && hours <= 23 ? hours : 0;
+  const clampMinutes = Number.isFinite(minutes) && minutes >= 0 && minutes <= 59 ? minutes : 0;
+  const clampSeconds = Number.isFinite(seconds) && seconds >= 0 && seconds <= 59 ? seconds : 0;
+
   return {
-    hours: Number.isFinite(hours) ? hours : 0,
-    minutes: Number.isFinite(minutes) ? minutes : 0,
-    seconds: Number.isFinite(seconds) ? seconds : 0,
+    hours: clampHours,
+    minutes: clampMinutes,
+    seconds: clampSeconds,
   };
 }
tests/unit/repository/usage-logs-sessionid-filter.test.ts (1)

54-76: 测试覆盖良好,但断言可更明确。

当前测试断言 blankWhereSql === baseWhereSql 来验证空白 sessionId 不会追加额外条件。建议额外断言 where 子句中不包含 "sessionid" 关键字,使测试意图更清晰:

♻️ 建议的改进
     expect(whereArgs).toHaveLength(2);
     const baseWhereSql = sqlToString(whereArgs[0]).toLowerCase();
     const blankWhereSql = sqlToString(whereArgs[1]).toLowerCase();
     expect(blankWhereSql).toBe(baseWhereSql);
+    expect(baseWhereSql).not.toContain("sessionid");
src/app/[locale]/dashboard/logs/_utils/logs-query.ts (1)

69-70: 冗余 trim 操作。

sessionIdparseStringParam 中已经被 trim 过(Line 50 调用),此处再次 trim 是冗余的。虽然不影响正确性,但可以移除以保持一致性。

♻️ 建议的简化
-  const sessionId = filters.sessionId?.trim();
-  if (sessionId) query.set("sessionId", sessionId);
+  if (filters.sessionId) query.set("sessionId", filters.sessionId);
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx (2)

197-208: requestKey 计算逻辑存在重复。

loadSessionIdsForFilter(Lines 201-207)和 useEffect(Lines 249-255)中有相同的 requestKey 拼接逻辑。建议提取为独立的辅助函数或 useMemo,避免重复代码并降低不一致风险。

建议提取 requestKey 计算
const sessionIdRequestKey = useMemo(() => {
  const term = debouncedSessionIdSearchTerm.trim();
  return [
    term,
    isAdmin ? (localFilters.userId ?? "").toString() : "",
    (localFilters.keyId ?? "").toString(),
    (localFilters.providerId ?? "").toString(),
    isAdmin ? "1" : "0",
  ].join("|");
}, [debouncedSessionIdSearchTerm, isAdmin, localFilters.userId, localFilters.keyId, localFilters.providerId]);

718-722: 输入时立即 trim 可能导致用户体验问题。

onChange 中对输入值调用 .trim() 会移除用户输入的前后空格,但如果用户在中间位置输入空格后继续输入,trim 不会影响。然而这里的问题是:当用户在输入框开头输入空格时,空格会被立即移除,可能导致光标位置异常或输入被"吞掉"的感觉。

建议仅在提交/查询时进行 trim,而非每次 onChange 时:

 onChange={(e) => {
-  const next = e.target.value.trim();
-  setLocalFilters((prev) => ({ ...prev, sessionId: next || undefined }));
-  setSessionIdPopoverOpen(next.length >= SESSION_ID_SUGGESTION_MIN_LEN);
+  const next = e.target.value;
+  setLocalFilters((prev) => ({ ...prev, sessionId: next || undefined }));
+  setSessionIdPopoverOpen(next.trim().length >= SESSION_ID_SUGGESTION_MIN_LEN);
 }}

实际过滤和后端查询时已有 trim 处理,前端输入框保持原值即可。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9e51d and b543fff.

📒 Files selected for processing (27)
  • .gitignore
  • docs/dashboard-logs-callchain.md
  • messages/en/dashboard.json
  • messages/ja/dashboard.json
  • messages/ru/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • package.json
  • src/actions/usage-logs.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • src/app/[locale]/dashboard/logs/_utils/time-range.ts
  • src/repository/usage-logs.ts
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/dashboard-logs-query-utils.test.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • vitest.logs-sessionid-time-filter.config.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use emoji characters in any code, comments, or string literals

Files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • vitest.logs-sessionid-time-filter.config.ts
  • src/actions/usage-logs.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • src/repository/usage-logs.ts
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_utils/time-range.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

All new features must have unit test coverage of at least 80%

Files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text
Use path alias @/ to reference files in ./src/ directory
Format code with Biome using: double quotes, trailing commas, 2-space indent, 100 character line width

Files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • vitest.logs-sessionid-time-filter.config.ts
  • src/actions/usage-logs.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • src/repository/usage-logs.ts
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_utils/time-range.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer named exports over default exports

Files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • vitest.logs-sessionid-time-filter.config.ts
  • src/actions/usage-logs.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • src/repository/usage-logs.ts
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_utils/time-range.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx
tests/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for unit testing and happy-dom for DOM testing

Files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
src/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Source-adjacent tests should be placed in src/**/*.test.ts alongside source files

Files:

  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
src/repository/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for data access in the repository layer

Files:

  • src/repository/usage-logs.ts
🧠 Learnings (9)
📚 Learning: 2026-01-10T17:53:25.066Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T17:53:25.066Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : All new features must have unit test coverage of at least 80%

Applied to files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • tests/unit/dashboard-logs-query-utils.test.ts
  • vitest.logs-sessionid-time-filter.config.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
📚 Learning: 2026-01-10T17:53:25.066Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T17:53:25.066Z
Learning: Applies to tests/**/*.test.{ts,tsx,js,jsx} : Use Vitest for unit testing and happy-dom for DOM testing

Applied to files:

  • tests/unit/dashboard-logs-time-range-utils.test.ts
  • package.json
  • tests/unit/dashboard-logs-query-utils.test.ts
  • vitest.logs-sessionid-time-filter.config.ts
  • tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx
  • tests/unit/dashboard-logs-filters-time-range.test.tsx
📚 Learning: 2026-01-10T17:53:25.066Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T17:53:25.066Z
Learning: Applies to src/repository/**/*.{ts,tsx} : Use Drizzle ORM for data access in the repository layer

Applied to files:

  • tests/unit/repository/usage-logs-sessionid-suggestions.test.ts
📚 Learning: 2026-01-05T03:02:14.502Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx:66-66
Timestamp: 2026-01-05T03:02:14.502Z
Learning: In the claude-code-hub project, the translations.actions.addKey field in UserKeyTableRowProps is defined as optional for backward compatibility, but all actual callers in the codebase provide the complete translations object. The field has been added to all 5 locale files (messages/{locale}/dashboard.json).

Applied to files:

  • messages/zh-CN/dashboard.json
  • messages/en/dashboard.json
  • messages/ja/dashboard.json
📚 Learning: 2026-01-05T03:01:39.354Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/types/user.ts:158-170
Timestamp: 2026-01-05T03:01:39.354Z
Learning: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined. Example: for numeric limits like limitTotalUsd, use 'number | null | undefined' when null signifies explicitly unlimited (e.g., matches DB schema or special UI logic) and undefined signifies 'inherit default'. This pattern should be consistently reflected in type definitions across related fields to preserve semantic clarity between database constraints and UI behavior.

Applied to files:

  • src/actions/usage-logs.ts
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • src/repository/usage-logs.ts
  • src/app/[locale]/dashboard/logs/_utils/time-range.ts
📚 Learning: 2026-01-10T06:19:58.167Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 573
File: src/actions/model-prices.ts:275-335
Timestamp: 2026-01-10T06:19:58.167Z
Learning: Do not modify hardcoded Chinese error messages in Server Actions under src/actions/*.ts as part of piecemeal changes. This is a repo-wide architectural decision that requires a coordinated i18n refactor across all Server Action files (e.g., model-prices.ts, users.ts, system-config.ts). Treat i18n refactor as a separate unified task rather than per-PR changes, and plan a project-wide approach for replacing hardcoded strings with localized resources.

Applied to files:

  • src/actions/usage-logs.ts
📚 Learning: 2026-01-10T06:20:04.478Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 573
File: src/actions/model-prices.ts:275-335
Timestamp: 2026-01-10T06:20:04.478Z
Learning: In the `ding113/claude-code-hub` repository, Server Actions (files under `src/actions/*.ts`) currently return hardcoded Chinese error messages directly. This is a codebase-wide architectural decision that applies to all action files (e.g., model-prices.ts, users.ts, system-config.ts). Changing this pattern requires a coordinated i18n refactor across all Server Actions, which should be handled as a separate unified task rather than piecemeal changes in individual PRs.

Applied to files:

  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
📚 Learning: 2026-01-10T17:53:25.066Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T17:53:25.066Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text

Applied to files:

  • tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx
📚 Learning: 2026-01-10T17:53:25.066Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-10T17:53:25.066Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.{ts,tsx} : The proxy pipeline processes requests through a GuardPipeline with sequential guards: auth, sensitive, client, model, version, probe, session, warmup, requestFilter, rateLimit, provider, providerRequestFilter, messageContext

Applied to files:

  • .gitignore
🧬 Code graph analysis (12)
tests/unit/dashboard-logs-time-range-utils.test.ts (1)
src/app/[locale]/dashboard/logs/_utils/time-range.ts (4)
  • parseClockString (7-19)
  • dateStringWithClockToTimestamp (29-45)
  • inclusiveEndTimestampFromExclusive (47-49)
  • formatClockFromTimestamp (21-27)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)
src/components/ui/tooltip.tsx (4)
  • TooltipProvider (57-57)
  • Tooltip (57-57)
  • TooltipTrigger (57-57)
  • TooltipContent (57-57)
tests/unit/repository/usage-logs-sessionid-suggestions.test.ts (1)
src/repository/usage-logs.ts (1)
  • findUsageLogSessionIdSuggestions (570-617)
tests/unit/dashboard-logs-query-utils.test.ts (1)
src/app/[locale]/dashboard/logs/_utils/logs-query.ts (2)
  • parseLogsUrlFilters (34-60)
  • buildLogsUrlQuery (62-93)
vitest.logs-sessionid-time-filter.config.ts (2)
scripts/sync-settings-keys.js (1)
  • path (15-15)
scripts/validate-migrations.js (1)
  • __dirname (18-18)
src/actions/usage-logs.ts (4)
src/actions/types.ts (1)
  • ActionResult (31-31)
src/lib/auth.ts (1)
  • getSession (116-128)
src/repository/usage-logs.ts (1)
  • findUsageLogSessionIdSuggestions (570-617)
src/lib/logger.ts (1)
  • logger (168-187)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx (1)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (1)
  • UsageLogsTable (45-567)
src/repository/usage-logs.ts (3)
src/drizzle/schema.ts (1)
  • messageRequest (276-362)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/drizzle/db.ts (1)
  • db (37-44)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (3)
scripts/sync-settings-keys.js (1)
  • t (184-184)
src/components/ui/table.tsx (2)
  • TableHead (92-92)
  • TableCell (92-92)
src/components/ui/tooltip.tsx (4)
  • TooltipProvider (57-57)
  • Tooltip (57-57)
  • TooltipTrigger (57-57)
  • TooltipContent (57-57)
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx (3)
src/lib/hooks/use-debounce.ts (1)
  • useDebounce (11-27)
src/actions/usage-logs.ts (1)
  • getUsageLogSessionIdSuggestions (293-331)
src/app/[locale]/dashboard/logs/_utils/time-range.ts (3)
  • formatClockFromTimestamp (21-27)
  • inclusiveEndTimestampFromExclusive (47-49)
  • dateStringWithClockToTimestamp (29-45)
src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx (2)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)
  • VirtualizedLogsTableFilters (31-43)
src/app/[locale]/dashboard/logs/_utils/logs-query.ts (2)
  • parseLogsUrlFilters (34-60)
  • buildLogsUrlQuery (62-93)
src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx (1)
src/app/[locale]/dashboard/logs/_utils/logs-query.ts (2)
  • parseLogsUrlFilters (34-60)
  • buildLogsUrlQuery (62-93)
⏰ 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: Docker Build Test
  • GitHub Check: pr-description
  • GitHub Check: pr-label
🔇 Additional comments (49)
.gitignore (1)

19-19: LGTM!

新增的覆盖率目录忽略规则与现有模式一致,命名规范与 package.json 中新增的测试脚本相对应。

messages/ja/dashboard.json (1)

63-69: LGTM!

日语翻译键值新增正确,命名和风格与现有翻译保持一致。sessionId 相关的 UI 字符串已按 i18n 要求添加到 logs.filterslogs.columns 部分。

Also applies to: 102-102

package.json (1)

21-21: LGTM!

新增的测试覆盖率脚本遵循现有模式,与其他 test:coverage:* 脚本保持一致的命名约定和配置结构。

messages/zh-TW/dashboard.json (1)

63-69: LGTM!

繁体中文翻译键值新增正确,保持 "Session ID" 等技术术语不翻译的风格与文件中其他技术术语(如 "API"、"Model"、"Token")一致。

Also applies to: 102-102

tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx (1)

69-69: LGTM!

使用本地定义的 loadMessages() 辅助函数替代之前的调用,简化了测试设置,同时保持了测试逻辑的完整性。

messages/ru/dashboard.json (1)

63-69: LGTM!

俄语本地化键值对添加正确,与英文版本保持一致,翻译符合上下文语义。sessionIdsearchSessionIdnoSessionFoundcolumns.sessionId 四个新键均已正确放置。

Also applies to: 102-102

messages/en/dashboard.json (1)

63-69: LGTM!

英文本地化键值对添加正确。sessionId 相关的四个新键(filters.sessionIdfilters.searchSessionIdfilters.noSessionFoundcolumns.sessionId)均已正确放置,文案清晰准确。

Also applies to: 102-102

docs/dashboard-logs-callchain.md (2)

1-95: 文档结构清晰,边界定义明确。

调用链盘点文档对本次需求的入口、链路、语义和影响面描述完整:

  • 明确区分了虚拟列表与非虚拟表格两套链路
  • endExclusive = endInclusive + 1s 的语义转换说明有助于理解前后端协作
  • "在范围内/不在范围内" 的边界定义有助于避免后续需求跑偏

67-67: 文件路径引用正确,无需修改。验证确认 src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx 在实际代码库中存在,与文档引用一致。

src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx (1)

15-28: LGTM!

sessionId 字段的添加方式正确:

  • 作为可选字段加入 filters 接口
  • 通过 JSON.stringify(filters) 自动参与 filtersKey 计算,确保 sessionId 变化时缓存失效
  • 无需额外修改加载逻辑,现有 loadStats 已正确依赖 filters 对象
vitest.logs-sessionid-time-filter.config.ts (1)

11-61: 配置结构清晰,覆盖目标明确。

专项覆盖率配置合理:

  • 测试文件列表与覆盖目标文件(logs-query.tstime-range.ts)对应完整
  • 90% 阈值符合 PR 目标描述
  • setupFiles 路径 tests/setup.ts 存在
  • mock 重置策略完整(mockResetrestoreMocksclearMocks
  • happy-dom 环境与项目规范一致
  • 所有引用的测试文件均存在且位置正确
  • path 模块导入正确,别名配置有效
tests/unit/dashboard-logs-time-range-utils.test.ts (1)

1-46: 测试覆盖全面,结构清晰。

测试用例覆盖了主要功能和边界情况:

  • parseClockString 的 HH:MM 格式和无效输入处理
  • dateStringWithClockToTimestamp 的有效/无效日期处理
  • inclusiveEndTimestampFromExclusive 的往返验证和零值钳制
  • formatClockFromTimestamp 的格式化输出

需要注意:Line 19-23 和 Line 42-45 的测试依赖本地时区,在不同时区的 CI 环境中可能产生不一致结果。如果遇到问题,可以考虑使用固定时区或 mock Date

tests/unit/dashboard-logs-query-utils.test.ts (1)

1-90: 测试覆盖完整,验证了 URL 参数的解析与构建逻辑。

测试用例覆盖了关键场景:

  • sessionId 的 trim 处理
  • 数组参数取首值
  • statusCode: "!200"excludeStatusCode200 的映射
  • 无效数字的 undefined 处理
  • page 的边界值验证
  • build + parse 往返一致性

测试质量良好,符合 80% 覆盖率要求。

messages/zh-CN/dashboard.json (1)

63-69: i18n 翻译键添加正确。

新增的翻译键与现有风格一致,技术术语 "Session ID" 保持英文是合理的做法。

Also applies to: 102-102

src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (2)

214-219: Session ID 列实现正确,UI 一致性良好。

新增的 Session ID 列:

  • 表头使用 i18n 键 logs.columns.sessionId
  • 单元格提供 Tooltip 显示完整 ID
  • 点击复制功能与整体交互风格一致
  • 无数据时显示 muted dash 符合现有模式

Also applies to: 327-352


35-35: VirtualizedLogsTableFilters 接口扩展正确。

新增 sessionId?: string 字段与 LogsUrlFilters 类型保持一致。

tests/unit/repository/usage-logs-sessionid-suggestions.test.ts (3)

1-34: 辅助函数设计合理

sqlToString 递归遍历 SQL 对象结构的实现能正确处理 Drizzle ORM 生成的嵌套 SQL 片段。使用 visited Set 防止循环引用是一个好的防御性编程实践。


36-68: Mock query builder 实现正确

createThenableQuery 正确模拟了 Drizzle 的链式调用 API,并通过 opts 参数捕获调用参数以便断言。Promise.resolve(result) 的方式使其可 await。


70-84: 空白 term 测试覆盖充分

正确验证了空白字符串 " " 会直接返回空数组且不触发数据库查询,符合 repository 层的早期返回逻辑。

tests/unit/dashboard-logs-filters-time-range.test.tsx (2)

29-53: renderWithIntl 工具函数设计良好

正确使用 React 19 的 createRoot API,并提供了 unmount 方法用于测试清理。NextIntlClientProvider 配置了 timeZone="UTC" 确保时间计算一致性。


97-127: 页面字段泄漏测试有效

此测试用例验证了 Apply Filter 时 page 字段被正确过滤掉,防止分页状态泄漏到 URL 参数中。这是对 PR 中提到的回归修复的有效覆盖。

tests/unit/dashboard-logs-sessionid-suggestions-ui.test.tsx (4)

11-13: i18n mock 返回 key 本身是合理的测试策略

useTranslations: () => (key: string) => key 使得测试可以通过 i18n key 查找元素(如 logs.filters.searchSessionId),避免依赖具体翻译文本。


51-111: Popover mock 实现充分支持测试场景

自定义的 PopoverContext 和相关组件正确模拟了 Radix UI Popover 的核心行为,包括 open 状态管理和 asChild 模式。这种轻量级 mock 避免了引入完整 Radix 依赖的复杂性。


141-205: 防抖和最小长度测试覆盖全面

测试正确验证了:

  1. 输入长度 < 2 时不触发请求
  2. 防抖时间内(299ms)不触发请求
  3. 防抖时间到达(300ms)后触发单次请求

时间步进测试策略(分步推进 299ms + 1ms)能精确验证边界行为。


256-321: Provider scope 变更测试验证了关键回归修复

此测试对应 PR 描述中的回归修复"sessionId 联想会在 scope 变化时刷新请求"。正确验证了:

  1. 初始请求在 focus 后发出
  2. Provider 变更后触发第二次请求
  3. 第二次请求携带正确的 providerId: 1
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (2)

164-188: sessionId 单元格实现符合现有模式

新增的 sessionId 列正确复用了文件中其他列的 Tooltip 模式,通过 data-session-id 属性传递数据避免了闭包问题。truncate 类配合 Tooltip 展示完整值是良好的 UX 设计。


105-105: 表头和 colSpan 更新正确

新增 logs.columns.sessionId 表头列,并正确将空数据行的 colSpan 从 10 更新为 11,保持表格结构一致性。

Also applies to: 118-118

src/actions/usage-logs.ts (2)

22-23: 常量定义清晰

SESSION_ID_SUGGESTION_MIN_LENSESSION_ID_SUGGESTION_MAX_LEN 提供了明确的输入边界,便于维护和调整。


286-331: sessionId 联想 API 实现正确

实现要点:

  1. 权限控制:非 admin 用户强制使用 session.user.id,防止越权查询
  2. 输入验证trim() + slice() 处理边界,长度不足时早期返回空数组
  3. 错误处理:统一的 try-catch 和日志记录
src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx (2)

54-55: LGTM! 筛选条件解析逻辑清晰。

使用 parseLogsUrlFilters 统一解析 URL 参数,并通过 page ?? 1 确保页码有默认值,逻辑正确且符合预期。


148-151: LGTM! 筛选条件变更处理正确。

使用 buildLogsUrlQuery 构建查询参数,自动排除 page 参数(因为 newFilters 类型为 Omit<typeof filters, "page">),这确保了应用新筛选条件时会重置到第一页,修复了 PR 描述中提到的"Apply Filter 不再携带旧 page"问题。

src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx (1)

148-151: LGTM! 与非虚拟化版本保持一致。

handleFilterChange 使用 buildLogsUrlQuery 构建查询参数,逻辑与 usage-logs-view.tsx 保持一致。

src/app/[locale]/dashboard/logs/_utils/time-range.ts (2)

21-27: 注意:使用本地时区格式化。

formatClockFromTimestamp 使用 getHours()getMinutes()getSeconds() 基于本地时区格式化。请确认这与 UI 上显示的时间语义一致(用户期望看到本地时间)。如果需要 UTC 时间,应使用 getUTCHours() 等方法。


47-49: 函数命名与实际用途需确认。

inclusiveEndTimestampFromExclusive 从命名看是将"排他上界"转换为"包含上界"(减 1 秒)。但 PR 描述提到"UI 的包含式 end 在发送后端前转换为排他上界 +1s",这是相反的转换方向。

请确认此函数的使用场景:

  • 如果用于显示(将后端的排他时间戳转为 UI 显示的包含时间),当前实现正确
  • 如果用于提交(将 UI 包含时间转为后端排他时间),则应为 +1000 而非 -1000
tests/unit/repository/usage-logs-sessionid-filter.test.ts (3)

3-34: LGTM! sqlToString 辅助函数实现合理。

使用 visited Set 防止循环引用导致的无限递归,递归遍历 SQL 对象结构提取字符串值。这是合理的测试辅助实现。


78-98: LGTM! sessionId trim 精确匹配测试。

正确验证了 sessionId 会被 trim 后再进行匹配,确保 "abc" 出现在 where 子句中而 " abc " 不会出现。


100-162: 测试覆盖全面。

findUsageLogsWithDetailsfindUsageLogsStats 提供了相同模式的测试覆盖,确保 sessionId 过滤逻辑在所有查询路径上保持一致。

Also applies to: 164-206, 208-266, 268-308

src/app/[locale]/dashboard/logs/_utils/logs-query.ts (4)

1-14: LGTM! 接口定义清晰。

LogsUrlFilters 接口涵盖所有筛选字段,使用 ? 标记可选字段。字段类型与 URL 参数语义匹配。


16-32: LGTM! 辅助解析函数实现正确。

  • firstString 正确处理 Next.js searchParams 的 string | string[] | undefined 类型
  • parseIntParam 使用 Number.isFinite 过滤 NaN 和 Infinity
  • parseStringParam 返回 trim 后的非空字符串或 undefined

34-60: LGTM! 解析逻辑完整。

parseLogsUrlFilters 正确处理:

  • statusCode === "!200" 的特殊情况设置 excludeStatusCode200: true
  • 页码小于 1 时返回 undefined
  • 所有字段的类型转换和边界处理

88-90: page > 1 时才写入 URL 的设计合理。

这确保了默认第一页不会在 URL 中显示冗余参数,保持 URL 简洁。与 parseLogsUrlFilterspage ?? 1 的默认值处理配合良好。

src/repository/usage-logs.ts (4)

15-16: LGTM!

sessionId 字段添加到 UsageLogFilters 接口,文档注释清晰说明了精确匹配语义和空值处理逻辑。


147-150: sessionId 过滤逻辑实现正确。

trim + 空值检查的模式在 findUsageLogsBatchfindUsageLogsWithDetailsfindUsageLogsStats 三个函数中保持一致,确保了过滤行为的统一性。


605-608: 条件 JOIN 优化逻辑正确。

仅在 keyId 过滤器生效时才 innerJoin keysTable,避免了无意义的 JOIN 操作,提升了查询性能。


570-617: findUsageLogSessionIdSuggestions 实现良好。

  • limit 使用 Math.min(50, Math.max(1, ...)) 进行边界约束
  • 空 term 提前返回空数组
  • min(created_at) 降序排序,优先展示最近活跃的 sessionId
  • 结果过滤确保返回非空字符串

实现符合联想查询的预期行为。

src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx (4)

321-348: LGTM! handleApply 白名单模式有效防止 page 泄漏。

显式解构并传递 filter 属性,而非直接传递整个 localFilters 对象,确保了只有预期的过滤条件被提交,符合 PR 描述中修复的回归点。


398-414: 时间显示逻辑正确。

使用 inclusiveEndTimestampFromExclusive 将排他上界 (exclusive endTime) 转换为包含式时间戳用于 UI 显示,与 formatClockFromTimestamp 配合正确展示秒级精度。


463-512: 时间输入控件实现合理。

  • 使用 type="time" 配合 step={1} 支持秒级精度
  • 日期未选择时禁用时间输入 (disabled={!displayStartDate})
  • 更新逻辑正确处理 inclusive/exclusive 转换

实现符合需求。


706-761: Session ID 联想 UI 实现完整。

  • 使用 PopoverAnchor 将 Input 作为锚点,避免额外的 trigger 元素
  • onOpenAutoFocus={(e) => e.preventDefault()} 防止 Popover 打开时抢夺焦点
  • shouldFilter={false} 正确配置,因为过滤由后端完成
  • 加载状态和空结果状态都有合适的提示文案

实现符合 UX 预期。

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

@github-actions github-actions bot added the size/L Large PR (< 1000 lines) label Jan 14, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR adds second-level time filtering and Session ID filtering/suggestions to the Dashboard Logs page. The implementation is well-structured with proper error handling, i18n support across all 5 languages, and comprehensive test coverage.

PR Size: L

  • Lines changed: 2143 (2006 additions + 137 deletions)
  • Files changed: 27

Split Suggestion for L-sized PR: Consider splitting future similar PRs into:

  1. Backend changes (repository + actions)
  2. Frontend filter UI changes
  3. Table column additions
  4. Test files

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 Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean (ILIKE query uses parameterized input, no SQL injection risk)
  • Error handling - Clean (proper try/catch with logging in actions, graceful fallbacks in UI)
  • Type safety - Clean (proper TypeScript types, no any usage in new code)
  • Documentation accuracy - Clean (new callchain doc accurately reflects implementation)
  • Test coverage - Adequate (comprehensive unit tests for new functionality, 95.52% coverage reported)
  • Code clarity - Good

Validation Notes

Potential concerns investigated and validated:

  1. Session ID ILIKE query performance: The PR description acknowledges this is not scalable for large datasets but mitigates with debounce (300ms), minLen (2 chars), and limit (20/50). This is documented as out-of-scope for optimization in this PR.

  2. handleApply whitelist pattern: The explicit field whitelist in handleApply() correctly prevents page field leakage - this is the documented regression fix and is tested.

  3. Clipboard API error handling: The handleCopySessionIdClick function properly catches clipboard errors and shows user feedback via toast.

  4. Time range edge cases: The inclusiveEndTimestampFromExclusive function properly clamps at 0 to prevent negative timestamps.

  5. i18n completeness: All 5 required languages (en, ja, ru, zh-CN, zh-TW) have the new translation keys added.


Automated review by Claude AI

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 15, 2026
@YangQing-Lin YangQing-Lin reopened this Jan 15, 2026
@YangQing-Lin
Copy link
Collaborator Author

修复了上面 Gemini 以及 CodeRabbit 提出的问题与优化建议

@ding113
Copy link
Owner

ding113 commented Jan 19, 2026

为了能够实现在请求中大海捞针,这个部分还需要实现一个功能,即在向客户端报错时,在 message 内附带 CCH 内部的 Session ID。否则还是很难通过用户反馈定位到具体请求。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
src/app/v1/_lib/codex/chat-completions-handler.ts (2)

67-78: 缺少 Session ID 附加

此处 OpenAI 格式的 model 缺失错误直接返回 Response,未通过 attachSessionIdToErrorResponse 包装。与其他错误路径(如 Lines 49-60、149-159)的处理方式不一致。

🔧 建议修复
       if (!openAIRequest.model) {
-        return new Response(
+        const response = new Response(
           JSON.stringify({
             error: {
               message: "Invalid request: model is required",
               type: "invalid_request_error",
               code: "missing_required_fields",
             },
           }),
           { status: 400, headers: { "Content-Type": "application/json" } }
         );
+        return await attachSessionIdToErrorResponse(session.sessionId, response);
       }

125-139: 转换错误同样缺少 Session ID 附加

请求格式转换失败时,返回的错误响应未附加 Session ID,与其他错误路径处理不一致。

🔧 建议修复
       } catch (transformError) {
         logger.error("[ChatCompletions] Request transformation failed:", {
           context: transformError,
         });
-        return new Response(
+        const response = new Response(
           JSON.stringify({
             error: {
               message: "Failed to transform request format",
               type: "invalid_request_error",
               code: "transformation_error",
             },
           }),
           { status: 400, headers: { "Content-Type": "application/json" } }
         );
+        return await attachSessionIdToErrorResponse(session.sessionId, response);
       }

@YangQing-Lin
Copy link
Collaborator Author

YangQing-Lin commented Jan 19, 2026

为了能够实现在请求中大海捞针,这个部分还需要实现一个功能,即在向客户端报错时,在 message 内附带 CCH 内部的 Session ID。否则还是很难通过用户反馈定位到具体请求。

已按需求更新代码:

image image image image

@ding113 ding113 merged commit d9e85ac into ding113:dev Jan 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:session area:UI enhancement New feature or request size/L Large PR (< 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants