Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions 01_PRD_v0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ v0.1 仅服务单一用户,运行在一台 M2 MacBook 上,不需要账号、
3. 模拟 `Command + C`。
4. 在限定时间内读取新文本。
5. 恢复原剪贴板。
6. 仍失败时显示轻提示:“未检测到选中文字”,并提供“开始 OCR”按钮。
6. 仍失败时分两种情况:
- 剪贴板已有文本:把该文本填入原文框,提示区注明内容来自剪贴板,并提供“翻译剪贴板内容”和“开始 OCR”按钮。默认不自动翻译;通用设置“无选中文字时自动翻译剪贴板”开启后跳过确认直接翻译。
- 剪贴板没有文本:显示轻提示“未检测到选中文字”,并提供“开始 OCR”按钮。

禁止在读取失败后自动触发 OCR,避免用户误框选。
禁止在读取失败后自动触发 OCR,避免用户误框选。剪贴板兜底只读取当前剪贴板内容,不保存剪贴板历史。

#### D. OCR 框选

Expand Down Expand Up @@ -115,8 +117,15 @@ v0.1 仅服务单一用户,运行在一台 M2 MacBook 上,不需要账号、
浮窗目标语言控制:

- 一级:中文、English
- 更多:日本語、한국어、繁體中文
- 点击当前目标语言会使用已修改的原文重新翻译。
- 更多:日本語、한국어、繁體中文;选中“更多”里的语言后,菜单标题显示该语言名并呈现选中态。
- 用户在浮窗内手动切换过目标语言后,浮窗保持可见期间的后续取词与 OCR 不再被自动检测覆盖;浮窗关闭后恢复自动检测。

重新翻译的入口:

- 在原文框内按回车。正在翻译时按回车会取消当前请求并用当前原文重新翻译;`Shift + 回车`、`Option + 回车` 插入换行;输入法组字期间回车交给输入法处理。
- 点击原文区的“翻译”按钮,等价于回车。
- 点击当前目标语言。
- 翻译失败时点击错误提示行的“重试”。

翻译应流式显示。若第三方接口不支持流式,应兼容一次性 JSON 返回。

Expand All @@ -134,7 +143,7 @@ v0.1 仅服务单一用户,运行在一台 M2 MacBook 上,不需要账号、

优先保持含义、语气和阅读体验,避免机械逐字对应;只输出译文。

三个预设均只在设置页选择和编辑,并支持恢复默认。“翻译预设”页为当前默认翻译配置选择默认预设;浮窗不提供预设切换
三个预设均只在设置页编辑,并支持恢复默认。“翻译预设”页为当前默认翻译配置选择默认预设。浮窗提供会话级预设切换:切换后立即用当前原文重新翻译,只影响当前浮窗会话,不修改设置中的默认预设,浮窗关闭后回到默认预设

#### H. 翻译模型配置

Expand Down Expand Up @@ -175,37 +184,47 @@ v0.1 仅服务单一用户,运行在一台 M2 MacBook 上,不需要账号、
建议结构:

```text
┌──────────────────────────────────────┐
│ 原文 中文 English 更多 │
│ 可编辑原文区域 │
│ [复制] [朗读] │
├──────────────────────────────────────┤
│ 译文 [翻译中/取消] │
│ 流式译文区域 │
│ [复制] [朗读] │
│ OCR 小图预览(可折叠) [固定] │
└──────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ 原文 预设 中文 English 更多 [固定] │
│ 可编辑原文区域 │
│ [翻译] [复制] [朗读] (OCR 来源追加 [重新框选]) │
├────────────────────────────────────────────────┤
│ 译文 [翻译中/取消] │
│ 流式译文区域 │
│ 错误行:重试 / 打开设置 / 开始 OCR │
│ 提示行:翻译剪贴板内容 / 开始 OCR │
│ [复制] [朗读] │
│ OCR 小图预览(可折叠) │
└────────────────────────────────────────────────┘
```

行为:

