-
Notifications
You must be signed in to change notification settings - Fork 1
feat: integrate Zhipu AI translation service with comprehensive subtitle translation #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Replace release event trigger with workflow_run mechanism to ensure reliable automatic triggering after semantic-release creates GitHub releases. - Change trigger from release event to workflow_run listening to Release workflow - Add workflow_run event handling with GitHub API fetching - Maintain backward compatibility and manual dispatch functionality - Improve trigger type identification in summary display This resolves the issue where semantic-release created releases would not automatically trigger the GitCode sync workflow.
* conductor-checkpoint-start * fix(styled-components): resolve unknown props warnings in settings page - Convert HStack layout props (gap, alignItems, justifyContent) to transient props with $ prefix - Convert ColorCircle isActive prop to transient prop $isActive - Update all usage across settings components to use transient props - Remove duplicate gap property in Box component - Build passes without styled-components warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
… Zhipu AI integration - Add SubtitleTranslationService with batch processing and intelligent context handling - Extend ConfigManager to support Zhipu API Key configuration with secure storage - Refactor ASR settings page to "Subtitle Generation" with translation configuration group - Add comprehensive TypeScript types for translation service interfaces - Implement database layer support for batch subtitle translation updates - Add IPC channel for API Key validation between frontend and backend - Update dependencies with AI SDK packages for translation functionality - Add OpenSpec specifications for config management, translation service, and UI integration - Enable automatic background translation after subtitle generation - Add comprehensive internationalization support for translation UI components Key Features: - Intelligent translation with video filename context and subtitle batching - Automatic background translation without blocking main workflow - Robust error handling with exponential backoff retry mechanism - Comprehensive UI for API Key configuration and validation - Full internationalization support in Chinese and English Technical Improvements: - Follow project conventions with styled-components and lucide-react icons - Implement proper Zustand state management patterns - Add comprehensive logging using loggerService - Maintain type safety throughout the translation pipeline
Track subtitle ID processing status to ensure each translation item is counted exactly once, replacing the previous record-based counting logic. Key improvements: - Use Set<string> to track processed and failed subtitle IDs - Count failures based on individual translation items rather than records - Prevent double counting by tracking each subtitleId's processing state - Provide detailed error messages with specific subtitle IDs - Ensure successCount + failureCount equals input translations.length This resolves the issue where successCount + failureCount could exceed the total number of input translations, making it difficult for callers to determine actual processing results.
- refine `TranslationOptions` to enforce `targetLanguage` as 'zh-CN' - improve `TranslationResult` type using discriminated union for success/failure - make `results` property in `TranslationBatchResult` readonly
- move startBackgroundTranslation after ASR process completion - ensure translation starts only after successful ASR and DB save
- add sourceLanguage option to translation results - improve API key validation logic
Walkthrough本PR新增基于 Zhipu AI 的字幕翻译子系统(类型、服务、DAO、IPC、预加载与前端设置),在 ASR 保存后异步批量翻译;同时调整发布工作流触发逻辑、若干 UI 布局私有 prop 前缀、i18n 文案与依赖项,并新增若干规范文档与配置文件。 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Renderer
participant Preload as Preload API
participant Main as Main IPC
participant Trans as SubtitleTranslationService
participant DB as Database
participant Zhipu as Zhipu AI API
User->>Renderer: 在设置中输入 Zhipu API Key
Renderer->>Preload: api.translation.validateApiKey(apiKey)
Preload->>Main: invoke Translation_ValidateApiKey(apiKey)
Main->>Trans: validateApiKey(apiKey)
Trans->>Zhipu: 发送轻量翻译请求
alt 有效
Zhipu-->>Trans: 成功响应
Trans-->>Main: 返回 true
else 无效
Zhipu-->>Trans: 错误响应
Trans-->>Main: 返回 false
end
Main-->>Preload: 返回结果
Preload-->>Renderer: Promise<boolean>
rect rgb(230,245,230)
Renderer->>Main: 发起 ASR 生成请求
Main->>DB: 保存字幕数据
Main->>Trans: startBackgroundTranslation(videoId, videoPath) (异步)
Trans->>DB: getSubtitlesByVideoId(videoId)
DB-->>Trans: 字幕列表
loop 批处理 (10-20 项/批, 并发 <= maxConcurrency)
Trans->>Zhipu: 发送批量翻译提示
Zhipu-->>Trans: 返回翻译(JSON 或文本)
Trans->>Trans: 解析/抽取翻译结果
Trans->>DB: updateSubtitleTranslations(batchResults)
DB-->>Trans: 更新结果
end
Trans-->>Main: 报告完成与统计
Main-->>Renderer: 可选进度/完成事件
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 分钟 Possibly related PRs
诗歌
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (2)
⏰ 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)
🔇 Additional comments (3)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 27
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/pages/settings/SettingsPage.tsx (1)
97-102: 语法错误:多余的]导致样式模板字符串中断,页面将无法编译请移除多余的
]。const ContentContainer = styled.div` display: flex; flex: 1; flex-direction: row; -]` +`.github/workflows/sync-release-to-gitcode.yml (1)
395-447: 我需要进一步查证 GitCode 的官方 API 文档,以确认当前实现是否真的有问题。让我搜索更具体的信息。更新发布需改用 release_id 而非 tag 作为路径参数
Gitee API 更新发布需要使用
PATCH https://gitee.com/api/v5/repos/{owner}/{repo}/releases/{id}端点,但当前代码使用了 tag 作为路径参数,这会导致 GitCode/Gitee API 调用失败。需要在"存在性检查"步骤中提取 release_id,并在更新时使用该 ID:
# 在"Check if release exists on GitCode"步骤中,从响应提取 release_id - if [ "$http_code" = "200" ]; then - echo "exists=true" >> $GITHUB_OUTPUT - echo "Release already exists on GitCode" + if [ "$http_code" = "200" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + rel_id=$(echo "$response_body" | jq -r '.id // .release.id // empty') + echo "release_id=$rel_id" >> $GITHUB_OUTPUT + echo "Release already exists on GitCode (id=$rel_id)"- "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/$TAG_NAME?access_token=$ACCESS_TOKEN") + "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/${{ steps.check-release.outputs.release_id }}?access_token=$ACCESS_TOKEN")
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (25)
.github/workflows/sync-release-to-gitcode.yml(4 hunks).gitignore(1 hunks)openspec/specs/config-management/spec.md(1 hunks)openspec/specs/translation-service/spec.md(1 hunks)openspec/specs/ui-integration/spec.md(1 hunks)package.json(2 hunks)packages/shared/IpcChannel.ts(1 hunks)packages/shared/types/index.ts(1 hunks)packages/shared/types/translation.ts(1 hunks)src/main/db/dao/SubtitleLibraryDAO.ts(1 hunks)src/main/ipc.ts(2 hunks)src/main/services/ASRSubtitleService.ts(3 hunks)src/main/services/ConfigManager.ts(3 hunks)src/main/services/SubtitleTranslationService.ts(1 hunks)src/preload/index.ts(1 hunks)src/renderer/src/components/Layout/index.ts(4 hunks)src/renderer/src/i18n/locales/en-us.json(3 hunks)src/renderer/src/i18n/locales/zh-cn.json(3 hunks)src/renderer/src/pages/player/components/ASRSubtitlePrompt.tsx(1 hunks)src/renderer/src/pages/settings/ASRSettings.tsx(8 hunks)src/renderer/src/pages/settings/AboutSettings.tsx(1 hunks)src/renderer/src/pages/settings/AppearanceSettings.tsx(3 hunks)src/renderer/src/pages/settings/SettingsPage.tsx(2 hunks)src/renderer/src/pages/settings/ShortcutSettings.tsx(2 hunks)stagewise.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
openspec/specs/*/spec.md
📄 CodeRabbit inference engine (openspec/AGENTS.md)
Capabilities must be single-focused directories under openspec/specs// with their requirements in spec.md
Files:
openspec/specs/ui-integration/spec.mdopenspec/specs/translation-service/spec.mdopenspec/specs/config-management/spec.md
🧬 Code graph analysis (7)
src/renderer/src/pages/settings/ShortcutSettings.tsx (1)
src/renderer/src/components/Layout/index.ts (1)
HStack(130-132)
src/main/ipc.ts (1)
src/main/services/SubtitleTranslationService.ts (1)
subtitleTranslationService(440-440)
src/main/services/SubtitleTranslationService.ts (3)
src/renderer/src/services/Logger.ts (2)
loggerService(817-817)error(422-424)packages/shared/types/translation.ts (4)
TranslationProgress(55-66)TranslationOptions(5-16)TranslationResult(18-42)TranslationBatchResult(44-53)src/main/services/ConfigManager.ts (1)
configManager(259-259)
src/renderer/src/pages/settings/ASRSettings.tsx (3)
src/main/services/ConfigManager.ts (2)
setZhipuApiKey(254-256)setDeepgramApiKey(229-231)src/renderer/src/pages/settings/index.tsx (2)
SettingHelpTextRow(79-84)SettingHelpLink(92-95)src/renderer/src/components/Layout/index.ts (1)
HStack(130-132)
src/renderer/src/pages/settings/AppearanceSettings.tsx (1)
src/renderer/src/components/Layout/index.ts (1)
HStack(130-132)
src/renderer/src/pages/settings/AboutSettings.tsx (1)
src/renderer/src/components/Layout/index.ts (1)
HStack(130-132)
src/main/services/ASRSubtitleService.ts (2)
src/main/services/ConfigManager.ts (1)
configManager(259-259)src/main/services/SubtitleTranslationService.ts (1)
subtitleTranslationService(440-440)
🪛 GitHub Actions: Test
src/main/services/ASRSubtitleService.ts
[error] 586-586: Type error TS2345: Argument of type '{ targetLanguage: string; batchSize: number; maxConcurrency: number; videoFilename: string; }' is not assignable to parameter of type 'TranslationOptions'.
🪛 GitHub Check: test (ubuntu-latest, 20)
src/main/services/ASRSubtitleService.ts
[failure] 586-586:
Argument of type '{ targetLanguage: string; batchSize: number; maxConcurrency: number; videoFilename: string; }' is not assignable to parameter of type 'TranslationOptions'.
🪛 LanguageTool
openspec/specs/ui-integration/spec.md
[uncategorized] ~139-~139: “关于”组成的介词短语必需位于句首,或请改用"对于"代替。
Context: ... 状态管理规范 - 使用 React Hooks 进行状态管理 - 遵循项目中关于 Zustand 使用的规定 - 避免在 useEffect 中调用 store...
(wb2)
🪛 markdownlint-cli2 (0.18.1)
openspec/specs/ui-integration/spec.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
openspec/specs/translation-service/spec.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
openspec/specs/config-management/spec.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (25)
stagewise.json (1)
1-6: 原审查意见不正确,建议忽略。经过验证,
stagewise.json是 Stagewise 调试工具栏的配置文件,仅在开发模式下初始化使用(见 CHANGELOG.md),而非应用服务配置文件。根据代码库中的实际实现:
- SubtitleTranslationService 位于
src/main/services/SubtitleTranslationService.ts,已包含 Zhipu AI 翻译服务集成(使用 glm-4.5-flash 模型)- 翻译服务配置 应通过环境变量(
.env)或 ConfigManager 服务管理,而不是stagewise.json.env.example已定义 API_KEY 配置示例packages/shared/types/translation.ts中的TranslationServiceConfig类型已定义翻译服务配置结构
stagewise.json的现有配置(port、appPort、autoPlugins、plugins)与其工具栏用途相符,不应添加翻译相关配置。Likely an incorrect or invalid review comment.
.gitignore (1)
58-58: LGTM!添加忽略规则以排除生成的 OpenSpec 输出文件是合理的做法。
package.json (2)
108-108: LGTM!将
@types/hls.js移至 devDependencies 是正确的,类型定义仅在构建时需要。
62-62: 依赖版本和安全性验证完成 - 无问题。验证结果:
@ai-sdk/openai: npm 最新版本 2.0.53,package.json 指定 ^2.0.53 ✓ai: npm 最新版本 5.0.76,package.json 指定 ^5.0.76 ✓- 两个包均无已知安全漏洞 ✓
新增的依赖已经是最新版本,不存在安全问题,无需任何调整。
packages/shared/IpcChannel.ts (1)
149-150: LGTM!新增的翻译 API 密钥验证通道遵循现有命名约定,并与其他翻译相关通道正确分组。
src/renderer/src/pages/settings/AboutSettings.tsx (1)
196-196: LGTM!将 prop 重命名为
$alignItems符合 styled-components 的 transient props 约定,防止非标准属性传递到 DOM 元素。src/renderer/src/pages/settings/AppearanceSettings.tsx (2)
30-30: LGTM!ColorCircle 组件的 prop 重命名为
$isActive符合 styled-components transient props 约定,CSS 样式逻辑也正确更新。Also applies to: 40-40
172-183: LGTM!HStack 组件的 props 已正确更新为使用
$gap和$alignItems,与 Layout 组件的重构保持一致。src/renderer/src/components/Layout/index.ts (2)
25-25: LGTM!Props 重命名为使用
$前缀(transient props)是 styled-components 的最佳实践,防止非标准属性传递到 DOM 元素,避免 React 警告。Also applies to: 49-51
93-93: LGTM!CSS 样式逻辑已正确更新以使用新的
$前缀 props,与类型定义保持一致。Also applies to: 120-122
src/renderer/src/pages/settings/ShortcutSettings.tsx (2)
333-333: LGTM!HStack 的 prop 已正确更新为
$alignItems,与 Layout 组件的重构保持一致。
420-420: LGTM!HStack 的 prop 已正确更新为
$justifyContent,与 Layout 组件的重构保持一致。src/renderer/src/pages/player/components/ASRSubtitlePrompt.tsx (1)
24-32: i18n 键已验证完整,无遗留问题。验证结果确认:
- 新的 i18n 路径
settings.subtitleGeneration.speechRecognition.languages在 en-us.json 和 zh-cn.json 中均已正确定义,包括所有 9 个语言选项的完整翻译- 旧键
settings.asr.languages.*在整个代码库中无任何遗留引用- ASRSubtitlePrompt.tsx(第 24-32 行)和 ASRSettings.tsx 中的实现均一致使用新的 i18n 键
- 重构完整,无后遗问题
packages/shared/types/index.ts (1)
9-9: 公共类型出口安全确认验证通过:
./translation.ts仅包含类型级导出(7 个接口和类型),不含任何运行时符号(const、function、class、default)。export *不会引入运行时代码或循环依赖,可安全合并。src/main/ipc.ts (1)
746-751: 翻译 API Key 校验 IPC 处理器实现完整,所有关键项已验证
- ✅ IpcChannel 枚举中已声明
Translation_ValidateApiKey = 'translation:validate-api-key'- ✅ preload 已正确暴露
translation.validateApiKey并调用该通道- ✅
SubtitleTranslationService.validateApiKey方法存在且类型正确- ✅ 日志未暴露敏感的 apiKey 值
src/renderer/src/pages/settings/SettingsPage.tsx (1)
58-60: 验证通过,代码改动无误经过逐一验证,所有提出的关切均已确认:
settings.subtitleGeneration.titlei18n key 已在 en-us.json 和 zh-cn.json 中存在并正确配置lucide-react的Captions图标(^0.536.0)已包含,并在 SettingsPage.tsx 第 2 行正确导入- 该图标还在其他文件中被正确使用(ImportSubtitleButton.tsx、CaptionsButton.tsx)
src/main/services/ConfigManager.ts (2)
69-72: 默认值设置合理。为 ZhipuApiKey 设定空字符串默认值与其它密钥类项保持一致,OK。
46-49: 枚举扩展合理且命名清晰。ZhipuApiKey 键符合现有命名与分组方式,OK。
.github/workflows/sync-release-to-gitcode.yml (1)
4-9:on.workflow_run事件官方支持branches过滤条件,代码无需修改。GitHub 官方文档确认
on.workflow_run支持branches和branches-ignore进行分支过滤,因此文件.github/workflows/sync-release-to-gitcode.yml中的分支过滤配置是正确的,无需调整。Likely an incorrect or invalid review comment.
src/main/services/ASRSubtitleService.ts (1)
531-567: 后台翻译任务入口的可用性校验与日志粒度合理,LGTM已正确按 API Key 与服务可用性做短路;视频文件名提取用于上下文也到位。建议:把
videoFilename放入结构化字段以便检索。请确认移除/变更 Zhipu API Key 后是否有地方调用了
subtitleTranslationService.reinitializeClient()以刷新客户端。src/renderer/src/i18n/locales/en-us.json (1)
35-40: 新增翻译相关错误文案合理,LGTM覆盖了缺 Key、无效 Key、配额、网络等主要场景。请确认 zh-cn.json 同步更新。
packages/shared/types/translation.ts (1)
5-16: 将 targetLanguage 限定为字面量提升了类型安全,但会要求调用方显式注解这正是引发 ASR 调用处 TS2345 的根因。建议保留当前设计(更安全),并在调用处按本次建议进行类型注解;若预计近期要支持多语种,可预留联合类型如
'zh-CN' | 'en-US'。src/renderer/src/i18n/locales/zh-cn.json (3)
22-32: 新增 API Key 通用配置对象结构合理。中文翻译准确,用词得当。将 "API Key" 等技术术语保持为英文,与文件中其他技术术语(如 FFmpeg、Stream)的处理方式保持一致。
629-673: 新增字幕生成配置块已在代码中正确使用,结构和翻译质量良好。验证结果:
subtitleGeneration配置在 ASRSettings.tsx、SettingsPage.tsx、ASRSubtitlePrompt.tsx 等文件中被正确引用,所有 i18n 键调用有效- 中文翻译准确,"智谱 AI" 品牌保持一致
- apiKey 结构在
common、speechRecognition、translation中的重复是本地化 JSON 文件的正常现象,各处的描述和占位符都因上下文而异,不存在实际的设计问题原始审查意见中关于通过对象合并复用的建议不适用于静态 JSON 本地化文件。
67-71: 确认:翻译键已加入 locales,但仓库中未找到直接引用 — 需要开发确认zh-cn.json 中新增了 5 条翻译键(translationApiKeyInvalid, translationApiKeyMissing, translationFailed, translationNetworkError, translationQuotaExceeded),中文表述合适。仓库搜索未找到对这些键的直接引用,可能尚未在实现中调用或通过动态/间接方式引用。请开发确认并采取其一:1) 若尚未实现,添加对应错误抛出/映射;2) 若以动态方式引用,提供引用位置或改为静态键以便本地化使用。
| elif [ "${{ github.event_name }}" = "workflow_run" ]; then | ||
| # Automatic trigger from workflow_run - get release info from API | ||
| echo "🚀 Workflow run trigger detected, fetching latest release from GitHub API" | ||
| # Get the latest release from GitHub API | ||
| latest_release=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | ||
| "https://api.github.com/repos/${{ github.repository }}/releases/latest") | ||
| if [ "$(echo "$latest_release" | jq -r '.message // empty')" = "Not Found" ]; then | ||
| echo "❌ No releases found in repository" | ||
| exit 1 | ||
| fi | ||
| TAG_NAME=$(echo "$latest_release" | jq -r '.tag_name') | ||
| RELEASE_NAME=$(echo "$latest_release" | jq -r '.name') | ||
| RELEASE_BODY=$(echo "$latest_release" | jq -r '.body // ""') | ||
| PRERELEASE=$(echo "$latest_release" | jq -r '.prerelease') | ||
| DRAFT=$(echo "$latest_release" | jq -r '.draft') | ||
| echo "Found latest release: $TAG_NAME" | ||
| echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT | ||
| echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT | ||
| echo "release_body<<EOF" >> $GITHUB_OUTPUT | ||
| echo "$RELEASE_BODY" >> $GITHUB_OUTPUT | ||
| echo "EOF" >> $GITHUB_OUTPUT | ||
| echo "prerelease=$PRERELEASE" >> $GITHUB_OUTPUT | ||
| echo "draft=$DRAFT" >> $GITHUB_OUTPUT | ||
| echo "test_mode=false" >> $GITHUB_OUTPUT | ||
| else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
workflow_run 分支:使用“最新发布”可能与触发的运行不匹配,存在同步错误风险。
- 目前总是读取 /releases/latest,若并发多个发布或回滚时,可能取错 tag。
- 建议基于 workflow_run 上下文定位“本次运行对应的 tag”,优先用 head_sha 反查 tag(git tag --points-at),否则再回退到 API。
示例最小修改(仅示意关键片段):
- elif [ "${{ github.event_name }}" = "workflow_run" ]; then
- latest_release=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
- "https://api.github.com/repos/${{ github.repository }}/releases/latest")
- TAG_NAME=$(echo "$latest_release" | jq -r '.tag_name')
+ elif [ "${{ github.event_name }}" = "workflow_run" ]; then
+ echo "🔎 Deriving tag from workflow_run head_sha..."
+ HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
+ # 尝试用 head_sha 反查 tag(需已 checkout)
+ TAG_NAME=$(git tag --points-at "$HEAD_SHA" | head -n1)
+ if [ -z "$TAG_NAME" ]; then
+ echo "⚠️ No tag points at $HEAD_SHA, falling back to latest release"
+ latest_release=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+ "https://api.github.com/repos/${{ github.repository }}/releases/latest")
+ TAG_NAME=$(echo "$latest_release" | jq -r '.tag_name')
+ fi
+ # 其余字段通过 /releases/tags/{tag} 获取,确保与 TAG_NAME 一致
+ tag_release=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+ "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG_NAME")
+ RELEASE_NAME=$(echo "$tag_release" | jq -r '.name')
+ RELEASE_BODY=$(echo "$tag_release" | jq -r '.body // ""')
+ PRERELEASE=$(echo "$tag_release" | jq -r '.prerelease')
+ DRAFT=$(echo "$tag_release" | jq -r '.draft')Committable suggestion skipped: line range outside the PR's diff.
| ## Purpose | ||
|
|
||
| 配置管理服务 SHALL 为应用程序提供统一的配置项管理功能,包括 API Key、用户偏好设置等的存储、读取和更新。系统 SHALL 支持多种配置类型的安全存储和实时通知机制。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
补充一级标题以通过 MD041。
建议将首行改为顶级标题,示例:
-## Purpose
+# 配置管理(Config Management)
+
+## Purpose📝 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.
| ## Purpose | |
| 配置管理服务 SHALL 为应用程序提供统一的配置项管理功能,包括 API Key、用户偏好设置等的存储、读取和更新。系统 SHALL 支持多种配置类型的安全存储和实时通知机制。 | |
| # 配置管理(Config Management) | |
| ## Purpose | |
| 配置管理服务 SHALL 为应用程序提供统一的配置项管理功能,包括 API Key、用户偏好设置等的存储、读取和更新。系统 SHALL 支持多种配置类型的安全存储和实时通知机制。 |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents
openspec/specs/config-management/spec.md lines 1-3: the file starts with a
level-2 header ("## Purpose"), which triggers MD041 requiring a top-level
heading as the first line; update the first line to be a level-1 header (change
"## Purpose" to "# Purpose") while leaving the following content unchanged so
the document begins with a proper H1.
| Zhipu API Key SHALL 使用 electron-conf 进行安全存储,支持配置变更时的自动通知,并提供配置验证和错误处理功能。 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“使用 electron-conf 进行安全存储”表述不准确,存在安全姿态风险。
- electron-conf 为本地明文配置,不等同安全存储;API Key 应写入系统凭据管理(如 keytar/Keychain/Credential Manager),conf 仅保存引用标识或必要元数据,并确保日志全程脱敏。
- 建议在规范中明确:
- 必须使用 OS 密钥环保存密钥;失败时回退方案需高亮风险与用户提示。
- 禁止在日志/崩溃上报中记录明文或可逆加密的 Key。
- 定义轮换与删除流程(包括迁移与回滚)。
🤖 Prompt for AI Agents
In openspec/specs/config-management/spec.md around lines 30–31, the current text
that mandates "使用 electron-conf 进行安全存储" is inaccurate and creates a security
risk; update the spec to require storing API keys in the OS credential store
(e.g., Keychain/Windows Credential Manager via keytar or equivalent), persist
only a reference or metadata in electron-conf (never the plaintext or reversible
encryption), and explicitly forbid logging or crash-reporting of plaintext/
reversible-key material (ensure all logs are masked). Also add language that: 1)
mandates a clear fallback behavior when OS keyring is unavailable (highlighting
risk and requiring explicit user consent and warning), 2) requires documented
key rotation and deletion procedures including migration and rollback steps, and
3) requires validation and error handling for key store operations with
user-facing error messages.
| @@ -0,0 +1,168 @@ | |||
| ## Purpose | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复 markdownlint MD041:首行应为一级标题
将首行 “## Purpose” 提升为 H1。
-## Purpose
+# Purpose🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents
In openspec/specs/translation-service/spec.md around line 1, the file starts
with a level-2 heading "## Purpose" which violates markdownlint MD041 requiring
the first line be a top-level heading; change that first line to a level-1
heading by replacing "## Purpose" with "# Purpose" so the document begins with
an H1.
|
|
||
| **Description**: 在数据访问层添加批量更新字幕翻译的方法 | ||
|
|
||
| **Acceptance Criteria**: | ||
|
|
||
| - 添加 `updateSubtitleTranslations(subtitleLibraryId, translations)` 方法 | ||
| - 接收字幕 ID 和翻译文本的映射 | ||
| - 支持事务处理,确保数据一致性 | ||
| - 提供详细的错误信息和统计结果 | ||
|
|
||
| **File**: `src/main/db/dao.ts` | ||
|
|
||
| #### Scenario: DB-010-01: 批量更新实现 | ||
|
|
||
| ```typescript | ||
| const result = await db.subtitleLibrary.updateSubtitleTranslations(subtitleLibraryId, translations) | ||
| // 应该返回更新统计信息 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
规范与实现存在“批量更新”方法命名偏差
规范使用 updateSubtitleTranslations(...)(复数),实现中(SubtitleTranslationService.processBatch)调用的是逐条 updateSubtitleTranslation(...)。请二选一对齐:要么实现批量 DAO 方法,要么调整规范描述。
| "subtitleGeneration": { | ||
| "description": "Configure AI subtitle generation features, supporting speech recognition and automatic translation", | ||
| "speechRecognition": { | ||
| "apiKey": { | ||
| "description": "Get an API Key from Deepgram to use AI subtitle generation features", | ||
| "getKey": "Get API Key", | ||
| "label": "API Key", | ||
| "placeholder": "Enter your Deepgram API Key" | ||
| }, | ||
| "defaultLanguage": { | ||
| "description": "Default language for automatic subtitle generation", | ||
| "label": "Default Language" | ||
| }, | ||
| "description": "Automatically generate accurate subtitles using Deepgram speech recognition technology, supporting multiple languages and word-level timestamps", | ||
| "languages": { | ||
| "auto": "Auto Detect", | ||
| "de": "German", | ||
| "en": "English", | ||
| "es": "Spanish", | ||
| "fr": "French", | ||
| "ja": "Japanese", | ||
| "ko": "Korean", | ||
| "ru": "Russian", | ||
| "zh": "Chinese" | ||
| }, | ||
| "model": { | ||
| "description": "Select Deepgram transcription model", | ||
| "label": "Transcription Model", | ||
| "nova2": "Nova 2 (Recommended)", | ||
| "nova3": "Nova 3 (Latest)" | ||
| }, | ||
| "title": "Speech Recognition" | ||
| }, | ||
| "title": "Subtitle Generation", | ||
| "translation": { | ||
| "apiKey": { | ||
| "description": "Get an API Key from Zhipu AI to use subtitle translation features", | ||
| "getKey": "Get API Key", | ||
| "label": "Zhipu API Key", | ||
| "placeholder": "Enter your Zhipu API Key" | ||
| }, | ||
| "description": "Automatically translate generated subtitles to Chinese using Zhipu AI, providing localized translation results", | ||
| "title": "Subtitle Translation" | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
“Subtitle Generation” 分组结构清晰,但存在文案复用机会
speechRecognition.apiKey.* 与 translation.apiKey.* 的 “Get API Key/Label/Placeholder” 与 common.apiKey 重复,可考虑统一引用以避免不一致;不影响功能。
🤖 Prompt for AI Agents
In src/renderer/src/i18n/locales/en-us.json around lines 434 to 478, the
speechRecognition.apiKey.* and translation.apiKey.* blocks duplicate the same
Get API Key/Label/Placeholder strings already defined under common.apiKey;
remove the duplicated literal entries and update these blocks to reuse the
existing common.apiKey keys (or point the UI to common.apiKey.getKey,
common.apiKey.label, common.apiKey.placeholder) so the texts are centralized and
can't drift out of sync.
| const handleValidateZhipuApiKey = async () => { | ||
| if (!zhipuApiKey.trim()) { | ||
| message.warning(t('common.apiKey.invalid') || 'API Key 无效') | ||
| return | ||
| } | ||
|
|
||
| setValidatingZhipuApiKey(true) | ||
| try { | ||
| const isValid = await window.api.translation.validateApiKey(zhipuApiKey) | ||
| setZhipuApiKeyValid(isValid) | ||
| if (isValid) { | ||
| message.success(t('common.apiKey.valid') || 'API Key 有效') | ||
| // Save the validated key | ||
| await window.api.config.set('zhipuApiKey', zhipuApiKey) | ||
| } else { | ||
| message.error(t('common.apiKey.invalid') || 'API Key 无效') | ||
| } | ||
| } catch (error) { | ||
| setZhipuApiKeyValid(false) | ||
| message.error(t('common.apiKey.invalid') || 'API Key 无效') | ||
| } finally { | ||
| setValidatingZhipuApiKey(false) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Deepgram/Zhipu 校验保存逻辑重复,可抽取通用函数以降低重复与分支差异。
- 建议提炼 validateAndSaveApiKey(providerKey, value, validator) 以统一消息与状态处理,避免两处逻辑漂移。
Also applies to: 83-115
🤖 Prompt for AI Agents
In src/renderer/src/pages/settings/ASRSettings.tsx around lines 83-115 and
149-172, the Deepgram and Zhipu API key validation and saving logic is
duplicated; extract a shared async helper validateAndSaveApiKey(providerKey:
string, value: string, validator: (v:string)=>Promise<boolean>) that centralizes
trimming/empty check, sets a single validating state (or accepts setters), calls
the provided validator, updates the corresponding valid state, shows
success/error messages via t('common.apiKey.*'), persists the key on success via
window.api.config.set(providerKey, value), and ensures validating state is
cleared in finally; replace both blocks with calls to this helper to eliminate
duplication and keep behavior consistent.
| <Input.Password | ||
| value={deepgramApiKey} | ||
| onChange={handleApiKeyChange} | ||
| placeholder={t('settings.asr.apiKey.placeholder')} | ||
| placeholder={t('settings.subtitleGeneration.speechRecognition.apiKey.placeholder')} | ||
| onBlur={handleApiKeySave} | ||
| status={apiKeyValid === false ? 'error' : undefined} | ||
| /> | ||
| <Button onClick={handleValidateApiKey} loading={validatingApiKey}> | ||
| {t('settings.asr.apiKey.validate')} | ||
| {t('common.apiKey.validate')} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
可考虑仅在校验成功后持久化,避免 onBlur 写入无效 Key(体验建议)。
- 目前 onBlur 即保存,用户未点“验证”仍会落盘无效值;可改为验证成功后保存,或在保存时标注未验证状态。
Also applies to: 266-275
🏁 Script executed:
#!/bin/bash
# 校验新 i18n key 是否在中英文 locale 中存在
rg -n "settings\.subtitleGeneration\.translation\.apiKey\.(label|placeholder|getKey|description)" src/renderer/src/i18n/locales -C0
# 校验 window.api.translation.validateApiKey 类型可见性(需配合 preload 全局 d.ts)
rg -n "translation\.validateApiKey" -S src --type=ts --type=tsx -C2Length of output: 27
---
🏁 Script executed:
```shell
#!/bin/bash
# First, check what file types are available and look for TypeScript files
rg --type-list | grep -i "^ts:"
# Search for the i18n keys in locale directory - use proper pattern
rg -n "apiKey" src/renderer/src/i18n/locales -C1 2>/dev/null | head -50
# Find the ASRSettings.tsx file and examine it
find src -name "ASRSettings.tsx" -type f 2>/dev/null
Length of output: 1555
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine ASRSettings.tsx file to understand the handlers and state management
cat src/renderer/src/pages/settings/ASRSettings.tsx | sed -n '1,250p'
# Then look at the second section
cat src/renderer/src/pages/settings/ASRSettings.tsx | sed -n '250,350p'
Length of output: 10050
改进 API Key 持久化逻辑,避免保存未验证的无效密钥。
当前实现中,onBlur 事件会立即保存 API Key,而不等待用户点击验证按钮。这导致无效的密钥被持久化到存储中。
建议改为以下任一方案:
- 移除
onBlur={handleApiKeySave},仅在验证成功后保存(推荐) - 或保留
onBlur但在保存时添加验证状态标记,表示未经验证
此建议适用于:
- Deepgram API Key 处理器(
handleApiKeyChange/handleApiKeySave/handleValidateApiKey) - Zhipu API Key 处理器(
handleZhipuApiKeyChange/handleZhipuApiKeySave/handleValidateZhipuApiKey)
🤖 Prompt for AI Agents
In src/renderer/src/pages/settings/ASRSettings.tsx around lines 198 to 206, the
API key input currently calls handleApiKeySave on onBlur which persists the key
immediately and can store invalid keys; remove the onBlur={handleApiKeySave} and
instead persist the Deepgram API key only after handleValidateApiKey confirms
success (or alternatively gate onBlur save behind an "isValidated" flag), and
apply the same change to the Zhipu handlers (remove onBlur save and only call
handleZhipuApiKeySave after successful handleValidateZhipuApiKey, or mark saves
as unvalidated if keeping onBlur). Ensure the change updates
handleApiKeyChange/handleApiKeySave/handleValidateApiKey and the Zhipu
equivalents so persistence happens only post-validation (or is explicitly
flagged as unvalidated).
| <SettingHelpTextRow style={{ justifyContent: 'space-between' }}> | ||
| <HStack></HStack> | ||
| <SettingHelpLink onClick={openZhipuWebsite}> | ||
| {t('settings.subtitleGeneration.speechRecognition.apiKey.getKey')} | ||
| </SettingHelpLink> | ||
| </SettingHelpTextRow> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
翻译分组“获取密钥”文案 key 使用了语音识别路径,需改为翻译路径。
- 当前使用:settings.subtitleGeneration.speechRecognition.apiKey.getKey
- 建议改为:settings.subtitleGeneration.translation.apiKey.getKey
- <SettingHelpLink onClick={openZhipuWebsite}>
- {t('settings.subtitleGeneration.speechRecognition.apiKey.getKey')}
- </SettingHelpLink>
+ <SettingHelpLink onClick={openZhipuWebsite}>
+ {t('settings.subtitleGeneration.translation.apiKey.getKey')}
+ </SettingHelpLink>🤖 Prompt for AI Agents
In src/renderer/src/pages/settings/ASRSettings.tsx around lines 279 to 284, the
translation key used for the "Get Key" link is incorrect (it references
speechRecognition); update the t(...) call to use the translation path
settings.subtitleGeneration.translation.apiKey.getKey instead of
settings.subtitleGeneration.speechRecognition.apiKey.getKey so the text is
pulled from the translation group for translation features.
| { | ||
| "port": 3100, | ||
| "appPort": 5173, | ||
| "autoPlugins": true, | ||
| "plugins": [] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
建议添加配置文件文档和示例。
此配置文件缺少注释说明各配置项的含义。建议在项目中添加:
- 配置文件的说明文档(如
stagewise.config.md或README.config.md) - 示例配置文件(如
stagewise.example.json) - 明确指定哪些配置项是可选的,哪些是必需的
此外,请确认此文件是否应该被提交到版本控制,或是否应该添加到.gitignore中(如果是开发环境特定的配置)。
🤖 Prompt for AI Agents
In stagewise.json around lines 1 to 6, the file lacks documentation and an
example, so add a docs page (e.g., stagewise.config.md or README.config.md) that
documents every key (port, appPort, autoPlugins, plugins), marks which are
required vs optional, describes expected types/values and defaults, and shows
usage examples; add a committed example config (stagewise.example.json) with
comments in the repo README pointing to it; finally decide and implement VCS
policy by either adding stagewise.json to .gitignore if it is
environment-specific or keeping it committed and documenting how to override it
(e.g., environment variables or a .stagewise.local file) and include that
decision in the new docs.
…rvice Import TranslationOptions type and add explicit type annotation to fix TypeScript compilation error where targetLanguage string was incompatible with 'zh-CN' literal type.
There was a problem hiding this 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
♻️ Duplicate comments (1)
src/main/services/ASRSubtitleService.ts (1)
235-236: 应使用void前缀标明有意放弃等待此处调用后台翻译任务未使用
await或void前缀。虽然内部已有错误处理,但使用void前缀可以更明确地表达"发射后不管"的意图,避免潜在的未处理 Promise 告警。建议修改:
- // 启动后台翻译任务 - this.startBackgroundTranslation(options.videoId, options.videoPath) + // 启动后台翻译任务(显式放弃等待) + void this.startBackgroundTranslation(options.videoId, options.videoPath)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/main/services/ASRSubtitleService.ts(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/services/ASRSubtitleService.ts (3)
src/main/services/ConfigManager.ts (1)
configManager(259-259)src/main/services/SubtitleTranslationService.ts (1)
subtitleTranslationService(440-440)packages/shared/types/translation.ts (1)
TranslationOptions(5-16)
⏰ 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 (3)
src/main/services/ASRSubtitleService.ts (3)
14-15: 导入语句正确新增的
TranslationOptions类型导入用于后台翻译功能,与项目类型结构一致。
29-29: 导入翻译服务正确引入的
subtitleTranslationService单例用于后台翻译任务,导入路径正确。
570-613: 类型标注已修复,实现正确第 578 行显式添加了
: TranslationOptions类型标注,成功解决了之前评审中提到的 TS2345 类型错误。targetLanguage: 'zh-CN'现在被正确推断为字面量类型而非泛型string。方法实现清晰,日志记录完善,错误处理得当。
可选改进:建议在 579 行注释附近添加 TODO,标记未来扩展多语言目标的计划:
const translationOptions: TranslationOptions = { - targetLanguage: 'zh-CN', // 当前版本仅支持翻译为中文 + targetLanguage: 'zh-CN', // TODO: 未来支持更多目标语言 batchSize: 15, maxConcurrency: 2, videoFilename
| /** | ||
| * 启动后台翻译任务 | ||
| */ | ||
| private async startBackgroundTranslation(videoId: number, videoPath: string): Promise<void> { | ||
| try { | ||
| // 检查是否配置了 Zhipu API Key | ||
| const zhipuApiKey = configManager.getZhipuApiKey() | ||
| if (!zhipuApiKey) { | ||
| logger.debug('未配置 Zhipu API Key,跳过翻译任务') | ||
| return | ||
| } | ||
|
|
||
| // 检查翻译服务是否可用 | ||
| if (!subtitleTranslationService.isServiceAvailable()) { | ||
| logger.warn('翻译服务不可用,跳过翻译任务') | ||
| return | ||
| } | ||
|
|
||
| // 提取视频文件名作为上下文 | ||
| const videoFilename = path.basename(videoPath, path.extname(videoPath)) | ||
|
|
||
| logger.info('启动后台翻译任务', { videoId, videoFilename }) | ||
|
|
||
| // 异步启动翻译任务,不阻塞主流程 | ||
| this.translateSubtitlesInBackground(videoId, videoFilename).catch((error) => { | ||
| logger.error('后台翻译任务失败', { | ||
| videoId, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }) | ||
| }) | ||
| } catch (error) { | ||
| logger.error('启动后台翻译任务失败', { | ||
| videoId, | ||
| error: error instanceof Error ? error.message : String(error) | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
建议在 556 行也添加 void 前缀保持一致性
方法整体逻辑清晰,错误处理合理。第 556 行的 Promise 调用已附加 .catch() 处理异步错误,但为了与 236 行的建议保持一致,同样建议添加 void 前缀以明确表达"发射后不管"的意图。
建议修改:
// 异步启动翻译任务,不阻塞主流程
- this.translateSubtitlesInBackground(videoId, videoFilename).catch((error) => {
+ void this.translateSubtitlesInBackground(videoId, videoFilename).catch((error) => {
logger.error('后台翻译任务失败', {
videoId,
error: error instanceof Error ? error.message : String(error)
})
})🤖 Prompt for AI Agents
In src/main/services/ASRSubtitleService.ts around lines 532 to 568, the
background translation Promise is invoked without the explicit "void" prefix
(line ~556) which is inconsistent with other fire-and-forget calls; update the
call to prefix it with "void" so it reads as a deliberate non-awaited invocation
while keeping the existing .catch(...) handler unchanged to preserve error
logging.
- Add getZhipuApiKey method to ConfigManager mocks in both test files - Add debug method to LoggerService mock in ipc.database.test.ts - Resolves TypeError: configManager.getZhipuApiKey is not a function - Resolves TypeError: logger.debug is not a function - All 665 tests now pass successfully
Summary
Test plan
Summary by CodeRabbit
新功能
界面与国际化
文档