Conversation
…nteractions - Extend auto-return timer initiation to include user click item event - Ensure consistent behavior when transitioning to user browsing state - Improve user experience by automatically returning to current subtitle after interactions
Walkthrough在字幕滚动状态机中,进入 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as 用户
participant L as 字幕列表
participant SM as 滚动状态机
participant T as 自动返回计时器
U->>L: 手动滚动 (USER_MANUAL_SCROLL)
L->>SM: ScrollTrigger.USER_MANUAL_SCROLL
SM->>SM: 进入 USER_BROWSING
SM->>T: 启动计时器
rect rgba(200,240,255,0.25)
note over U,L: 新增触发路径
U->>L: 点击字幕项 (USER_CLICK_ITEM)
L->>SM: ScrollTrigger.USER_CLICK_ITEM
SM->>SM: 进入/保持 USER_BROWSING
SM->>T: 启动/重置计时器
end
T-->>SM: 超时
SM->>L: 自动返回到当前字幕
sequenceDiagram
autonumber
actor Caller as 启动器
participant UBS as UvBootstrapperService
participant Reg as 区域探测/镜像解析
participant DL as 下载器
Caller->>UBS: 请求安装 UV(platform, arch)
UBS->>UBS: 生成 key (platform|arch)
alt key 已在 activeDownloads
UBS-->>Caller: 跳过/返回(正在下载)
else 未在 activeDownloads
UBS->>UBS: activeDownloads.add(key)
UBS->>UBS: 检查本地已安装/已下载(短路可能)
UBS->>Reg: 探测区域并获取镜像列表
Reg-->>UBS: 镜像列表(或错误/回退)
UBS->>UBS: 再次检查 activeDownloads(防竞态)
UBS->>DL: 执行下载与安装
DL-->>UBS: 成功/失败
UBS->>UBS: activeDownloads.delete(key)
UBS-->>Caller: 返回结果
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/renderer/src/**/*.{ts,tsx,scss,css}
📄 CodeRabbit inference engine (CLAUDE.md)
优先使用 CSS 变量,避免硬编码样式值(颜色等)
Files:
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
尺寸与时长等不要硬编码,优先使用 useTheme() 的 token 或集中样式变量(如 motionDurationMid、borderRadiusSM/MD)
Files:
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: 定制 antd 组件样式优先使用 styled-components 包装 styled(Component),避免全局 classNames
项目的图标统一使用 lucide-react,不使用 emoji 作为图标
组件/Hook 顶层必须通过 useStore(selector) 使用 Zustand,禁止在 useMemo/useEffect 内部调用 store Hook
避免使用返回对象的 Zustand 选择器(如 useStore(s => ({ a: s.a, b: s.b })));应使用单字段选择器或配合 shallow 比较器
遵循 React「副作用与状态更新」规范:渲染纯函数、Effect 三分法、幂等更新、稳定引用、严格清理、禁止写回自身依赖、Provider 值 memo、外部状态 selector 稳定等
统一使用 loggerService 记录日志而不是 console
logger 使用示例中第二个参数必须为对象字面量(如 logger.error('msg', { error }))
任何组件或页面都不要写入 media 元素的 currentTime,播放器控制由编排器统一负责
在 styled-components 中:主题相关属性使用 AntD CSS 变量(如 var(--ant-color-bg-elevated));
在 styled-components 中:设计系统常量(尺寸、动画、层级、字体、毛玻璃等)使用 JS 常量(如 SPACING、BORDER_RADIUS、Z_INDEX、FONT_SIZES 等)
Files:
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
**/player/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Player 页面:统一在组件顶层使用 Zustand selector,禁止在 useMemo/useEffect 内调用 store Hook;useSubtitleEngine 通过参数传入 subtitles 等防御处理
Files:
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (windows-latest, 20)
- GitHub Check: test (ubuntu-latest, 20)
- GitHub Check: test (macos-latest, 20)
| if ( | ||
| trigger === ScrollTrigger.USER_MANUAL_SCROLL || | ||
| trigger === ScrollTrigger.USER_CLICK_ITEM | ||
| ) { |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
修复正确,但建议补充用户浏览时点击重置计时器的逻辑
此修复已正确解决了从 LOCKED_TO_CURRENT 或 TRANSITIONING 状态点击字幕项时启动自动返回计时器的问题。
但发现一个潜在的不一致性:当用户已经处于 USER_BROWSING 状态时再次点击字幕项,计时器不会重置。对比第 86 行的手动滚动行为,在 USER_BROWSING 状态下手动滚动会触发自转换并重置计时器。为保持交互一致性,建议在 USER_BROWSING 状态转换表中也添加 USER_CLICK_ITEM 的自转换。
请运行以下脚本验证当前状态机的转换逻辑,并确认是否需要支持浏览时点击重置计时器:
如果需要支持该场景,可应用以下修改:
[SubtitleScrollState.USER_BROWSING]: {
[ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING,
[ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT,
- [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING // 重置定时器
+ [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING, // 重置定时器
+ [ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING // 重置定时器
},🏁 Script executed:
#!/bin/bash
# 描述:检查 USER_BROWSING 状态的所有转换定义
# 查找 USER_BROWSING 状态的转换配置
rg -A 5 '\[SubtitleScrollState\.USER_BROWSING\]:' src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.tsLength of output: 316
在 USER_BROWSING 状态中点击字幕项应重置自动返回定时器
当前状态机配置只对 USER_MANUAL_SCROLL 触发自转换以重置计时器,却未对 USER_CLICK_ITEM 做同样处理。请在配置中添加:
[SubtitleScrollState.USER_BROWSING]: {
[ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING,
[ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT,
[ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING,
+ [ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING
},📝 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.
| if ( | |
| trigger === ScrollTrigger.USER_MANUAL_SCROLL || | |
| trigger === ScrollTrigger.USER_CLICK_ITEM | |
| ) { | |
| [SubtitleScrollState.USER_BROWSING]: { | |
| [ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING, | |
| [ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT, | |
| [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING, | |
| [ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING | |
| }, |
🤖 Prompt for AI Agents
In src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts around
lines 240 to 243, the USER_BROWSING state only treats
ScrollTrigger.USER_MANUAL_SCROLL as a self-transition to reset the auto-return
timer; add ScrollTrigger.USER_CLICK_ITEM to the same transition so clicks on
subtitle items also reset the timer. Update the transition condition or trigger
list to include USER_CLICK_ITEM alongside USER_MANUAL_SCROLL so both actions
perform the same self-transition and timer-reset behavior.
…ency control - Introduce `activeDownloads` set to track ongoing downloads and prevent concurrent downloads of the same version. - Refactor download logic to include defensive checks ensuring no concurrent downloads occur. - Improve error handling during region detection to default to standard UV download sources if detection fails. - Ensure proper cleanup of `activeDownloads` set after download completion or failure. - Enhance logging to provide clearer insights into download initiation and status.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/main/services/UvBootstrapperService.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: 定制 antd 组件样式优先使用 styled-components 包装 styled(Component),避免全局 classNames
项目的图标统一使用 lucide-react,不使用 emoji 作为图标
组件/Hook 顶层必须通过 useStore(selector) 使用 Zustand,禁止在 useMemo/useEffect 内部调用 store Hook
避免使用返回对象的 Zustand 选择器(如 useStore(s => ({ a: s.a, b: s.b })));应使用单字段选择器或配合 shallow 比较器
遵循 React「副作用与状态更新」规范:渲染纯函数、Effect 三分法、幂等更新、稳定引用、严格清理、禁止写回自身依赖、Provider 值 memo、外部状态 selector 稳定等
统一使用 loggerService 记录日志而不是 console
logger 使用示例中第二个参数必须为对象字面量(如 logger.error('msg', { error }))
任何组件或页面都不要写入 media 元素的 currentTime,播放器控制由编排器统一负责
在 styled-components 中:主题相关属性使用 AntD CSS 变量(如 var(--ant-color-bg-elevated));
在 styled-components 中:设计系统常量(尺寸、动画、层级、字体、毛玻璃等)使用 JS 常量(如 SPACING、BORDER_RADIUS、Z_INDEX、FONT_SIZES 等)
Files:
src/main/services/UvBootstrapperService.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (macos-latest, 20)
- GitHub Check: test (ubuntu-latest, 20)
- GitHub Check: test (windows-latest, 20)
🔇 Additional comments (1)
src/main/services/UvBootstrapperService.ts (1)
196-196: 良好的设计:新增下载去重机制。使用
activeDownloads集合跟踪进行中的下载操作是防止重复下载的有效方法。这个字段与 try/finally 块配合使用,确保了即使出现异常也能正确清理状态。
| if (this.activeDownloads.has(key)) { | ||
| logger.warn('uv 正在下载中', { platform, arch }) | ||
| return false | ||
| } | ||
|
|
||
| if (this.regionDetectionPromise) { | ||
| try { | ||
| await this.regionDetectionPromise | ||
| } catch (error) { | ||
| logger.warn('地区检测失败,将使用默认 UV 下载源', { | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }) | ||
| this.activeDownloads.add(key) | ||
| try { | ||
| // 仅当请求的平台与当前进程一致时,才额外使用缓存的系统检测结果 | ||
| if (platform === process.platform && arch === process.arch) { | ||
| const installation = await this.checkUvInstallation() | ||
| if (installation.exists && installation.isDownloaded) { | ||
| logger.info('uv 已存在,跳过下载', { platform, arch, path: installation.path }) | ||
| return true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const version = this.getUvVersion(platform, arch) | ||
| if (!version) { | ||
| logger.error('不支持的平台', { platform, arch }) | ||
| return false | ||
| } | ||
| if (this.regionDetectionPromise) { | ||
| try { | ||
| await this.regionDetectionPromise | ||
| } catch (error) { | ||
| logger.warn('地区检测失败,将使用默认 UV 下载源', { | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| logger.info('开始下载 uv', { | ||
| platform, | ||
| arch, | ||
| version: version.version, | ||
| url: version.url | ||
| }) | ||
| const version = this.getUvVersion(platform, arch) | ||
| if (!version) { | ||
| logger.error('不支持的平台', { platform, arch }) | ||
| return false | ||
| } | ||
|
|
||
| // 再次确认未有并发下载(防御性检查) | ||
| if (this.downloadProgress.has(key) || this.downloadController.has(key)) { | ||
| logger.warn('uv 正在下载中', { platform, arch }) | ||
| return false | ||
| } | ||
|
|
||
| return await this.performDownload(platform, arch, version, onProgress) | ||
| logger.info('开始下载 uv', { | ||
| platform, | ||
| arch, | ||
| version: version.version, | ||
| url: version.url | ||
| }) | ||
|
|
||
| return await this.performDownload(platform, arch, version, onProgress) | ||
| } finally { | ||
| this.activeDownloads.delete(key) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
实现正确:有效防止了重复下载。
整体重构达成了防止重复下载的目标:
- 下载去重:通过
activeDownloads集合有效防止并发下载(第 516-519 行) - 状态清理:try/finally 块确保即使出现异常也能清理
activeDownloads(第 562-564 行) - 线程安全:在 JavaScript 事件循环模型下,检查-添加模式(第 516、521 行)是安全的
可选优化建议:
-
简化检查逻辑:第 510-514 行的早期检查与第 525 行
checkUvInstallation()内部的下载检查存在一定重复。虽然早期检查是性能优化(避免进入 try 块和添加到 activeDownloads),但可以考虑统一到一处以提高代码可维护性。 -
移除防御性检查:第 548-552 行的
downloadProgress/downloadController检查在正常流程中永远不会触发(因为第 516 行的activeDownloads检查已确保唯一性)。如果对activeDownloads机制有信心,可以考虑移除此检查以简化逻辑。 -
优化地区检测时机:第 532-540 行的地区检测可以移到第 521 行之前执行,减少
activeDownloads的持有时间,提升并发性能。
这些都是次要优化点,当前实现已经足够可靠。
🤖 Prompt for AI Agents
In src/main/services/UvBootstrapperService.ts around lines 516 to 564, the code
works but can be simplified and optimized: (1) Remove the redundant early
platform/arch existence check at lines ~510-514 and consolidate installation
existence check into the try block after adding to activeDownloads so there is a
single canonical check path; (2) Remove the defensive check against
downloadProgress/downloadController at lines ~548-552 since activeDownloads
already guarantees uniqueness; (3) Move the regionDetectionPromise await (lines
~532-540) to before adding the key to activeDownloads so region detection does
not hold the activeDownloads lock; implement these three changes to reduce
duplication and shorten the time the activeDownloads lock is held.
Summary
Testing
Summary by CodeRabbit
Bug Fixes
Chores