vault backup: 2026-06-09 23:15:17

This commit is contained in:
2026-06-09 23:15:17 +08:00
parent 31fd89aafe
commit e92c0327d9
111 changed files with 7276 additions and 8846 deletions
+65 -106
View File
@@ -1,15 +1,20 @@
---
tags: [voice, stt, 语音输入, push-to-talk, doubao, anthropic]
create time: 2026-06-09 22:30
---
# VOICE_MODE — 语音输入
## 概述
VOICE_MODE 实现"按键说话"(Push-to-Talk)语音输入。用户按住空格键录音,音频流式传输到 STT 后端,实时转录显示在终端中。支持 Anthropic STT 和豆包 ASR 两个后端。
> [!info]
> Feature Flag: `FEATURE_VOICE_MODE=1`
> 实现状态:完整可用(双后端:Anthropic OAuth / 豆包 ASR)
> 引用数:46
## 一、功能概述
VOICE_MODE 实现"按键说话"(Push-to-Talk)语音输入。用户按住空格键录音,音频流式传输到 STT 后端,实时转录显示在终端中。支持两个后端:
- **Anthropic STT(默认)**:通过 WebSocket 流式传输到 Nova 3 端点,需要 Anthropic OAuth
- **豆包 ASR(Doubao)**:通过 `doubaoime-asr` 包的 AsyncGenerator 协议流式识别,使用独立凭证文件,无需 Anthropic OAuth
## 正文
### 核心特性
@@ -18,7 +23,7 @@ VOICE_MODE 实现"按键说话"(Push-to-Talk)语音输入。用户按住空
- **无缝集成**:转录文本直接作为用户消息提交到对话
- **双后端切换**:通过 `/voice` 命令参数选择 STT 后端,持久化到 settings.json
## 二、用户交互
### 用户交互
| 操作 | 行为 |
|------|------|
@@ -28,15 +33,15 @@ VOICE_MODE 实现"按键说话"(Push-to-Talk)语音输入。用户按住空
| `/voice doubao` | 启用语音模式并使用豆包 ASR 后端 |
| `/voice anthropic` | 切换回 Anthropic STT 后端 |
### UI 反馈
#### UI 反馈
- **录音指示器**:录音时显示红色/脉冲动画
- **中间转录**:录音过程中显示 STT 实时识别文本
- **最终转录**:完成后替换中间结果
## 三、实现架构
### 实现架构
### 3.1 门控逻辑
#### 门控逻辑
文件:`src/voice/voiceModeEnabled.ts`
@@ -50,12 +55,14 @@ isVoiceModeEnabled() = hasVoiceAuth() && isVoiceGrowthBookEnabled()
isVoiceAvailable() = isVoiceGrowthBookEnabled()
```
1. **Feature Flag**:`feature('VOICE_MODE')` — 编译时/运行时开关
2. **GrowthBook Kill-Switch**:`!getFeatureValue_CACHED_MAY_BE_STALE('tengu_amber_quartz_disabled', false)` — 紧急关闭开关(默认 false = 未禁用)
3. **Auth 检查(仅 Anthropic)**:`hasVoiceAuth()` — 需要 Anthropic OAuth token(非 API key)
4. **Provider 检查**:`voiceProvider` 设置决定使用哪个后端,豆包后端跳过 OAuth 检查
四层门控:
### 3.2 核心模块
1. **Feature Flag**:`feature('VOICE_MODE')` — 编译时/运行时开关
2. **GrowthBook Kill-Switch**:`!getFeatureValue_CACHED_MAY_BE_STALE('tengu_amber_quartz_disabled', false)` — 紧急关闭开关
3. **Auth 检查(仅 Anthropic)**:`hasVoiceAuth()` — 需要 Anthropic OAuth token
4. **Provider 检查**:`voiceProvider` 设置决定使用哪个后端
#### 核心模块
| 模块 | 职责 |
|------|------|
@@ -67,90 +74,61 @@ isVoiceAvailable() = isVoiceGrowthBookEnabled()
| `src/hooks/useVoiceEnabled.ts` | 语音启用状态 hook,根据 provider 决定是否跳过 OAuth |
| `src/utils/settings/types.ts` | `voiceProvider: 'anthropic' | 'doubao'` 设置类型定义 |
### 3.3 数据流
#### 数据流
#### Anthropic 后端
##### Anthropic 后端
```
用户按下空格键
│
▼
useVoice hook 激活
│
▼
macOS 原生音频 / SoX 开始录音
│
▼
WebSocket 连接到 Anthropic STT 端点
│
├──→ 中间转录结果 → 实时显示
│
▼
用户释放空格键
│
▼
停止录音,等待最终转录
│
▼
转录文本 → 插入输入框 → 自动提交
```mermaid
graph TD
A["用户按下空格键"] --> B["useVoice hook 激活"]
B --> C["macOS 原生音频 / SoX 开始录音"]
C --> D["WebSocket 连接到 Anthropic STT 端点"]
D --> E["中间转录结果 → 实时显示"]
D --> F["用户释放空格键"]
F --> G["停止录音,等待最终转录"]
G --> H["转录文本 → 插入输入框 → 自动提交"]
```
#### 豆包 ASR 后端
##### 豆包 ASR 后端
```
用户按下空格键
│
▼
useVoice hook 激活(检测到 voiceProvider === 'doubao')
│
▼
macOS 原生音频 / SoX 开始录音
│
▼
connectDoubaoStream() 创建 AudioChunkQueue + VoiceStreamConnection
│
├──→ onReady 立即触发(无需等待握手)
│
▼
音频数据通过 AudioChunkQueue 传入 transcribeRealtime()
│
├──→ INTERIM_RESULT → 实时显示中间转录
├──→ FINAL_RESULT → 显示最终转录
│
▼
用户释放空格键
│
▼
finalize() 立即返回(豆包在录音过程中已返回结果,无需等待)
│
▼
转录文本 → 插入输入框 → 自动提交
```mermaid
graph TD
A["用户按下空格键"] --> B["useVoice hook 激活 voiceProvider=doubao"]
B --> C["macOS 原生音频 / SoX 开始录音"]
C --> D["connectDoubaoStream 创建 AudioChunkQueue + VoiceStreamConnection"]
D --> E["onReady 立即触发(无需等待握手)"]
E --> F["音频数据通过 AudioChunkQueue 传入 transcribeRealtime"]
F --> G["INTERIM_RESULT → 实时显示中间转录"]
F --> H["FINAL_RESULT → 显示最终转录"]
H --> I["用户释放空格键"]
I --> J["finalize 立即返回"]
J --> K["转录文本 → 插入输入框 → 自动提交"]
```
### 3.4 音频录制
#### 音频录制
支持两种音频后端(两个 STT 后端共享):
- **macOS 原生音频**:优先使用,低延迟
- **SoX(Sound eXchange)**:回退方案,跨平台
### 3.5 豆包 ASR 适配器设计
#### 豆包 ASR 适配器设计
文件:`src/services/doubaoSTT.ts`
豆包后端使用适配器模式,将 `doubaoime-asr` 的 AsyncGenerator 协议桥接到 `VoiceStreamConnection` 接口:
**AudioChunkQueue** — push 式异步队列:
- 实现 `AsyncIterable<Uint8Array>` 接口
- `push(chunk)` 将音频数据入队,`push(null)` 发送结束信号
- 内部维护等待者(waiting)和缓冲队列(chunks)两个状态
**connectDoubaoStream()** — 连接入口:
- 动态导入 `doubaoime-asr`(optionalDependencies)
- 从 `~/.claude/tts/doubao/credentials.json` 加载凭证
- 创建 AudioChunkQueue 和 VoiceStreamConnection
- 立即触发 `onReady`(避免与 useVoice 的音频缓冲死锁)
- `finalize()` 立即返回(豆包在录音过程中已返回结果)
- 后台 async IIFE 消费 `transcribeRealtime` generator,映射响应类型到回调
**响应类型映射**:
@@ -163,42 +141,19 @@ finalize() 立即返回(豆包在录音过程中已返回结果,无需等待
| ERROR | `onError(errorMsg)` |
| SESSION_FINISHED | 日志记录 |
### 3.6 后端选择逻辑
### 关键设计决策
文件:`src/hooks/useVoice.ts`
```ts
// 判断当前 provider
isDoubaoProvider() → 读取 settings.voiceProvider
// handleKeyEvent 中的可用性检查
const sttAvailable = isDoubaoProvider()
? isDoubaoAvailableSync() // 乐观检查(首次返回 true)
: isVoiceStreamAvailable() // Anthropic WebSocket 检查
// attemptConnect 中的连接函数选择
const connectFn = isDoubaoProvider()
? connectDoubaoStream
: connectVoiceStream
```
豆包后端的特殊处理:
- 跳过 `getVoiceKeyterms()` 调用(豆包无需关键词提示)
- 跳过 Focus Mode(`if (!enabled || !focusMode || isDoubaoProvider())`)
## 四、关键设计决策
1. **双后端共存**:豆包后端作为独立适配器与 Anthropic 后端并存,不替换原有流程,通过 `voiceProvider` 设置切换
1. **双后端共存**:豆包后端作为独立适配器与 Anthropic 后端并存,通过 `voiceProvider` 设置切换
2. **设置持久化**:`voiceProvider` 存储在 `settings.json`,通过 `/voice` 命令修改,跨会话生效
3. **OAuth 独占(Anthropic)**:Anthropic 后端使用 `voice_stream` 端点(claude.ai),仅 OAuth 用户可用
4. **豆包无需 OAuth**:豆包后端使用独立凭证文件,不依赖 Anthropic 认证,通过 `isVoiceAvailable()` 放宽门控
4. **豆包无需 OAuth**:豆包后端使用独立凭证文件,不依赖 Anthropic 认证
5. **GrowthBook 负向门控**:`tengu_amber_quartz_disabled` 默认 `false`,新安装自动可用
6. **onReady 立即触发**:豆包后端在连接建立后立即触发 `onReady`,避免与 useVoice 音频缓冲的时序死锁(Anthropic 需要等待 WebSocket 握手)
7. **finalize() 立即返回**:豆包在录音过程中已返回所有结果,用户抬手时无需等待处理
8. **乐观可用性检查**:`isDoubaoAvailableSync()` 在首次调用时返回 `true`,实际导入错误在 `connectDoubaoStream` 中处理
6. **onReady 立即触发**:避免与 useVoice 音频缓冲的时序死锁
7. **finalize() 立即返回**:豆包在录音过程中已返回所有结果,用户抬手时无需等待
8. **乐观可用性检查**:`isDoubaoAvailableSync()` 首次调用返回 `true`,实际导入错误在 `connectDoubaoStream` 中处理
9. **optionalDependencies**:`doubaoime-asr` 作为可选依赖,安装失败不影响 Anthropic 后端
## 五、使用方式
### 使用方式
```bash
# 启用 feature
@@ -223,7 +178,7 @@ FEATURE_VOICE_MODE=1 bun run dev
/voice # 关闭语音模式
```
### 豆包凭证配置
#### 豆包凭证配置
凭证文件路径:`~/.claude/tts/doubao/credentials.json`
@@ -238,7 +193,7 @@ FEATURE_VOICE_MODE=1 bun run dev
}
```
## 六、外部依赖
### 外部依赖
| 依赖 | 说明 | 适用后端 |
|------|------|----------|
@@ -249,7 +204,7 @@ FEATURE_VOICE_MODE=1 bun run dev
| doubaoime-asr | 豆包 ASR SDK(optionalDependencies) | 豆包 |
| 凭证文件 | `~/.claude/tts/doubao/credentials.json` | 豆包 |
## 七、文件索引
### 文件索引
| 文件 | 职责 |
|------|------|
@@ -261,3 +216,7 @@ FEATURE_VOICE_MODE=1 bun run dev
| `src/commands/voice/voice.ts` | `/voice` 命令(开关 + 后端选择) |
| `src/commands/voice/index.ts` | 命令注册(去除 availability 限制) |
| `src/utils/settings/types.ts` | `voiceProvider` 类型定义 |
## 关联笔记
- [[all-features-guide]]