Conversation
…ling speeds (#210) Fixed an issue where clicking the playback speed button to cycle through favorite speeds would update the store state but not actually change the video playback rate. The problem was that cycleFavoriteRate() only updated the Zustand store without going through the PlayerOrchestrator. According to the project's architecture, all playback control operations must be executed through the orchestrator to properly sync with the video element. Menu clicks worked because they called setSpeed() which properly invokes orchestrator.requestSetPlaybackRate(). Changes: - Calculate next favorite speed before calling cycleFavoriteRate() - Call setSpeed() after updating store to apply rate through orchestrator - Maintains same code path as menu selection for consistency
…ion (#209) * feat(player): implement HLS session progress polling with visual feedback - Add waitForSessionReady to poll session creation progress before playback - Integrate useSessionProgress hook for reusable progress tracking logic - Display deterministic progress bar with percentage and stage information during session initialization - Add SessionService.getSessionProgress API with HTTP 425 handling for in-progress state - Update backend submodule to v64aaf0b with HLS session progress support - Remove excessive debug logging in SubtitleOverlay to reduce console noise Changes: PlayerPage: - Add waitForSessionReady state and sessionProgress state for UI rendering - Implement polling loop with 2s interval to fetch session progress until ready - Display progress bar with stage text and percentage during session creation - Handle HTTP 425 (session not ready) gracefully in progress polling - Fallback to default playlist URL if progress response URL parsing fails useSessionProgress Hook: - Provide reusable session progress polling with configurable interval (default 2s) - Auto-stop polling when session is ready or error occurs - Expose startPolling, stopPolling, reset methods for lifecycle control - Implement progress threshold (5%) to reduce log noise SessionService: - Add getSessionProgress(sessionId) API to fetch session creation progress - Define SessionProgressResponse and AudioProgressInfo interfaces - Treat HTTP 425 as normal in-progress state (not error) This enhancement provides real-time visual feedback during HLS session initialization, improving user experience by showing progress instead of generic "loading..." spinner. The polling mechanism ensures playback starts only after the session is fully ready, preventing premature playlist access. * refactor(PlayerPage): replace progress bar hardcoded values with theme tokens - Import theme constants (SPACING, FONT_SIZES, ANIMATION_DURATION, etc.) - Replace hardcoded width (240px) with SPACING.XXL * 5 - Replace hardcoded heights (4px) with COMPONENT_TOKENS.PROGRESS_BAR.TRACK_HEIGHT_HOVER - Replace hardcoded border-radius (2px) with COMPONENT_TOKENS.PROGRESS_BAR.TRACK_BORDER_RADIUS - Replace hardcoded font-sizes (16px, 14px) with FONT_SIZES.BASE and FONT_SIZES.SM - Replace hardcoded transition timing with ANIMATION_DURATION.SLOW and EASING.STANDARD - Fix: Add setTranscodeStatus('completed') call after HLS session completes Changes ensure theme consistency, maintainability, and proper status tracking across components. Progress bar now respects design system tokens for cross-theme compatibility.
- Add MediaServer_CleanupCachesForFile IPC channel in shared types - Implement cleanupCachesForFile method in MediaServerService with asset hash calculation - Add TranscodeCacheCleanupResult type for cleanup operation results - Register IPC handler in main process to expose cleanup functionality - Expose cleanupCachesForFile API to renderer through preload bridge - Integrate cache cleanup into HomePage video deletion flow Changes: - IpcChannel: Add MediaServer_CleanupCachesForFile channel - MediaServerService: Add cleanupCachesForFile, calculateAssetHash, removeDirectoryIfExists methods - ipc.ts: Register handler for cleanup cache IPC channel - preload: Expose cleanupCachesForFile to renderer API - HomePage: Lookup video file path and trigger cache cleanup after deletion This enhancement ensures HLS segments and audio cache directories are automatically removed when users delete videos from the library, preventing unnecessary disk space usage and maintaining cache hygiene.
- remove 'hls-player-missing' error type from ExtendedErrorType - clean up related UI components and styled components - remove HLS-specific status display from VideoErrorRecovery modal
- trim video and audio codec strings to avoid whitespace issues - ensure compatibility checks handle undefined codec values - add missing codec reasons to incompatibility list - improve robustness of codec detection logic
- Move the creation of target and temp directories outside of the try block for better scope management. - Ensure the cleanup of temporary directories in the finally block to prevent leftover files after download completion or failure. - Improve resource management by guaranteeing temp files are removed regardless of download success.
- Add useRef to track initial mount state - Prevent volume indicator from showing on component's first render - Ensure indicator only appears on subsequent volume changes This change enhances user experience by avoiding unnecessary indicator display during initial component load.
…h checks - Add logic to check for cached binary paths before initiating downloads - Ensure system checks are only performed when platform and architecture match the current process - Improve logging to reflect when UV binaries are already present and downloads are skipped - Refactor download logic to optimize performance and reduce unnecessary downloads This update optimizes the UV download process by leveraging cached paths and system checks, ensuring efficient resource utilization and improved performance.
- Add cleanup for success timeout to prevent potential memory leaks - Ensure proper handling of download completion state transitions This fix addresses the issue of unhandled timeouts after FFprobe download success, ensuring resources are properly managed.
- Replace hardcoded gap values with theme-based spacing constants - Ensure consistent styling across components using SPACING.XS This change improves maintainability and consistency in the styling of the FFprobeSection component by utilizing predefined theme constants.
- Add a ref to handle completion timeout for download success state - Clear existing timeout before setting a new one to prevent overlapping - Ensure timeout is cleared on component unmount to avoid memory leaks This update ensures that the download completion state is managed properly, preventing potential issues with overlapping timeouts and ensuring proper cleanup.
…display - Eliminate logger usage for state changes in TranscodeLoadingIndicator - Simplify component by removing unnecessary debug logs This change reduces the complexity of the component by removing logging that was not essential for the functionality of the loading indicator.
…g function - Ensure proper cleanup by adding an empty return statement in the download progress polling function. - This change prevents potential issues with function execution flow and enhances code readability.
- Add successTimeoutRef to handle and clear timeout for success state transition - Ensure proper cleanup of success timeout on component unmount - Prevent multiple timeout instances by clearing existing timeout before setting a new one These changes improve the reliability of the download success state transition by ensuring timeouts are properly managed and cleaned up.
- Update font size in StatusContainer to use FONT_SIZES.SM from theme constants for consistency across the application. This change ensures uniformity in font sizing, improving the visual coherence of the UI.
…le existence check - Remove synchronous fs.existsSync checks and replace them with asynchronous stat calls to improve non-blocking behavior. - Simplify logic in removeDirectoryIfExists by directly attempting to remove directories without prior existence check. - Enhance error handling for file existence validation in cleanupCachesForFile method.
…download controllers - Add condition to check for existing download controllers to avoid initiating multiple downloads for the same platform and architecture. - Improve logging to warn when a download is already in progress.
- Add specific error handling for ENOENT code in cleanupCachesForFile method - Ensure other errors are propagated correctly
Walkthrough本次变更新增“按文件清理媒体转码缓存”的 IPC 通道与服务实现,扩展预加载层 API;在播放器引入会话进度轮询与可视化进度 UI;调整播放速率点击逻辑;精简若干日志;改进 uv 下载引导的预检查与清理;设置页的 FFmpeg/FFprobe 下载完成状态采用延时复位与主题化样式;编解码兼容性检查区分缺失与不支持。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor R as Renderer (UI)
participant P as Preload (contextBridge)
participant M as Main (IPC)
participant S as MediaServerService
participant FS as File System
rect rgb(240,248,255)
note over R,P: 发起按文件清理请求
R->>P: api.mediaServer.cleanupCachesForFile(filePath)
P->>M: IPC MediaServer_CleanupCachesForFile(filePath)
M->>S: cleanupCachesForFile(filePath)
end
rect rgb(245,255,250)
note over S: 计算资产哈希与缓存目录
S->>FS: stat/read (采样文件头尾/全量)
S->>S: 计算 SHA-256 短哈希
S->>FS: rm -r HLS 缓存目录
S->>FS: rm -r 音频缓存目录
S-->>M: {assetHash,hls{...},audio{...}}
M-->>P: 结果
P-->>R: 结果
end
sequenceDiagram
autonumber
actor R as Renderer PlayerPage
participant SS as SessionService
participant MS as Media Server (HTTP)
rect rgb(253,246,227)
note over R: 创建转码会话(既有流程)
R->>MS: POST /sessions
MS-->>R: {session_id}
end
rect rgb(230,245,255)
note over R,MS: 轮询进度直到就绪或出错
loop poll progress
R->>SS: getSessionProgress(session_id)
SS->>MS: GET /{id}/progress
alt 200 OK
MS-->>SS: {is_ready,progress_percent,stage,...}
SS-->>R: 同上
else 425 Too Early
MS-->>SS: 425
SS-->>R: 抛出以触发重试/等待
else error
MS-->>SS: 4xx/5xx
SS-->>R: 抛出错误(终止)
end
end
end
rect rgb(240,255,240)
alt 就绪
R->>MS: GET playlist.m3u8(或使用返回 URL)
MS-->>R: HLS 源
R->>R: 切换播放器到 HLS
else 出错/取消
R->>R: 回退到原文件 URL 或显示错误
end
end
Estimated code review effort🎯 4 (复杂) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/renderer/src/services/CodecCompatibilityChecker.ts (1)
500-527: 建议改进用户错误消息的清晰度。当编解码器缺失时,
getCodecDisplayName返回"未知",导致错误消息如"视频编解码器 未知 不支持"。这对用户来说不够清晰,无法区分:
- 情况 A:无法检测到编解码器信息
- 情况 B:检测到编解码器但浏览器不支持
建议利用
incompatibilityReasons中已有的区分信息,生成更精确的错误消息:public static generateErrorMessage(compatibilityResult: CodecCompatibilityResult): string { if (!compatibilityResult.needsTranscode) { return '视频格式兼容' } - const { videoSupported, audioSupported, detectedCodecs } = compatibilityResult + const { videoSupported, audioSupported, detectedCodecs, incompatibilityReasons } = compatibilityResult // 获取友好的编解码器名称 const videoCodecName = this.getCodecDisplayName(detectedCodecs.video, 'video') const audioCodecName = this.getCodecDisplayName(detectedCodecs.audio, 'audio') + // 检查是否存在缺失编解码器的情况 + const videoMissing = incompatibilityReasons.includes('video-codec-missing') + const audioMissing = incompatibilityReasons.includes('audio-codec-missing') + // 同时不支持视频和音频 if (!videoSupported && !audioSupported) { + if (videoMissing && audioMissing) { + return '无法检测到视频和音频编解码器信息,需要转码' + } + if (videoMissing) { + return `无法检测到视频编解码器,音频编解码器 ${audioCodecName} 不支持` + } + if (audioMissing) { + return `视频编解码器 ${videoCodecName} 不支持,无法检测到音频编解码器` + } return `视频格式不支持:视频编解码器 ${videoCodecName} 和音频编解码器 ${audioCodecName} 均不兼容` } // 仅视频不支持 if (!videoSupported) { + if (videoMissing) { + return '无法检测到视频编解码器信息,需要转码' + } return `视频编解码器 ${videoCodecName} 不支持,需要转码` } // 仅音频不支持 if (!audioSupported) { + if (audioMissing) { + return '无法检测到音频编解码器信息,可能导致无声音播放' + } return `音频编解码器 ${audioCodecName} 不支持,可能导致无声音播放` } return '视频格式需要转码以确保最佳兼容性' }src/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsx (1)
48-64: 建议添加边界情况的日志记录。虽然当前逻辑已正确处理了边界情况(通过
% favoriteRates.length避免索引越界),但添加日志记录可以帮助调试潜在问题,例如:
favoriteRates为空但进入了 if 分支- 索引计算异常
- 速率应用失败
可以在关键步骤添加日志:
import { loggerService } from '@renderer/services/logger.service' const handleLeftClick = () => { if (favoriteRates.length > 0) { const currentIndex = usePlayerStore.getState().currentFavoriteIndex const nextIndex = (currentIndex + 1) % favoriteRates.length const nextRate = favoriteRates[nextIndex] loggerService.debug('Cycling favorite rate', { currentIndex, nextIndex, nextRate, favoriteRates }) cycleFavoriteRate() setSpeed(nextRate) } else { toggleMenu() } }基于 coding guidelines: 统一使用 loggerService 记录日志而不是 console。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (17)
backend(1 hunks)packages/shared/IpcChannel.ts(1 hunks)src/main/ipc.ts(1 hunks)src/main/services/MediaServerService.ts(3 hunks)src/main/services/UvBootstrapperService.ts(3 hunks)src/preload/index.ts(1 hunks)src/renderer/src/pages/home/HomePage.tsx(3 hunks)src/renderer/src/pages/player/PlayerPage.tsx(8 hunks)src/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsx(1 hunks)src/renderer/src/pages/player/components/SubtitleOverlay.tsx(0 hunks)src/renderer/src/pages/player/components/TranscodeLoadingIndicator.tsx(0 hunks)src/renderer/src/pages/player/components/VideoErrorRecovery.tsx(0 hunks)src/renderer/src/pages/player/components/VolumeIndicator.tsx(2 hunks)src/renderer/src/pages/settings/FFmpegSection.tsx(4 hunks)src/renderer/src/pages/settings/FFprobeSection.tsx(5 hunks)src/renderer/src/services/CodecCompatibilityChecker.ts(2 hunks)src/renderer/src/services/SessionService.ts(2 hunks)
💤 Files with no reviewable changes (3)
- src/renderer/src/pages/player/components/SubtitleOverlay.tsx
- src/renderer/src/pages/player/components/VideoErrorRecovery.tsx
- src/renderer/src/pages/player/components/TranscodeLoadingIndicator.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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/preload/index.tssrc/renderer/src/pages/settings/FFmpegSection.tsxsrc/renderer/src/services/SessionService.tssrc/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsxsrc/renderer/src/pages/settings/FFprobeSection.tsxsrc/renderer/src/pages/player/PlayerPage.tsxpackages/shared/IpcChannel.tssrc/renderer/src/services/CodecCompatibilityChecker.tssrc/main/ipc.tssrc/main/services/MediaServerService.tssrc/renderer/src/pages/home/HomePage.tsxsrc/main/services/UvBootstrapperService.tssrc/renderer/src/pages/player/components/VolumeIndicator.tsx
src/renderer/src/**/*.{ts,tsx,scss,css}
📄 CodeRabbit inference engine (CLAUDE.md)
优先使用 CSS 变量,避免硬编码样式值(颜色等)
Files:
src/renderer/src/pages/settings/FFmpegSection.tsxsrc/renderer/src/services/SessionService.tssrc/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsxsrc/renderer/src/pages/settings/FFprobeSection.tsxsrc/renderer/src/pages/player/PlayerPage.tsxsrc/renderer/src/services/CodecCompatibilityChecker.tssrc/renderer/src/pages/home/HomePage.tsxsrc/renderer/src/pages/player/components/VolumeIndicator.tsx
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
尺寸与时长等不要硬编码,优先使用 useTheme() 的 token 或集中样式变量(如 motionDurationMid、borderRadiusSM/MD)
Files:
src/renderer/src/pages/settings/FFmpegSection.tsxsrc/renderer/src/services/SessionService.tssrc/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsxsrc/renderer/src/pages/settings/FFprobeSection.tsxsrc/renderer/src/pages/player/PlayerPage.tsxsrc/renderer/src/services/CodecCompatibilityChecker.tssrc/renderer/src/pages/home/HomePage.tsxsrc/renderer/src/pages/player/components/VolumeIndicator.tsx
**/player/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Player 页面:统一在组件顶层使用 Zustand selector,禁止在 useMemo/useEffect 内调用 store Hook;useSubtitleEngine 通过参数传入 subtitles 等防御处理
Files:
src/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsxsrc/renderer/src/pages/player/PlayerPage.tsxsrc/renderer/src/pages/player/components/VolumeIndicator.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-17T14:59:36.985Z
Learnt from: CR
PR: mkdir700/EchoPlayer#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-17T14:59:36.985Z
Learning: Applies to **/player/**/*.{ts,tsx} : Player 页面:统一在组件顶层使用 Zustand selector,禁止在 useMemo/useEffect 内调用 store Hook;useSubtitleEngine 通过参数传入 subtitles 等防御处理
Applied to files:
src/renderer/src/pages/player/components/VolumeIndicator.tsx
🧬 Code graph analysis (7)
src/renderer/src/pages/settings/FFmpegSection.tsx (1)
src/renderer/src/infrastructure/styles/theme.ts (2)
SPACING(43-58)FONT_SIZES(25-40)
src/renderer/src/services/SessionService.ts (1)
src/renderer/src/services/Logger.ts (1)
error(422-424)
src/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsx (1)
src/renderer/src/state/stores/player.store.ts (1)
usePlayerStore(609-611)
src/renderer/src/pages/settings/FFprobeSection.tsx (1)
src/renderer/src/infrastructure/styles/theme.ts (2)
SPACING(43-58)FONT_SIZES(25-40)
src/renderer/src/pages/player/PlayerPage.tsx (2)
src/renderer/src/services/SessionService.ts (2)
SessionService(154-600)SessionError(138-147)src/renderer/src/infrastructure/styles/theme.ts (5)
SPACING(43-58)COMPONENT_TOKENS(164-334)ANIMATION_DURATION(91-100)EASING(103-114)FONT_SIZES(25-40)
src/main/ipc.ts (1)
src/main/services/MediaServerService.ts (1)
mediaServerService(723-723)
src/main/services/MediaServerService.ts (1)
src/main/utils/index.ts (1)
getDataPath(11-17)
⏰ 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 (windows-latest, 20)
- GitHub Check: test (ubuntu-latest, 20)
🔇 Additional comments (15)
src/renderer/src/pages/player/components/VolumeIndicator.tsx (1)
2-2: LGTM!改进了用户体验新增的初次渲染跳过逻辑能够防止音量指示器在页面加载时闪现,提升了用户体验。实现方式符合 React 最佳实践:
- 使用
useRef追踪初次挂载状态- 在 effect 中添加守卫逻辑提前返回
- 后续的音量/静音变化将正常触发指示器显示
代码遵循了项目规范,store hook 在组件顶层调用,effect 具备正确的清理逻辑。
Also applies to: 16-16, 20-24
src/renderer/src/pages/settings/FFmpegSection.tsx (3)
76-76: 引入完成超时引用的实现正确。新增的
completionTimeoutRef用于管理下载完成后的延迟状态重置,类型定义正确(NodeJS.Timeout | null),符合 React ref 的使用规范。
153-165: 超时管理逻辑实现完善。完成超时的处理逻辑设计合理:
- 在设置新超时前清除现有超时(153-155 行),避免重叠计时器
- 2 秒延迟后重置状态并触发预热(156-165 行)
- cleanup 函数中正确清理超时(177-180 行),防止内存泄漏
- 超时执行后将 ref 置为 null(164 行),保持状态一致性
该实现有效避免了组件卸载或状态变更时的潜在问题。
Also applies to: 177-180
480-481: 样式优化符合编码规范。将硬编码的样式值替换为主题常量:
gap使用SPACING.XS(8px)font-size使用FONT_SIZES.SM(14px)这一改动符合项目编码指南中"在 styled-components 中使用 JS 常量作为设计系统常量"的要求,提升了代码的可维护性和主题一致性。
基于编码指南(coding guidelines)
src/renderer/src/services/CodecCompatibilityChecker.ts (4)
379-380: LGTM!编解码器提取逻辑正确。使用可选链和 trim() 方法能够优雅地处理 undefined、null 和空白字符串等边缘情况。
390-393: LGTM!条件检查合理。在检查编解码器支持前先验证其是否存在,使得系统能够区分"编解码器缺失"与"编解码器不支持"两种情况,这是一个有价值的改进。
400-409: LGTM!不兼容原因分类清晰。准确区分了"编解码器缺失"(codec-missing)与"编解码器不支持"(codec-unsupported),为调试和错误处理提供了更精确的信息。
455-480: 验证错误类型映射的一致性。
getErrorTypeFromCompatibility方法根据videoSupported和audioSupported布尔值返回错误类型,但无法区分"编解码器缺失"与"编解码器不支持"。而incompatibilityReasons数组(400-409 行)已经做了这种区分。这种不一致可能导致调用方无法根据
ExtendedErrorType准确判断是缺失编解码器信息还是编解码器真的不支持。请确认:
- 是否应该在
ExtendedErrorType中添加video-codec-missing和audio-codec-missing类型?- 或者当前设计是否有意将"缺失"视为"不支持"的一种形式?
backend (1)
1-1: 请确认子模块更新内容当前 diff 只展示子模块指针从旧提交切换到 64aaf0bd4d1c1d15bd703bbd5ac00c2034bb2e4b,无法直接审查内部改动。请确认该提交已在子模块仓库完成必要评审并在本仓库跑通回归测试,确保行为兼容。
src/renderer/src/pages/settings/FFprobeSection.tsx (1)
426-428: 样式 token 替换符合规范,LGTM已用 SPACING/FONT_SIZES 替代硬编码 px,保持主题一致性。
Also applies to: 553-553
src/main/services/MediaServerService.ts (1)
578-611: 资产哈希实现稳健,采样策略合理,LGTM按路径/大小/mtime 与首尾采样 8MB 组合,低碰撞且性能可控。返回 16 位十六进制用于目录命名也合适。
packages/shared/IpcChannel.ts (1)
129-130: 新增清理通道命名清晰,LGTMMediaServer_CleanupCachesForFile 一致性良好,符合既有命名风格。
src/main/ipc.ts (1)
654-656: IPC 处理器委托实现清晰,LGTM主进程直接委托到 mediaServerService.cleanupCachesForFile,异常由服务内 stat 校验捕获即可。
src/renderer/src/pages/player/PlayerPage.tsx (1)
748-775: 确定性进度 UI 使用主题 token,LGTM宽度、圆角、动画时长与缓动均取自设计系统 token,样式一致性良好。
src/renderer/src/services/SessionService.ts (1)
108-133: 接口定义清晰,类型安全。新增的两个接口
AudioProgressInfo和SessionProgressResponse结构清晰,字段类型定义准确,可空字段使用了| null类型,符合现有代码规范。
| private async removeDirectoryIfExists( | ||
| dirPath: string | ||
| ): Promise<{ removed: boolean; error?: string }> { | ||
| try { | ||
| await rm(dirPath, { recursive: true, force: true }) | ||
| logger.info('已删除转码缓存目录', { dirPath }) | ||
| return { removed: true } | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error) | ||
| logger.warn('删除转码缓存目录失败', { dirPath, error: message }) | ||
| return { removed: false, error: message } | ||
| } | ||
| } |
There was a problem hiding this comment.
removeDirectoryIfExists 对不存在目录也返回 removed:true,语义不准确
rm(..., { force: true }) 在路径不存在时不会抛错,当前实现会误报“已删除”。建议先检测存在性,区分“未找到”与“删除失败”。
应用此修正:
- private async removeDirectoryIfExists(
- dirPath: string
- ): Promise<{ removed: boolean; error?: string }> {
- try {
- await rm(dirPath, { recursive: true, force: true })
- logger.info('已删除转码缓存目录', { dirPath })
- return { removed: true }
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error)
- logger.warn('删除转码缓存目录失败', { dirPath, error: message })
- return { removed: false, error: message }
- }
- }
+ private async removeDirectoryIfExists(
+ dirPath: string
+ ): Promise<{ removed: boolean; error?: string }> {
+ try {
+ await stat(dirPath) // 不存在将抛 ENOENT
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
+ return { removed: false }
+ }
+ const message = err instanceof Error ? err.message : String(err)
+ logger.warn('检查转码缓存目录时出错', { dirPath, error: message })
+ return { removed: false, error: message }
+ }
+ try {
+ await rm(dirPath, { recursive: true, force: true })
+ logger.info('已删除转码缓存目录', { dirPath })
+ return { removed: true }
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error)
+ logger.warn('删除转码缓存目录失败', { dirPath, error: message })
+ return { removed: false, error: message }
+ }
+ }📝 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.
| private async removeDirectoryIfExists( | |
| dirPath: string | |
| ): Promise<{ removed: boolean; error?: string }> { | |
| try { | |
| await rm(dirPath, { recursive: true, force: true }) | |
| logger.info('已删除转码缓存目录', { dirPath }) | |
| return { removed: true } | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error) | |
| logger.warn('删除转码缓存目录失败', { dirPath, error: message }) | |
| return { removed: false, error: message } | |
| } | |
| } | |
| private async removeDirectoryIfExists( | |
| dirPath: string | |
| ): Promise<{ removed: boolean; error?: string }> { | |
| try { | |
| await stat(dirPath) // 不存在将抛 ENOENT | |
| } catch (err) { | |
| if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | |
| return { removed: false } | |
| } | |
| const message = err instanceof Error ? err.message : String(err) | |
| logger.warn('检查转码缓存目录时出错', { dirPath, error: message }) | |
| return { removed: false, error: message } | |
| } | |
| try { | |
| await rm(dirPath, { recursive: true, force: true }) | |
| logger.info('已删除转码缓存目录', { dirPath }) | |
| return { removed: true } | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error) | |
| logger.warn('删除转码缓存目录失败', { dirPath, error: message }) | |
| return { removed: false, error: message } | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main/services/MediaServerService.ts around lines 612-624, the current
removeDirectoryIfExists uses rm(..., { force: true }) which returns removed:
true even when the path doesn't exist; change the logic to first check for
directory existence (e.g., fs.promises.stat or fs.promises.access) and if the
path is missing log and return { removed: false, error: 'not found' } (or
similar sentinel) so callers can distinguish "not found" vs "deleted"; if it
exists proceed to rm(..., { recursive: true, force: true }), log success and
return { removed: true }, and on catch capture and return the error message as
currently done.
| const platformDir = `${version.version}-${platform}-${arch}` | ||
| const targetDir = path.join(this.binariesDir, platformDir) | ||
| const tempDir = path.join(this.binariesDir, '.temp', key) | ||
|
|
There was a problem hiding this comment.
缺少下载件完整性校验(SHA‑256),建议增加校验并在不匹配时中止与清理
当前直接解压运行下载产物,存在供应链风险。建议若 UvVersion.sha256 提供时,下载后先校验哈希,不匹配则清理并报错。
可在“下载文件”与“解压文件”之间插入校验逻辑:
// 下载文件后,添加以下校验(若提供 sha256)
if (version.sha256) {
const computed = await new Promise<string>((resolve, reject) => {
const { createHash } = require('node:crypto')
const { createReadStream } = require('node:fs')
const hash = createHash('sha256')
const rs = createReadStream(downloadPath)
rs.on('data', (c: Buffer) => hash.update(c))
rs.on('error', reject)
rs.on('end', () => resolve(hash.digest('hex')))
})
if (computed.toLowerCase() !== version.sha256.toLowerCase()) {
logger.error('uv 包校验失败,哈希不匹配', {
expected: version.sha256,
actual: computed
})
// 失败时清理临时目录并中止
this.cleanupTempDir(tempDir)
throw new Error('uv 下载包校验失败(SHA-256 不匹配)')
}
}同时可考虑:
- 将最终二进制移动采用 rename(同盘更快),失败再回退 copy。
- 对下载超时/重试策略做指数退避(可后续迭代)。
Also applies to: 488-502, 503-566, 580-581
🤖 Prompt for AI Agents
In src/main/services/UvBootstrapperService.ts around lines 478-481 (also apply
same change to 488-502, 503-566, 580-581): after the download step and before
any extraction/run, compute the SHA-256 of the downloaded file when
version.sha256 is present, compare it case-insensitively to version.sha256, and
if they differ log an error with expected/actual, clean up the temporary
download directory (call existing cleanupTempDir or equivalent) and throw an
Error to abort; ensure the hash computation streams the file (no buffering whole
file), and perform the check in each code path that proceeds to extract or move
the downloaded artifact (replace direct extract/move with the guarded version).
| getPort: (): Promise<number | null> => ipcRenderer.invoke(IpcChannel.MediaServer_GetPort), | ||
| cleanupCachesForFile: (filePath: string) => | ||
| ipcRenderer.invoke(IpcChannel.MediaServer_CleanupCachesForFile, filePath) | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
为 cleanupCachesForFile 补齐强类型定义,提升调用端类型安全
建议在 packages/shared 导出 TranscodeCacheCleanupResult,preload 侧将返回值声明为该类型,renderer 获得完善的类型提示。
示例:
- 在 packages/shared/types/media-server.ts 导出类型
- 此处改为:
cleanupCachesForFile: (filePath: string): Promise<TranscodeCacheCleanupResult> =>
ipcRenderer.invoke(IpcChannel.MediaServer_CleanupCachesForFile, filePath)🤖 Prompt for AI Agents
In src/preload/index.ts around lines 335-338, the cleanupCachesForFile IPC
wrapper lacks a strong return type; export TranscodeCacheCleanupResult from
packages/shared/types/media-server.ts and change the preload declaration to
return Promise<TranscodeCacheCleanupResult> so the renderer gets full type hints
— update imports in preload to reference the shared type and annotate
cleanupCachesForFile accordingly.
| @@ -1,5 +1,6 @@ | |||
| import { loggerService } from '@logger' | |||
| import FFmpegDownloadPrompt from '@renderer/components/FFmpegDownloadPrompt' | |||
| import FileManager from '@renderer/services/FileManager' | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
删除流程增强方向正确,但需两点完善:避免闭包更新与替换 Emoji 图标
- 状态更新避免闭包旧值:
setCachedVideos(cachedVideos.filter(...))可能用到过期的 cachedVideos。改为基于最新 store 值计算:const { cachedVideos: curr } = useVideoListStore.getState() setCachedVideos(curr.filter((v) => v.id !== video.id))
- UI 规范:项目禁止使用 Emoji 作为图标,建议用 lucide-react 的 AlertTriangle 并替换内联硬编码尺寸为设计 token/styled。
- 引入:
import { AlertTriangle } from 'lucide-react'
- 将确认弹窗中的 “
⚠️ …” 替换为:<p style={{ color: 'var(--ant-color-warning)', fontSize: '14px', marginTop: '8px' }}> <AlertTriangle size={14} style={{ marginRight: 6 }} /> {t('home.delete.confirm_warning')} </p>
- 引入:
另外,请确认 FileManager.getFile(record.fileId) 在 renderer 层存在并返回包含 path 的对象。
为验证 FileManager.getFile 的可用性,可在仓库根运行:
Also applies to: 169-181, 193-207
🏁 Script executed:
#!/bin/bash
# 查找 renderer 侧 FileManager 并确认导出 getFile
fd -t f -a "FileManager.(ts|tsx|js)" src/renderer 2>/dev/null
rg -n -C2 "class\\s+FileManager|export\\s+(default\\s+)?class\\s+FileManager|export\\s+(const|function|class)\\s+FileManager" -g "src/renderer/**"
rg -n -C2 "getFile\\s*\\(" -g "src/renderer/**"Length of output: 2995
为验证 setCachedVideos 闭包更新和 Emoji 警告图标使用,请运行:
🏁 Script executed:
#!/bin/bash
rg -n -C3 "setCachedVideos" -g "src/renderer/src/pages/home/HomePage.tsx"
rg -n -C3 "cachedVideos" -g "src/renderer/src/pages/home/HomePage.tsx"
rg -n "⚠️" -g "src/renderer/src/pages/home/HomePage.tsx"Length of output: 4760
优化删除流程:函数式更新 store 与替换 Emoji 图标
-
使用函数式更新避免过期缓存:
setCachedVideos(curr => curr.filter(v => v.id !== video.id))
替代
cachedVideos.filter(...),确保始终基于最新状态。 -
按项目规范用 lucide-react 图标替换 Emoji:
- 引入:
import { AlertTriangle } from 'lucide-react'
- 将页面中
⚠️ {t('home.delete.confirm_warning')}抽成 styled 组件,例如:const Warning = styled.p` display: flex; color: var(--ant-color-warning); font-size: ${FONT_SIZES.md}; margin-top: ${SPACING.sm}; ` <Warning> <AlertTriangle size={FONT_SIZES.md} style={{ marginRight: SPACING.xs }} /> {t('home.delete.confirm_warning')} </Warning>
- 引入:
FileManager.getFile 已验证,无需改动。
🤖 Prompt for AI Agents
In src/renderer/src/pages/home/HomePage.tsx around line 3, update the delete
flow to use a functional state update and replace the emoji warning with a
lucide-react icon: change any setCachedVideos(cachedVideos.filter(...)) to the
functional form setCachedVideos(curr => curr.filter(v => v.id !== video.id)),
add an import for AlertTriangle from 'lucide-react' at the top, create a small
styled Warning component (flex layout, warning color, font-size and margin using
existing FONT_SIZES and SPACING constants) and render <AlertTriangle ... />
inside that Warning with spacing to the left of the translated warning text
instead of the emoji; no changes needed to FileManager.getFile.
| // 计算下一个常用速度 | ||
| const currentIndex = usePlayerStore.getState().currentFavoriteIndex | ||
| const nextIndex = (currentIndex + 1) % favoriteRates.length | ||
| const nextRate = favoriteRates[nextIndex] | ||
|
|
||
| // 先更新 store 状态 | ||
| cycleFavoriteRate() | ||
|
|
||
| // 再通过 orchestrator 应用到视频 | ||
| setSpeed(nextRate) | ||
| } else { |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
考虑优化逻辑顺序以提高可读性。
当前实现先计算 nextRate,再调用 cycleFavoriteRate(),最后应用速率。虽然逻辑正确,但可以通过调整顺序使代码意图更清晰:
- // 计算下一个常用速度
- const currentIndex = usePlayerStore.getState().currentFavoriteIndex
- const nextIndex = (currentIndex + 1) % favoriteRates.length
- const nextRate = favoriteRates[nextIndex]
-
// 先更新 store 状态
cycleFavoriteRate()
-
+
+ // 获取更新后的索引和对应速率
+ const currentIndex = usePlayerStore.getState().currentFavoriteIndex
+ const nextRate = favoriteRates[currentIndex]
+
// 再通过 orchestrator 应用到视频
setSpeed(nextRate)这样逻辑流程更直观:更新索引 → 读取新索引 → 应用对应速率。
另外,建议验证 cycleFavoriteRate() 的实现,确保它只负责更新 currentFavoriteIndex,不包含额外的速率计算或应用逻辑,避免重复操作。
🏁 Script executed:
#!/bin/bash
# 验证 cycleFavoriteRate 的实现,确认它只更新索引不做其他操作
rg -n -A 10 "cycleFavoriteRate.*:" --type tsLength of output: 4307
精简下一个速率计算:删除手动索引计算,从 store 获取已更新的 playbackRate
- // 计算下一个常用速度
- const currentIndex = usePlayerStore.getState().currentFavoriteIndex
- const nextIndex = (currentIndex + 1) % favoriteRates.length
- const nextRate = favoriteRates[nextIndex]
- // 先更新 store 状态
- cycleFavoriteRate()
- // 再通过 orchestrator 应用到视频
- setSpeed(nextRate)
+ // 更新 store 状态并从 store 读取新的 playbackRate
+ cycleFavoriteRate()
+ setSpeed(usePlayerStore.getState().playbackRate)🤖 Prompt for AI Agents
In
src/renderer/src/pages/player/components/ControllerPanel/controls/PlaybackRateControl.tsx
around lines 50 to 60, remove the manual currentIndex/nextIndex/nextRate
calculation and instead call cycleFavoriteRate() first, then read the updated
playbackRate (or currentFavoriteRate) from usePlayerStore.getState() and pass
that value into setSpeed(). Ensure you read the store after cycleFavoriteRate
has run (no stale local index), and remove the now-unused favoriteRates index
math.
| useEffect(() => { | ||
| let cancelled = false | ||
| const pollIntervalMs = 2000 | ||
|
|
||
| const waitForSessionReady = async (sessionId: string) => { | ||
| while (!cancelled) { | ||
| try { | ||
| const progress = await SessionService.getSessionProgress(sessionId) | ||
| if (cancelled) { | ||
| break | ||
| } | ||
|
|
||
| setSessionProgress((prev) => { | ||
| const stage = progress.progress_stage?.trim() || prev?.stage || '处理中...' | ||
| const rawPercent = | ||
| typeof progress.progress_percent === 'number' | ||
| ? progress.progress_percent | ||
| : Number(progress.progress_percent) | ||
| const percent = Number.isFinite(rawPercent) ? rawPercent : (prev?.percent ?? 0) | ||
| return { | ||
| percent, | ||
| stage, | ||
| status: progress.status | ||
| } | ||
| }) | ||
|
|
||
| if (progress.is_ready) { | ||
| setSessionProgress((prev) => ({ | ||
| percent: 100, | ||
| stage: progress.progress_stage?.trim() || prev?.stage || '就绪', | ||
| status: progress.status | ||
| })) | ||
| return progress | ||
| } | ||
| } catch (progressError) { | ||
| if ( | ||
| progressError instanceof SessionError && | ||
| progressError.statusCode && | ||
| progressError.statusCode === 425 | ||
| ) { | ||
| // 会话尚未返回进度,等待下一轮 | ||
| } else { | ||
| throw progressError | ||
| } | ||
| } | ||
|
|
||
| if (cancelled) { | ||
| break | ||
| } | ||
|
|
||
| await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)) | ||
| } | ||
|
|
||
| throw new Error('会话进度轮询已取消') | ||
| } | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
会话进度轮询建议支持取消与超时,避免极端情况下无限等待
当前 while 轮询仅依赖 cancelled 标志,无法中断底层请求,也无总超时。建议:
- 为 getSessionProgress 增加 AbortSignal(若服务支持),退出时 abort;
- 添加总体超时(如 60–120s),超时 fail-fast。
示例(思路):
const controller = new AbortController()
const overall = setTimeout(() => controller.abort(), 120_000)
try {
const progress = await SessionService.getSessionProgress(sessionId, controller.signal)
// ...
} catch (e) {
if ((e as any).name === 'AbortError') throw new Error('会话进度超时/已取消')
throw e
} finally {
clearTimeout(overall)
}🤖 Prompt for AI Agents
In src/renderer/src/pages/player/PlayerPage.tsx around lines 118-173, the
polling loop only checks a local cancelled flag and can hang indefinitely
because requests aren't abortable and there's no overall timeout; update the
logic to create an AbortController per polling iteration (or reuse/replace one
when cancelled), pass controller.signal into SessionService.getSessionProgress
(and update that service to accept a signal), start an overall timeout (e.g.,
60–120s) that calls controller.abort(), clear the timeout in finally, treat
AbortError (or aborted fetch) as a controlled cancellation by throwing a
descriptive Error('会话进度超时/已取消'), and ensure when cancelled you call
controller.abort() before breaking so the in-flight request is actually aborted.
| const [showSuccessState, setShowSuccessState] = useState(false) | ||
| const isCancellingRef = useRef(false) | ||
| const isCompletionHandledRef = useRef(false) | ||
| const successTimeoutRef = useRef<number | null>(null) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
统一定时器类型,避免 DOM/Node 定义混用造成的类型不一致与潜在误用
当前 successTimeoutRef 使用 number,progressInterval 使用 NodeJS.Timeout。建议统一使用 ReturnType 并显式使用 window.clearTimeout/window.clearInterval,类型更稳健,亦便于日后在不同运行时切换。
应用此改动:
- const successTimeoutRef = useRef<number | null>(null)
+ const successTimeoutRef = useRef<ReturnType<typeof window.setTimeout> | null>(null)- let progressInterval: NodeJS.Timeout | null = null
+ let progressInterval: ReturnType<typeof window.setInterval> | null = null- clearInterval(progressInterval)
+ window.clearInterval(progressInterval)- if (successTimeoutRef.current) {
- clearTimeout(successTimeoutRef.current)
- }
+ if (successTimeoutRef.current) {
+ window.clearTimeout(successTimeoutRef.current)
+ }- if (successTimeoutRef.current) {
- clearTimeout(successTimeoutRef.current)
+ if (successTimeoutRef.current) {
+ window.clearTimeout(successTimeoutRef.current)
successTimeoutRef.current = null
}Also applies to: 121-131, 140-143
🤖 Prompt for AI Agents
In src/renderer/src/pages/settings/FFprobeSection.tsx around lines 67, 121-131
and 140-143, the code mixes number and NodeJS.Timeout for timer refs; update
successTimeoutRef and progressIntervalRef to use ReturnType<typeof setTimeout>
(and ReturnType<typeof setInterval> where applicable) for consistent,
environment-agnostic types, change all setTimeout/setInterval assignments to use
those refs, and replace clearTimeout/clearInterval calls with
window.clearTimeout/window.clearInterval to ensure correct runtime behavior and
type safety.
| /** | ||
| * 获取会话创建进度 | ||
| * | ||
| * @param sessionId 会话ID | ||
| * @returns 会话进度响应 | ||
| */ | ||
| public static async getSessionProgress(sessionId: string): Promise<SessionProgressResponse> { | ||
| logger.debug('获取会话进度', { sessionId }) | ||
|
|
||
| try { | ||
| const response = await this.makeRequest<SessionProgressResponse>(`/${sessionId}/progress`) | ||
|
|
||
| logger.debug('会话进度获取成功', { | ||
| sessionId, | ||
| status: response.status, | ||
| progress: response.progress_percent, | ||
| stage: response.progress_stage, | ||
| isReady: response.is_ready | ||
| }) | ||
|
|
||
| return response | ||
| } catch (error) { | ||
| // 如果是 HTTP 425 (Too Early),这是正常的进行中状态,不记录为错误 | ||
| if (error instanceof SessionError && error.statusCode === 425) { | ||
| logger.debug('会话尚未就绪 (HTTP 425)', { sessionId }) | ||
| throw error | ||
| } | ||
|
|
||
| logger.error('获取会话进度失败', { | ||
| sessionId, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }) | ||
| throw error | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
验证是否应使用重试机制以提升稳定性。
方法 getSessionProgress 当前使用 makeRequest 而非 makeRequestWithRetry。对比文件中其他方法:
- 只读操作(
getSessionInfo、updateSessionTime)使用makeRequest - 关键操作(
createSession、seekSession)使用makeRequestWithRetry
由于此方法会被 UI 频繁轮询(根据 AI 摘要),单次请求失败不应中断整体进度跟踪流程。建议考虑使用 makeRequestWithRetry 以提升对临时网络故障或服务端错误(5xx)的容错能力。
注意:HTTP 425 的特殊处理逻辑无需调整,因为重试逻辑已在 306-309 行跳过 4xx 错误。
如需重构,可应用以下改动:
try {
- const response = await this.makeRequest<SessionProgressResponse>(`/${sessionId}/progress`)
+ const response = await this.makeRequestWithRetry<SessionProgressResponse>(`/${sessionId}/progress`)📝 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.
| /** | |
| * 获取会话创建进度 | |
| * | |
| * @param sessionId 会话ID | |
| * @returns 会话进度响应 | |
| */ | |
| public static async getSessionProgress(sessionId: string): Promise<SessionProgressResponse> { | |
| logger.debug('获取会话进度', { sessionId }) | |
| try { | |
| const response = await this.makeRequest<SessionProgressResponse>(`/${sessionId}/progress`) | |
| logger.debug('会话进度获取成功', { | |
| sessionId, | |
| status: response.status, | |
| progress: response.progress_percent, | |
| stage: response.progress_stage, | |
| isReady: response.is_ready | |
| }) | |
| return response | |
| } catch (error) { | |
| // 如果是 HTTP 425 (Too Early),这是正常的进行中状态,不记录为错误 | |
| if (error instanceof SessionError && error.statusCode === 425) { | |
| logger.debug('会话尚未就绪 (HTTP 425)', { sessionId }) | |
| throw error | |
| } | |
| logger.error('获取会话进度失败', { | |
| sessionId, | |
| error: error instanceof Error ? error.message : String(error) | |
| }) | |
| throw error | |
| } | |
| } | |
| /** | |
| * 获取会话创建进度 | |
| * | |
| * @param sessionId 会话ID | |
| * @returns 会话进度响应 | |
| */ | |
| public static async getSessionProgress(sessionId: string): Promise<SessionProgressResponse> { | |
| logger.debug('获取会话进度', { sessionId }) | |
| try { | |
| const response = await this.makeRequestWithRetry<SessionProgressResponse>(`/${sessionId}/progress`) | |
| logger.debug('会话进度获取成功', { | |
| sessionId, | |
| status: response.status, | |
| progress: response.progress_percent, | |
| stage: response.progress_stage, | |
| isReady: response.is_ready | |
| }) | |
| return response | |
| } catch (error) { | |
| // 如果是 HTTP 425 (Too Early),这是正常的进行中状态,不记录为错误 | |
| if (error instanceof SessionError && error.statusCode === 425) { | |
| logger.debug('会话尚未就绪 (HTTP 425)', { sessionId }) | |
| throw error | |
| } | |
| logger.error('获取会话进度失败', { | |
| sessionId, | |
| error: error instanceof Error ? error.message : String(error) | |
| }) | |
| throw error | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/renderer/src/services/SessionService.ts around lines 548 to 582, replace
the call to makeRequest with makeRequestWithRetry to improve robustness for the
frequently polled getSessionProgress method; keep the existing HTTP 425
special-case handling (do not change the throw for 425), ensure retry options
mirror other retry-using methods (so 5xx/transport errors are retried while 4xx
are skipped per the existing retry policy), and add any necessary import for
makeRequestWithRetry while preserving the existing debug/error logging and
re-throw behavior on non-retryable errors.
Summary by CodeRabbit
新功能
改进
缺陷修复
清理