- 出现在鼠标或 OCR 区域附近。
- 自动避让菜单栏、Dock 和屏幕边缘。
- 已固定且已显示的浮窗不再被重新定位,只刷新内容并保持前置。
- 点击外部自动关闭。
- 固定后点击外部不关闭。
- `Esc` 关闭,若正在翻译则先取消请求。
- `⌘⇧C` 复制译文;原文框支持 `⌘A`、`⌘C`、`⌘V`、`⌘X`。
- 取词中与本地识别中原文框不可编辑,结束后恢复。
- 流式输出时译文区自动滚动到底部;用户手动向上滚动后,本次翻译不再自动跟随。
- 新快捷键任务会取消当前未完成请求并替换单一面板内容。
- 复制操作显示短暂成功状态。
- 默认不自动复制,不自动朗读
- 自动复制和自动朗读全部由通用设置控制,自动执行时同样给出复制成功状态。除“OCR 完成后自动复制识别文字”默认开启外,其余自动项默认关闭;默认不自动朗读

#### J. 语音朗读

原文和译文分别提供朗读按钮。

- 中文和英文分别保存一个默认 Voice。
- 中文和英文分别保存 Voice、Instructions 和朗读速度
- 使用独立的 TTS 配置,不与翻译模型绑定。
- 首选 OpenAI-compatible `/v1/audio/speech`。
- 支持 OpenAI-compatible `/v1/audio/speech`。
- 支持阿里云百炼 Qwen3-TTS 原生 HTTP 协议,包括
`qwen3-tts-flash` 与 `qwen3-tts-instruct-flash`。
- 支持自定义 URL、API Key、模型、Voice、请求头、输出格式。
- OpenAI-compatible Speech 支持 `0.25×–4.00×` 朗读速度;默认 `1.00×`。
- 百炼 Qwen3-TTS 的生成响应与临时音频下载分两步完成;下载不转发鉴权信息。
- 支持生成、播放、暂停、继续、停止、重新播放。
- 点击另一个朗读按钮时停止当前音频并播放新任务。
- API 不可用时可选择 macOS 本地系统语音兜底。
Expand All @@ -230,7 +249,11 @@ v0.1 仅服务单一用户,运行在一台 M2 MacBook 上,不需要账号、
- 点击外部关闭浮窗
- 默认固定状态,默认关闭
- OCR 完成后自动翻译,默认开启
- OCR 完成后自动复制识别文字,默认开启
- OCR 截图预览,默认开启
- 无选中文字时自动翻译剪贴板,默认关闭
- 翻译完成后自动复制译文,默认关闭
- 翻译完成后自动朗读译文,默认关闭
- 本地 TTS 兜底,默认开启

