Skip to main content
Glama
README.md
# OpenCode 语音控制(离线 ASR)

[![ci](https://github.com/BioMediChen/opencode-voice/actions/workflows/ci.yml/badge.svg)](https://github.com/BioMediChen/opencode-voice/actions/workflows/ci.yml)
![license](https://img.shields.io/badge/license-MIT-blue)
![platform](https://img.shields.io/badge/platform-Windows%2010%2F11-lightgrey)
![python](https://img.shields.io/badge/python-3.12-blue)

按住 **F8** 或**鼠标侧键**说话 → 松开 → SenseVoice 本地转写 → 文本自动作为用户消息投递到当前 OpenCode 会话。

- ASR:sherpa-onnx + SenseVoice-small (int8),纯 CPU,中文/中英混说,本机实测 11s 音频转写 0.28s
- 交互:全局热键 / 鼠标侧键按住说话(push-to-talk),无需唤醒词
- 投递:OpenCode HTTP API(Basic auth,自动从 `opencode service status` 发现端口)
- 打断:说“停止 / 取消”等词直接中断当前 agent 回合,不投递文本
- 纠错:`config.toml` 里的 `[terms]` 把常见误识别替换成代码写法(如“use state”→`useState`)
- MCP:`voice` 服务器提供 `status` / `listen` / `interrupt`,`listen` 让 agent 等待用户口头确认(该次语音不会自动提交)

## 目录

```
src/voice_asr/
  asr.py            SenseVoice 封装
  audio.py          麦克风采集、静音裁剪、重采样
  corrections.py    术语纠错
  opencode_client.py OpenCode 会话发现与 prompt 投递
  daemon.py         热键/鼠标守护进程 + 本地控制接口 (127.0.0.1:8756)
  mcp_server.py     MCP stdio 服务器(工具层)
  check.py          自检:设备列表 / 模型 / 转写
config.toml         配置
models/sensevoice/  SenseVoice int8 模型 (239MB) + tokens
logs/               运行日志
scripts/            MCP/投递自检、开机自启安装脚本
```

## 快速开始(从零克隆)

```powershell
git clone https://github.com/BioMediChen/opencode-voice.git E:\voice
cd E:\voice

# 1) Python 环境(uv 会自动装 3.12)
uv sync

# 2) 下载 SenseVoice int8 模型(约 239MB,国内可用 hf-mirror)
New-Item -ItemType Directory -Force models\sensevoice | Out-Null
curl.exe -L -o models\sensevoice\model.int8.onnx https://hf-mirror.com/csukuangfj/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/resolve/main/model.int8.onnx
curl.exe -L -o models\sensevoice\tokens.txt https://hf-mirror.com/csukuangfj/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/resolve/main/tokens.txt

# 3) 自检
uv run voice-check --record 4
uv run python scripts\selftest_e2e.py

# 4) 启动守护进程
uv run voice-daemon
```

平台:Windows 10/11(音频提示音、全局热键依赖 Windows)。Linux 下需自行处理 `winsound` 和音频设备。

## 实测效果

```
[recording:mouse:x2] ... release to transcribe
[asr] 1.38s  测试us stay和优 effect.
[terms] ... -> 测试useState和useEffect.
[sent -> ses_e6047cd3...] opencode MCP 语音转文字接入
```

SenseVoice-small int8 在 Ryzen 7 8840U(8C/16T)上的实测:5.7 秒语音转写 0.14 秒,
RTF ≈ 0.03(约为实时的 30 倍),全程离线、无 GPU。

## 作为命令行工具安装(可选)

不想克隆整个仓库时:

```powershell
uv tool install git+https://github.com/BioMediChen/opencode-voice

# 配置和模型放在固定目录(模型路径相对 config.toml 所在目录解析)
$env:VOICE_CONFIG="E:\voice\config.toml"
voice-daemon
```

安装后可用:`voice-daemon`、`voice-mcp`、`voice-check`。
MCP 注册时把 command 换成本机的 `voice-mcp` 可执行文件即可。

## 使用

```powershell
# 自检:列出输入设备,用麦克风录 4 秒并转写
uv run voice-check --record 4

# 转写一个 wav 文件
uv run voice-check --wav whisper.cpp\samples\jfk.wav

# 端到端自检(不消耗模型额度)
uv run python scripts\selftest_e2e.py

# 前台启动守护进程(调试用,能看实时日志)
uv run voice-daemon

# 静默启动(日常使用)
start-voice-silent.cmd
```

启动后按住 F8 或鼠标侧键说话,松开即发送(开始/结束各有一声提示音)。

```powershell
# 查看状态
curl.exe http://127.0.0.1:8756/status

# 无麦克风时直接测试投递
curl.exe -X POST http://127.0.0.1:8756/submit -H "Content-Type: application/json" -d "{\"text\":\"你好\"}"
```

## 配置要点(config.toml)

| 项 | 说明 |
| --- | --- |
| `hotkey.key` | 键盘触发键,`f1`..`f12` / `caps_lock` / `scroll_lock` / 单字符 |
| `hotkey.mouse` | 鼠标触发键,`x1`(后) / `x2`(前) / `middle`,`[]` 关闭 |
| `audio.device` | 留空用系统默认;填序号或名称片段(如 `HECATE`) |
| `commands.stop` | 说完即打断的短语(整句匹配,忽略标点) |
| `terms` | 误识别 → 目标写法;英文规则按单词边界匹配,不会破坏 `useState` |
| `opencode.session_mode` | `auto`(跟随活跃/最近会话,支持多项目)/ `directory`(限定目录)/ `pinned`(固定 `session_id`) |
| `opencode.directory` | `directory` 模式下的目标目录 |
| `opencode.session_id` | `pinned` 模式下的固定会话 |
| `asr.language` | `auto` / `zh` / `en` / `ja` / `ko` / `yue` |

修改配置后重启守护进程。注意鼠标侧键同时也会被浏览器/资源管理器当作“前进/后退”,
如果觉得干扰,可以把 `hotkey.mouse` 改成 `["middle"]` 或 `[]`。

## 自检与术语维护

```powershell
# 端到端自检:守护进程 / 会话创建 / 投递 / 纠错 / 停止分支(不消耗模型额度)
uv run python scripts\selftest_e2e.py

# MCP 服务器工具列表自检
uv run python scripts\selftest_mcp.py

# 扫描日志,找出还没被 [terms] 覆盖的英文词,给出待补建议
uv run python scripts\suggest_terms.py
```

`[terms]` 的格式是「正确写法 = [可能被听成的说法]」,以 use 开头的两词名称会自动展开
`use/us/u/you/优/优思/优斯` 与 `state/stay/stat`、`effect/affect` 等音近组合,
所以通常只需写标准说法,例如:

```toml
[terms]
"useState" = ["use state"]
"OpenCode" = ["open code", "opencode"]
"E:\\voice" = ["e 盘 voice", "e voice"]
```

## 多项目切换

`opencode.session_mode = "auto"` 时的选择顺序:

1. 配置了 `session_id` → 固定投递到该会话
2. 当前有且仅有一个会话在运行 → 投递到它(语音打断/追加指令就靠这条)
3. 否则投递到最近更新的会话(跨项目生效,跟着你正在用的会话走)

如果想锁定某个项目,改成 `"directory"` 并设置 `opencode.directory`;
想锁定某个会话,改成 `"pinned"` 并填写 `opencode.session_id`。

## MCP 注册

全局配置 `~/.config/opencode/opencode.json` 已注册:

```json
{
  "mcp": {
    "servers": {
      "voice": {
        "type": "local",
        "command": ["E:\\voice\\.venv\\Scripts\\python.exe", "-m", "voice_asr.mcp_server"],
        "cwd": "E:\\voice",
        "environment": { "PYTHONUTF8": "1", "PYTHONIOENCODING": "utf-8" }
      }
    }
  }
}
```

MCP 服务器在需要时会自动拉起守护进程(`VOICE_NO_AUTOSTART=1` 可关闭)。

## 开机自启

已通过 `scripts/install-autostart.ps1` 在启动文件夹创建 `OpenCode Voice.lnk`。
重新安装/更换路径时重跑:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\install-autostart.ps1
```

## 许可证

[MIT](LICENSE)