#### L. 权限引导
Expand Down
57 changes: 50 additions & 7 deletions 02_TECHNICAL_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@ protocol SpeechProviding: Sendable {
"model": "gpt-4o-mini-tts",
"input": "需要朗读的文本",
"voice": "coral",
"response_format": "mp3"
"response_format": "mp3",
"speed": 0.75
}
```

Expand All @@ -425,15 +426,57 @@ protocol SpeechProviding: Sendable {
- API Key
- Bearer 或自定义 Header
- 模型
- 中文 Voice
- 英文 Voice
- 中文 Voice、Instructions、speed
- 英文 Voice、Instructions、speed
- 输出格式
- 可选 instructions
- 超时

Provider 根据 `SpeechRequest.language` 选择对应语言的 Voice、Instructions 与 speed。
设置页将两种语言并列放在“声音与朗读方式”中;输出格式和超时仍属于“请求行为”。
旧 Profile 的单一 `instructions` 与 `speed` 在解码时分别复制到中文和英文配置;
新数据只写入独立字段。

为兼容未完整实现 OpenAI Speech schema 的旧第三方服务,`speed == 1.0` 时不发送
该可选字段;用户选择其他速度时才发送。Profile 解码旧数据时补默认值 `1.0`,
并将异常持久化值限制到官方范围。速度按界面精度归一到两位小数,请求使用
强类型 `Encodable` 与 `JSONEncoder`,禁止把 `0.8` 序列化为带二进制长尾的数字。
HTTP 5xx 归类为服务暂不可用,不显示为请求格式不兼容。百炼 Qwen Provider
不发送此字段。

第三方服务如果遵循 OpenAI Speech schema 可直接使用。若协议不同,新增独立 Provider,不在 v0.1 里加入任意 JSON 模板编辑器。

### 9.3 文本长度
### 9.3 阿里云百炼 Qwen3-TTS HTTP

使用独立 `DashScopeQwenSpeechProvider`,不把百炼协议伪装成
OpenAI `/v1/audio/speech`。

生成 Endpoint:

`/api/v1/services/aigc/multimodal-generation/generation`

请求概念:

```json
{
"model": "qwen3-tts-instruct-flash",
"input": {
"text": "需要朗读的文本",
"voice": "Cherry",
"language_type": "Chinese",
"instructions": "缓慢、清晰地朗读"
}
}
```

- `qwen3-tts-flash` 不发送 `instructions`
- `qwen3-tts-instruct-flash` 按文本语言发送中文或英文 `instructions`
- 非流式响应返回临时 OSS 音频 URL,Provider 随即下载到内存
- 只接受阿里云 OSS `aliyuncs.com` 域名,HTTP URL 升级为 HTTPS
- 音频下载请求不得携带 API Key 或生成请求的自定义 Header
- JSON 元数据与音频分别限制响应大小
- 生成和下载均支持任务取消

### 9.4 文本长度

实现 `TextChunker`:

Expand All @@ -443,7 +486,7 @@ protocol SpeechProviding: Sendable {
- 所有块生成完成后依次播放
- 任一块失败时停止并显示具体块序号

### 9.4 播放
### 9.5 播放

`AudioPlaybackController` 使用 AVFoundation:

Expand All @@ -453,7 +496,7 @@ protocol SpeechProviding: Sendable {
- 任务取消时释放 Data
- 同一时刻只允许一个播放会话

### 9.5 本地兜底
### 9.6 本地兜底

`SystemSpeechProvider` 使用 macOS 系统语音:

Expand Down
26 changes: 25 additions & 1 deletion 03_ACCEPTANCE_TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,31 @@

步骤:配置 OpenAI-compatible 第三方 Speech endpoint。

期望:自定义 URL、模型、Voice、Key、Header 生效,音频成功播放。
期望:

- 自定义 URL、模型、Voice、Key、Header 生效,音频成功播放
- 中文和英文分别使用各自的 Voice、Instructions 与朗读速度
- 旧版单一 Instructions/Speed 配置升级后同时迁移到中文和英文,不丢失设置
- 调整朗读速度后,请求发送对应 `speed`
- `0.8×`、`0.85×` 等速度以简洁十进制数字发送,不包含二进制浮点长尾
- 默认 `1.0×` 不发送可选 `speed` 字段,保持旧第三方服务兼容
- 修改速度后缓存键变化,不复用其他速度生成的音频
- HTTP 5xx 显示服务暂不可用,不误报为请求格式不兼容

### P0-14A 百炼 Qwen3-TTS HTTP

步骤:选择“阿里云百炼 Qwen HTTP”,配置北京区工作空间域名、API Key、
`qwen3-tts-flash` 或 `qwen3-tts-instruct-flash`,分别朗读中英文。

期望:

- 使用百炼原生 HTTP 请求结构,而不是 `/v1/audio/speech`
- 中文和英文分别发送正确 Voice 与 `language_type`
- 仅 Instruct 模型发送 Instructions
- 临时 OSS 音频地址升级为 HTTPS 后下载并播放
- 下载请求不携带 API Key 或自定义鉴权 Header
- 非阿里云 OSS 音频地址被拒绝
- 相同文本、语言和配置再次朗读时复用内存缓存,不重复计费请求

### P0-15 TTS 本地兜底

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Both paths open one floating panel where the source stays editable and the sourc
- **Two shortcuts, one flow.** No main window to find and no context switching.
- **Local OCR.** Screenshots stay in memory and text recognition runs through Apple Vision.
- **Bring your own model.** Configure custom URLs, models, headers, timeouts, streaming, OpenAI Responses, or Chat Completions.
- **Flexible speech.** Use an OpenAI-compatible speech endpoint or explicitly fall back to macOS system voices.
- **Flexible speech.** Use an OpenAI-compatible speech endpoint, Alibaba Cloud Model Studio Qwen3-TTS over native HTTP, or explicitly fall back to macOS system voices.
- **Privacy by design.** Keys live in Keychain; logs are redacted; there is no history, account, telemetry, or cloud sync.
- **Native and lean.** Swift 6, SwiftUI + AppKit, Apple frameworks, and zero third-party runtime dependencies.
- **Cancellation-safe.** A new capture replaces the previous session and cancels in-flight OCR, network, and audio work.
Expand Down Expand Up @@ -117,7 +117,7 @@ Translation profiles support:
- Automatic, enabled, or disabled streaming
- Streaming SSE and non-streaming JSON responses

Speech profiles support OpenAI-compatible `/v1/audio/speech` endpoints with custom URL, model, voice, headers, and output format. “OpenAI-compatible” implementations vary; please open a compatibility report when a provider needs a dedicated adapter.
Speech profiles support OpenAI-compatible `/v1/audio/speech` endpoints with custom URL, model, headers, output format, and independent Chinese/English voice, instructions, and `0.25×–4.00×` speed control. A dedicated Alibaba Cloud Model Studio HTTP adapter supports `qwen3-tts-flash` and `qwen3-tts-instruct-flash`; it downloads the short-lived audio result over HTTPS without forwarding API credentials. Other “OpenAI-compatible” implementations still vary, so please open a compatibility report when a provider needs a dedicated adapter.

Never place a real API key in source files, Markdown, test fixtures, `.env` files committed to Git, or issue screenshots.

Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
- **两个快捷键,一条工作流。** 不需要先找到主窗口,也不打断当前工作。
- **OCR 完全本地。** 截图只驻留内存,识别使用 Apple Vision。
- **自带模型。** 支持自定义 URL、模型、Header、超时、流式模式、Responses 和 Chat Completions。
- **灵活朗读。** 可使用 OpenAI-compatible Speech 接口,也可由用户明确切换到 macOS 系统语音。
- **灵活朗读。** 可使用 OpenAI-compatible Speech 接口、阿里云百炼 Qwen3-TTS 原生 HTTP,也可由用户明确切换到 macOS 系统语音。
- **隐私优先。** 密钥进入 Keychain;日志脱敏;不保存历史;没有账号、遥测或云同步。
- **原生且克制。** Swift 6、SwiftUI + AppKit、Apple 原生框架、零第三方运行时依赖。
- **完整取消链路。** 新任务会替换旧会话并取消进行中的 OCR、网络和音频任务。
Expand Down Expand Up @@ -117,7 +117,7 @@ brew install xcodegen
- 自动、开启或关闭流式模式
- SSE 流式响应和非流式 JSON 响应

语音配置支持 OpenAI-compatible `/v1/audio/speech`,可自定义 URL、模型、Voice、Header 和输出格式。不同厂商对“OpenAI 兼容”的实现并不完全一致;如需专门适配器,请提交兼容性报告。
语音配置支持 OpenAI-compatible `/v1/audio/speech`,可自定义 URL、模型、Header 和输出格式,并可独立配置中文/英文 Voice、Instructions 及 `0.25×–4.00×` 朗读速度。专用的阿里云百炼 HTTP 适配器支持 `qwen3-tts-flash` 和 `qwen3-tts-instruct-flash`,会通过 HTTPS 下载短期音频结果且不转发 API 凭据。其他厂商对“OpenAI 兼容”的实现仍可能不同;如需专门适配器,请提交兼容性报告。

不要把真实 API Key 放入源码、Markdown、测试 fixture、Git 中的 `.env` 文件或 Issue 截图。

Expand Down
Loading