dlinkclaw-openclaw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/README.en.md +46 -0
  3. package/README.md +15 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.js +1 -0
  6. package/dist/package.json +95 -0
  7. package/dist/src/account.d.ts +54 -0
  8. package/dist/src/account.js +394 -0
  9. package/dist/src/channel.d.ts +17 -0
  10. package/dist/src/channel.js +153 -0
  11. package/dist/src/client/client-manager.d.ts +4 -0
  12. package/dist/src/client/client-manager.js +10 -0
  13. package/dist/src/client/const.d.ts +37 -0
  14. package/dist/src/client/const.js +155 -0
  15. package/dist/src/client/crypto.d.ts +47 -0
  16. package/dist/src/client/crypto.js +260 -0
  17. package/dist/src/client/dl-client.d.ts +117 -0
  18. package/dist/src/client/dl-client.js +588 -0
  19. package/dist/src/client/dl-http.d.ts +27 -0
  20. package/dist/src/client/dl-http.js +223 -0
  21. package/dist/src/client/media-handler.d.ts +21 -0
  22. package/dist/src/client/media-handler.js +196 -0
  23. package/dist/src/client/message.d.ts +96 -0
  24. package/dist/src/client/message.js +1211 -0
  25. package/dist/src/client/timeout.d.ts +20 -0
  26. package/dist/src/client/timeout.js +36 -0
  27. package/dist/src/client/types.d.ts +226 -0
  28. package/dist/src/client/types.js +1 -0
  29. package/dist/src/monitor.d.ts +18 -0
  30. package/dist/src/monitor.js +322 -0
  31. package/dist/src/outbound.d.ts +6 -0
  32. package/dist/src/outbound.js +78 -0
  33. package/dist/src/policy.d.ts +42 -0
  34. package/dist/src/policy.js +216 -0
  35. package/dist/src/runtime.d.ts +9 -0
  36. package/dist/src/runtime.js +65 -0
  37. package/dist/src/services/gateway-rpc.d.ts +37 -0
  38. package/dist/src/services/gateway-rpc.js +37 -0
  39. package/dist/src/services/index.d.ts +41 -0
  40. package/dist/src/services/index.js +245 -0
  41. package/dist/src/services/services.d.ts +169 -0
  42. package/dist/src/services/services.js +1160 -0
  43. package/dist/src/services/types.d.ts +391 -0
  44. package/dist/src/services/types.js +5 -0
  45. package/dist/src/services/ws-client.d.ts +186 -0
  46. package/dist/src/services/ws-client.js +896 -0
  47. package/dist/src/utils.d.ts +20 -0
  48. package/dist/src/utils.js +128 -0
  49. package/ext_capabilities/workrelay-speak/CLONE_GUIDE.md +129 -0
  50. package/ext_capabilities/workrelay-speak/HARDWARE.md +84 -0
  51. package/ext_capabilities/workrelay-speak/QWEN_GUIDE.md +151 -0
  52. package/ext_capabilities/workrelay-speak/README.md +79 -0
  53. package/ext_capabilities/workrelay-speak/__pycache__/patch-kokoro-en.cpython-312.pyc +0 -0
  54. package/ext_capabilities/workrelay-speak/install.sh +329 -0
  55. package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
  56. package/ext_capabilities/workrelay-speak/manifest.json +63 -0
  57. package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
  58. package/ext_capabilities/workrelay-speak/status.sh +111 -0
  59. package/ext_capabilities/workrelay-speak/workrelay-tts.sh +118 -0
  60. package/ext_capabilities/workrelay-speak-luxtts/README.md +44 -0
  61. package/ext_capabilities/workrelay-speak-luxtts/install.sh +330 -0
  62. package/ext_capabilities/workrelay-speak-luxtts/manifest.json +63 -0
  63. package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav +0 -0
  64. package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav +0 -0
  65. package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav +0 -0
  66. package/ext_capabilities/workrelay-speak-luxtts/samples/reference_text.txt +4 -0
  67. package/ext_capabilities/workrelay-speak-luxtts/samples/stone-sample-6.4s.wav +0 -0
  68. package/ext_capabilities/workrelay-speak-luxtts/status.sh +115 -0
  69. package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
  70. package/ext_capabilities/workrelay-speak-qwen/README.md +62 -0
  71. package/ext_capabilities/workrelay-speak-qwen/install.sh +349 -0
  72. package/ext_capabilities/workrelay-speak-qwen/manifest.json +63 -0
  73. package/ext_capabilities/workrelay-speak-qwen/status.sh +106 -0
  74. package/ext_capabilities/workrelay-speak-qwen/workrelay-tts.sh +98 -0
  75. package/ext_capabilities/workrelay-transcribe/README.md +76 -0
  76. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe.cpython-312.pyc +0 -0
  77. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe_server.cpython-312.pyc +0 -0
  78. package/ext_capabilities/workrelay-transcribe/install.sh +227 -0
  79. package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
  80. package/ext_capabilities/workrelay-transcribe/status.sh +63 -0
  81. package/ext_capabilities/workrelay-transcribe/transcribe.py +56 -0
  82. package/ext_capabilities/workrelay-transcribe/transcribe_server.py +116 -0
  83. package/ext_capabilities/workrelay-transcribe/voicebox-transcribe.sh +88 -0
  84. package/openclaw.plugin.json +142 -0
  85. package/package.json +95 -0
@@ -0,0 +1,20 @@
1
+ export declare const readlineHandle: (filename: string, callback: (line: string) => void) => Promise<void>;
2
+ export declare const getOpenclawPath: (...paths: string[]) => string;
3
+ /**
4
+ * 2026-08-10 扩展能力(ext_capabilities)目录定位。
5
+ * 优先从插件包目录找(开发/本地路径),找不到则退回 ~/.openclaw/ext_capabilities。
6
+ */
7
+ /**
8
+ * 2026-08-10 扩展能力(ext_capabilities)目录定位。
9
+ * 注意:插件是 ESM("type": "module"),__dirname 未定义,必须用 import.meta.url 定位。
10
+ * 从当前文件向上查找含 package.json + ext_capabilities 的插件根目录,
11
+ * 同时保留 cwd / ~/.openclaw 作为兜底,兼容不同启动方式。
12
+ */
13
+ export declare const getExtCapabilitiesDir: () => string;
14
+ export declare const dtFormat: (dt: Date, fmt: string) => string;
15
+ /**
16
+ * 生成指定长度的uuid
17
+ * @param {Object} len
18
+ * @param {Object} radix 2,10,16进制
19
+ */
20
+ export declare const uuidStr: (len: number, radix?: number) => string;
@@ -0,0 +1,128 @@
1
+ import fs from 'fs';
2
+ import path, { join } from 'path';
3
+ import os, { tmpdir } from 'os';
4
+ import readline from 'readline';
5
+ import { fileURLToPath } from 'url';
6
+ import { copyFile, unlink } from 'fs/promises';
7
+ export const readlineHandle = async (filename, callback) => {
8
+ //被免文件被锁导致gateway重启,需要复制一份副本
9
+ const tmpFile = join(tmpdir(), `traj-${uuidStr(36)}.jsonl`);
10
+ await copyFile(filename, tmpFile);
11
+ //直接读取副本内容
12
+ const rl = readline.createInterface({
13
+ input: fs.createReadStream(tmpFile),
14
+ crlfDelay: Infinity,
15
+ });
16
+ for await (const line of rl) {
17
+ if (!line.trim())
18
+ continue;
19
+ callback(line);
20
+ }
21
+ callback('---end---');
22
+ //清理副本文件
23
+ await unlink(tmpFile).catch(() => { });
24
+ };
25
+ export const getOpenclawPath = (...paths) => {
26
+ return path.join(os.homedir(), '.openclaw', ...paths);
27
+ };
28
+ /**
29
+ * 2026-08-10 扩展能力(ext_capabilities)目录定位。
30
+ * 优先从插件包目录找(开发/本地路径),找不到则退回 ~/.openclaw/ext_capabilities。
31
+ */
32
+ /**
33
+ * 2026-08-10 扩展能力(ext_capabilities)目录定位。
34
+ * 注意:插件是 ESM("type": "module"),__dirname 未定义,必须用 import.meta.url 定位。
35
+ * 从当前文件向上查找含 package.json + ext_capabilities 的插件根目录,
36
+ * 同时保留 cwd / ~/.openclaw 作为兜底,兼容不同启动方式。
37
+ */
38
+ export const getExtCapabilitiesDir = () => {
39
+ const here = path.dirname(fileURLToPath(import.meta.url));
40
+ // 从当前文件(src/ 或 dist/src/)向上查找插件根目录(含 package.json 且含 ext_capabilities)
41
+ let dir = here;
42
+ for (let i = 0; i < 6; i++) {
43
+ try {
44
+ if (fs.existsSync(path.join(dir, 'package.json')) && fs.existsSync(path.join(dir, 'ext_capabilities'))) {
45
+ return path.join(dir, 'ext_capabilities');
46
+ }
47
+ }
48
+ catch { /* ignore */ }
49
+ const parent = path.dirname(dir);
50
+ if (parent === dir)
51
+ break;
52
+ dir = parent;
53
+ }
54
+ // 兜底:cwd / ~/.openclaw
55
+ const fallbacks = [
56
+ path.resolve(process.cwd(), 'ext_capabilities'),
57
+ getOpenclawPath('ext_capabilities'),
58
+ ];
59
+ for (const dir2 of fallbacks) {
60
+ try {
61
+ if (fs.existsSync(dir2))
62
+ return dir2;
63
+ }
64
+ catch { /* ignore */ }
65
+ }
66
+ return fallbacks[0];
67
+ };
68
+ export const dtFormat = (dt, fmt) => {
69
+ const o = {
70
+ "M+": dt.getMonth() + 1, //月份
71
+ "d+": dt.getDate(), //日
72
+ "h+": dt.getHours(), //小时
73
+ "m+": dt.getMinutes(), //分
74
+ "s+": dt.getSeconds(), //秒
75
+ "q+": Math.floor((dt.getMonth() + 3) / 3), //季度
76
+ "S": dt.getMilliseconds() //毫秒
77
+ };
78
+ // 处理年份
79
+ const yearMatch = fmt.match(/(y+)/);
80
+ if (yearMatch) {
81
+ const yearStr = dt.getFullYear().toString();
82
+ const pattern = yearMatch[0];
83
+ const len = pattern.length;
84
+ fmt = fmt.replace(pattern, yearStr.slice(-len));
85
+ }
86
+ // 处理其他格式
87
+ for (const k in o) {
88
+ const regex = new RegExp("(" + k + ")");
89
+ const match = fmt.match(regex);
90
+ if (match) {
91
+ const pattern = match[0];
92
+ const value = o[k].toString();
93
+ const replacement = pattern.length === 1
94
+ ? value
95
+ : ("00" + value).slice(-pattern.length);
96
+ fmt = fmt.replace(pattern, replacement);
97
+ }
98
+ }
99
+ return fmt;
100
+ };
101
+ /**
102
+ * 生成指定长度的uuid
103
+ * @param {Object} len
104
+ * @param {Object} radix 2,10,16进制
105
+ */
106
+ export const uuidStr = (len, radix) => {
107
+ const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
108
+ const uuid = [];
109
+ let i;
110
+ radix = radix || chars.length;
111
+ if (len) {
112
+ // Compact form
113
+ for (i = 0; i < len; i++)
114
+ uuid[i] = chars[0 | (Math.random() * radix)];
115
+ }
116
+ else {
117
+ let r;
118
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-";
119
+ uuid[14] = "4";
120
+ for (i = 0; i < 36; i++) {
121
+ if (!uuid[i]) {
122
+ r = 0 | (Math.random() * 16);
123
+ uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
124
+ }
125
+ }
126
+ }
127
+ return uuid.join("");
128
+ };
@@ -0,0 +1,129 @@
1
+ # WorkRelay 语音合成(TTS)扩展能力 — 声音克隆配置说明书
2
+
3
+ > 适用:已安装 workrelay-speak 扩展能力(默认 Kokoro 预设声音可用)的用户,
4
+ > 需要**克隆自己的声音**(做视频配音、个性化语音等场景)时的进阶配置。
5
+
6
+ ---
7
+
8
+ ## 一、原理说明
9
+
10
+ - WorkRelay TTS 扩展能力基于 Docker 部署的 **Voicebox** 服务,默认使用 **Kokoro** 小模型(82M,50+ 预设音色,开箱即用)。
11
+ - **Kokoro 不支持声音克隆**(只支持预设音色)。
12
+ - **声音克隆需要 Qwen3-TTS(千问)模型**(1.7B,约 4.6GB),Voicebox 内置支持 zero-shot 克隆:
13
+ 上传一段参考音频 + 逐字稿 → 生成克隆声音 profile → 用该 profile 合成语音。
14
+
15
+ ---
16
+
17
+ ## 二、准备工作
18
+
19
+ | 项目 | 要求 |
20
+ |---|---|
21
+ | 已安装 workrelay-speak 扩展 | Voicebox 容器运行中(`docker ps` 能看到 voicebox) |
22
+ | 参考音频 | 1~5 段**清晰人声**(10~30 秒/段,44.1kHz 或 48kHz 更佳,无明显噪音) |
23
+ | 逐字稿 | 每段音频对应的**逐字文本**(必须和音频内容一字不差,克隆质量关键) |
24
+ | 磁盘空间 | ≥ 5GB(千问模型 4.6GB) |
25
+
26
+ > 音频格式支持 wav / mp3 / m4a / webm 等(opuse/webm 会自动转码)。
27
+
28
+ ---
29
+
30
+ ## 三、配置步骤
31
+
32
+ ### 第 1 步:下载千问 TTS 模型
33
+
34
+ > ⚠️ 容器内直连 huggingface.co 通常不通(国内网络),需在**宿主机**预下载后放入容器缓存卷。
35
+
36
+ ```bash
37
+ # 1) 安装下载工具(宿主机执行,一次性)
38
+ python3 -m pip install --break-system-packages huggingface_hub
39
+
40
+ # 2) 找到容器缓存卷路径
41
+ HF_VOL=$(docker volume inspect voicebox_huggingface-cache -f '{{.Mountpoint}}')
42
+
43
+ # 3) 下载千问 1.7B 模型(约 4.6GB,视网速 5~30 分钟)
44
+ HF_LOC=$(python3 -m pip show huggingface_hub | grep Location | awk '{print $2}')
45
+ sudo env PYTHONPATH="$HF_LOC" HF_HUB_DISABLE_PROGRESS_BARS=1 python3 -c "
46
+ from huggingface_hub import snapshot_download
47
+ snapshot_download('Qwen/Qwen3-TTS-12Hz-1.7B-Base', cache_dir='$HF_VOL')
48
+ print('模型下载完成')
49
+ "
50
+ ```
51
+
52
+ ### 第 2 步:让容器离线加载模型
53
+
54
+ ```bash
55
+ # 在 Voicebox 仓库目录生成 override 配置并重建容器
56
+ cd ~/.workrelay/voicebox
57
+ cat > docker-compose.override.yml <<'EOF'
58
+ services:
59
+ voicebox:
60
+ environment:
61
+ - HF_HUB_CACHE=/home/voicebox/.cache/huggingface
62
+ - HF_HUB_OFFLINE=1
63
+ EOF
64
+ docker compose up -d
65
+ # 等待容器 healthy(约 30 秒)
66
+ ```
67
+
68
+ ### 第 3 步:创建克隆声音 profile
69
+
70
+ ```bash
71
+ # 创建克隆 profile(cloned 类型)
72
+ curl -X POST http://127.0.0.1:17600/profiles \
73
+ -H "Content-Type: application/json" \
74
+ -d '{"name":"我的克隆声音","description":"我的声音克隆","language":"zh","voice_type":"cloned"}'
75
+ # 记下返回的 "id"(形如 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),下面用 <PROFILE_ID> 代替
76
+ ```
77
+
78
+ ### 第 4 步:上传参考音频样本
79
+
80
+ ```bash
81
+ # 每段音频一条命令;reference_text 必须与音频内容逐字一致!
82
+ curl -X POST "http://127.0.0.1:17600/profiles/<PROFILE_ID>/samples" \
83
+ -F "file=@/path/to/sample1.wav" \
84
+ -F "reference_text=这段音频里说的话,逐字写在这里"
85
+
86
+ # 建议上传 2~5 段同一个人不同语句的样本,克隆效果更好
87
+ ```
88
+
89
+ ### 第 5 步:用克隆声音合成
90
+
91
+ ```bash
92
+ # 方式 A:命令行直接合成(WR_VOICEBOX_PROFILE_ID 指向克隆 profile)
93
+ WR_VOICEBOX_PROFILE_ID=<PROFILE_ID> \
94
+ ~/.workrelay/speak/workrelay-tts.sh "用克隆声音说的测试文字" /tmp/clone-test.wav
95
+
96
+ # 方式 B:让 OpenClaw 默认用克隆声音回复(写入 profile_id 文件,覆盖默认预设)
97
+ echo "<PROFILE_ID>" > ~/.workrelay/speak/profile_id
98
+ ```
99
+
100
+ > 引擎无需手动指定:克隆 profile 未设引擎时,脚本自动使用 qwen(千问)。
101
+ > 想切回默认 Kokoro 预设声音:`rm -f ~/.workrelay/speak/profile_id` 即可。
102
+
103
+ ---
104
+
105
+ ## 四、验证
106
+
107
+ ```bash
108
+ # 检查克隆 profile 及样本
109
+ curl http://127.0.0.1:17600/profiles/<PROFILE_ID>
110
+ curl http://127.0.0.1:17600/profiles/<PROFILE_ID>/samples
111
+
112
+ # 试听合成的 wav
113
+ ls -la /tmp/clone-test.wav
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 五、常见问题
119
+
120
+ | 问题 | 原因 / 解决 |
121
+ |---|---|
122
+ | 合成报 `Profile not found` | profile_id 写错,或 profile 被删;重新创建并核对 |
123
+ | 合成报模型下载失败/连不上 HF | 模型未预下载或 override 未生效;重做第 1、2 步 |
124
+ | 克隆声音不像 | 样本太短/有噪音/逐字稿不匹配;换更清晰样本、多传几段 |
125
+ | 想恢复默认声音 | 删除 `~/.workrelay/speak/profile_id` |
126
+
127
+ ---
128
+
129
+ *文档版本:2026-08-12(基于 WorkRelay workrelay-speak 扩展能力实测)*
@@ -0,0 +1,84 @@
1
+ # WorkRelay 语音功能最低硬件配置
2
+
3
+ ## 一、语音转录(faster-whisper)
4
+
5
+ | 项目 | 最低要求 | 推荐配置 |
6
+ |------|----------|----------|
7
+ | **CPU** | 双核 2.0GHz+ | 四核 2.5GHz+ |
8
+ | **内存** | 4GB 可用 | 8GB+ |
9
+ | **磁盘** | 2.5GB 可用 | 5GB+ |
10
+ | **OS** | Linux / macOS | Ubuntu 22.04+ |
11
+ | **Python** | 3.8+ | 3.10+ |
12
+
13
+ **额外依赖:** `pip`, `git`(标准 Linux 发行版自带)
14
+
15
+ **实测数据(8核 31GB Linux CPU):**
16
+ - 首次加载模型:10-20 秒
17
+ - 10 秒语音转录:2-5 秒
18
+ - 30 秒语音转录:5-10 秒
19
+
20
+ > ⚠️ 不支持原生 Windows(需 WSL2)
21
+
22
+ ---
23
+
24
+ ## 二、语音合成 / 声音克隆(Voicebox TTS,Docker)
25
+
26
+ | 项目 | 最低要求 | 推荐配置 |
27
+ |------|----------|----------|
28
+ | **CPU** | 双核 2.0GHz+ | 四核 3.0GHz+ |
29
+ | **内存** | 4GB 可用 | 8GB+ |
30
+ | **磁盘** | 10GB 可用 | 15GB+ |
31
+ | **OS** | Linux(需 Docker) | Ubuntu 22.04+ |
32
+ | **Docker** | 24.0+ | 27.0+ |
33
+ | **GPU** | 无(CPU 推理) | NVIDIA CUDA(加速 5-10x) |
34
+
35
+ **额外依赖:** `docker`, `docker compose v2`, `git`
36
+
37
+ **Docker 资源(默认 Kokoro 引擎):**
38
+ - Voicebox 镜像:~11GB(含全部 TTS 引擎依赖)
39
+ - Kokoro 模型:~350MB(默认,安装时自动预下载)
40
+ - 运行内存:~2-3GB(Docker 内,Kokoro CPU 推理)
41
+
42
+ **实测数据(8核 31GB Linux CPU,Kokoro):**
43
+ - 短句(10 字):1-3 秒
44
+ - 中句(30 字):3-8 秒
45
+ - 长句(100 字):10-20 秒
46
+
47
+ > 默认使用 Kokoro(82MB)轻量引擎,开箱即用;
48
+ > 如需声音克隆或更高音质,可另配 Qwen3-TTS 1.7B(~4.6GB,内存需求 6GB+),详见 `CLONE_GUIDE.md`。
49
+
50
+ ---
51
+
52
+ ## 三、两个功能同时启用
53
+
54
+ | 项目 | 最低要求 | 推荐配置 |
55
+ |------|----------|----------|
56
+ | **CPU** | 四核 2.5GHz+ | 八核 3.0GHz+ |
57
+ | **内存** | 12GB 可用 | 16GB+(配置给 Docker 8GB) |
58
+ | **磁盘** | 15GB 可用 | 25GB+ |
59
+ | **GPU** | 无 | NVIDIA CUDA |
60
+
61
+ **内存分配建议(16GB 总内存场景):**
62
+ - faster-whisper 模型常驻:~2GB
63
+ - Voicebox Docker 容器:~6-8GB
64
+ - 剩余系统缓冲:~6-8GB
65
+
66
+ ---
67
+
68
+ ## 四、客户端提示文案建议
69
+
70
+ ### 转录
71
+ ```
72
+ 最低:2核 CPU + 4GB 内存 + 2.5GB 磁盘
73
+ 推荐:4核 CPU + 8GB 内存 + 5GB 磁盘
74
+ 不支持原生 Windows(需 WSL2)
75
+ 首次安装需下载 1.6GB 模型文件
76
+ ```
77
+
78
+ ### 语音合成
79
+ ```
80
+ 最低:2核 CPU + 4GB 内存 + 10GB 磁盘 + Docker
81
+ 推荐:4核 CPU + 8GB 内存 + 15GB 磁盘 + Docker
82
+ 首次安装需下载 Docker 镜像 + Kokoro 模型(~350MB)
83
+ 默认 Kokoro 轻量引擎,CPU 推理快,开箱即用
84
+ ```
@@ -0,0 +1,151 @@
1
+ # WorkRelay 语音合成(TTS)— 使用 Qwen3-TTS(千问)引擎说明书
2
+
3
+ > 适用:已安装 workrelay-speak 扩展能力,想把 TTS 引擎从默认 Kokoro 换成
4
+ > **Qwen3-TTS 1.7B**(千问)的用户。千问模型效果更好、支持声音克隆,
5
+ > 代价是模型大(~4.6GB)、CPU 合成较慢。
6
+
7
+ ---
8
+
9
+ ## 一、两个引擎怎么选
10
+
11
+ | 项目 | Kokoro(默认) | Qwen3-TTS 1.7B(千问,可选) |
12
+ |------|---------------|------------------------------|
13
+ | 模型大小 | 82M(~350MB 落盘) | 1.7B(~4.6GB 落盘) |
14
+ | CPU 合成速度 | 快(秒级) | 较慢(10~60 秒/句,视文本长度) |
15
+ | 音色 | 50+ 预设音色 | 预设音色(Vivian 中文女声等)+ **声音克隆** |
16
+ | 中英混读 | 支持(install.sh 自动打补丁) | 支持 |
17
+ | 适合场景 | 默认开箱即用 | 追求音质 / 需要克隆自己的声音 |
18
+
19
+ > 声音克隆的完整步骤见同目录 `CLONE_GUIDE.md`;本说明聚焦「把引擎切到千问」。
20
+
21
+ ---
22
+
23
+ ## 二、第 1 步:下载千问模型(宿主机执行,一次性)
24
+
25
+ > ⚠️ 容器内直连 huggingface.co 通常不通(国内网络),必须在**宿主机**预下载后放入容器缓存卷。
26
+
27
+ ```bash
28
+ # 1) 安装下载工具(没有则装)
29
+ python3 -m pip install --break-system-packages huggingface_hub
30
+
31
+ # 2) 找到容器缓存卷路径(compose 模式卷名带前缀;run 回退模式是裸卷,二选一)
32
+ HF_VOL=$(docker volume inspect voicebox_huggingface-cache -f '{{.Mountpoint}}' 2>/dev/null)
33
+ [ -z "$HF_VOL" ] && HF_VOL=$(docker volume inspect huggingface-cache -f '{{.Mountpoint}}' 2>/dev/null)
34
+
35
+ # 3) 下载千问 1.7B 模型(约 4.6GB,视网速 5~30 分钟)
36
+ HF_LOC=$(python3 -m pip show huggingface_hub | grep Location | awk '{print $2}')
37
+ sudo env PYTHONPATH="$HF_LOC" HF_HUB_DISABLE_PROGRESS_BARS=1 python3 -c "
38
+ from huggingface_hub import snapshot_download
39
+ snapshot_download('Qwen/Qwen3-TTS-12Hz-1.7B-Base', cache_dir='$HF_VOL')
40
+ print('模型下载完成')
41
+ "
42
+ ```
43
+
44
+ > 无 sudo 权限时:直接去掉 `sudo env`,用 `PYTHONPATH=... python3 -c "..."` 执行。
45
+
46
+ ---
47
+
48
+ ## 三、第 2 步:确认容器离线加载配置
49
+
50
+ install.sh 安装时(compose 模式)已自动生成 `docker-compose.override.yml`,
51
+ 包含离线加载环境变量。确认一下即可:
52
+
53
+ ```bash
54
+ # Voicebox 仓库目录:install.sh 输出的实际路径(默认 ~/.workrelay/voicebox,
55
+ # snap docker 环境为 /var/snap/docker/common/workrelay-voicebox)
56
+ cd ~/.workrelay/voicebox
57
+
58
+ # 查看 override(应含 HF_HUB_CACHE 与 HF_HUB_OFFLINE=1)
59
+ cat docker-compose.override.yml
60
+
61
+ # 若文件缺失或内容不对,手动写入并重建容器:
62
+ cat > docker-compose.override.yml <<'EOF'
63
+ services:
64
+ voicebox:
65
+ environment:
66
+ - HF_HUB_CACHE=/home/voicebox/.cache/huggingface
67
+ - HF_HUB_OFFLINE=1
68
+ EOF
69
+ docker compose up -d
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 四、第 3 步:选择 / 创建千问 profile
75
+
76
+ **方式 A:用已有千问预设音色(Vivian 中文女声)**
77
+
78
+ install.sh 已自动创建过「小朵中文女声」profile(`preset_engine=qwen_custom_voice`,`preset_voice_id=Vivian`)。
79
+ 查询确认并拿它的 id:
80
+
81
+ ```bash
82
+ curl -s http://127.0.0.1:17600/profiles | python3 -m json.tool | grep -B2 -A6 "qwen"
83
+ ```
84
+
85
+ **方式 B:新建一个千问 preset profile**
86
+
87
+ ```bash
88
+ curl -X POST http://127.0.0.1:17600/profiles \
89
+ -H "Content-Type: application/json" \
90
+ -d '{"name":"我的千问声音","description":"Qwen3-TTS 预设","language":"zh","voice_type":"preset","preset_engine":"qwen_custom_voice","preset_voice_id":"Vivian"}'
91
+ # 记下返回的 "id",下面用 <PROFILE_ID> 代替
92
+ ```
93
+
94
+ **方式 C:克隆自己的声音** → 走 `CLONE_GUIDE.md`(克隆出的 profile 引擎自动是千问)。
95
+
96
+ ---
97
+
98
+ ## 五、第 4 步:把千问设为默认引擎
99
+
100
+ ```bash
101
+ # 写入 profile_id 文件(覆盖默认 Kokoro),workrelay-tts.sh 会自动读取
102
+ echo "<PROFILE_ID>" > ~/.workrelay/speak/profile_id
103
+ ```
104
+
105
+ > 也可以不写文件、临时指定:`WR_VOICEBOX_PROFILE_ID=<PROFILE_ID> ~/.workrelay/speak/workrelay-tts.sh "文本" /tmp/out.wav`
106
+ >
107
+ > 引擎无需手动指定:脚本按 profile 的 `default_engine` 自动匹配(qwen profile 自动走千问)。
108
+
109
+ ---
110
+
111
+ ## 六、验证
112
+
113
+ ```bash
114
+ # 1) 确认 profile 存在
115
+ curl http://127.0.0.1:17600/profiles/<PROFILE_ID>
116
+
117
+ # 2) 命令行试合成(千问 CPU 较慢,耐心等)
118
+ WR_VOICEBOX_PROFILE_ID=<PROFILE_ID> \
119
+ ~/.workrelay/speak/workrelay-tts.sh "你好,我是小朵,今天用千问模型给你合成语音。" /tmp/qwen-test.wav
120
+
121
+ # 3) 听一下(或看文件大小/时长)
122
+ ls -la /tmp/qwen-test.wav
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 七、常见问题
128
+
129
+ | 问题 | 原因 / 解决 |
130
+ |------|------------|
131
+ | 合成报模型下载失败 / 连不上 HF | 模型未预下载或 override 未生效;重做第 1、2 步 |
132
+ | 合成很慢 | 千问 1.7B CPU 推理正常现象;短句更快,可调大 OpenClaw `timeoutMs`(默认 180s 够用) |
133
+ | 合成报 `Profile not found` | profile_id 写错或 profile 被删;重新创建并核对 |
134
+ | 重跑 install.sh 后声音变回 Kokoro | install.sh 每次安装会重置默认 profile_id;按第 4 步重新 `echo` 千问 id 即可 |
135
+ | 想切回 Kokoro 默认声音 | `rm -f ~/.workrelay/speak/profile_id` |
136
+ | 想克隆自己的声音 | 见 `CLONE_GUIDE.md` |
137
+
138
+ ---
139
+
140
+ ## 八、附:关键信息速查
141
+
142
+ | 项目 | 值 |
143
+ |------|-----|
144
+ | 千问模型 | `Qwen/Qwen3-TTS-12Hz-1.7B-Base`(~4.6GB) |
145
+ | Voicebox API | `http://127.0.0.1:17600`(compose 固定映射;run 回退模式可能为 17493,以 status.sh 输出 endpoint 为准) |
146
+ | 缓存卷 | `voicebox_huggingface-cache`(compose)/ `huggingface-cache`(run 回退) |
147
+ | 引擎自动匹配 | 脚本读 profile 的 `default_engine`(qwen_custom_voice → 千问) |
148
+
149
+ ---
150
+
151
+ *文档版本:2026-08-13(基于 WorkRelay workrelay-speak 扩展能力实测)*
@@ -0,0 +1,79 @@
1
+ # WorkRelay 语音合成(TTS)模块
2
+
3
+ Agent 语音回复能力:Docker 部署 **Voicebox**(默认 **Kokoro** 引擎,82M 轻量模型),
4
+ 50+ 预设音色,开箱即用,CPU 推理快。
5
+
6
+ ## 文件说明
7
+
8
+ | 文件 | 作用 |
9
+ |------|------|
10
+ | `workrelay-tts.sh` | TTS 桥接脚本(调 Voicebox Docker REST API) |
11
+ | `install.sh` | 一键安装(Docker → 克隆仓库 → 启动容器 → 模型预下载 → 配置 OpenClaw) |
12
+ | `status.sh` | 状态查询,输出 JSON,客户端判断"已启用/未启用" |
13
+ | `CLONE_GUIDE.md` | 声音克隆进阶配置说明书(可选,需千问 Qwen3-TTS 模型) |
14
+ | `QWEN_GUIDE.md` | 使用 Qwen3-TTS(千问)引擎说明书(含预设音色 Vivian + 切引擎步骤) |
15
+
16
+ ## 依赖
17
+
18
+ - **Docker** (docker + docker compose v2)
19
+ - **Voicebox** (`jamiepine/voicebox`) — 通过 Docker 部署
20
+ - TTS 引擎:**Kokoro**(默认,82M,CPU 推理,50+ 预设音色)
21
+ - 首次安装:下载 Docker 镜像 + Kokoro 模型(约 350MB)
22
+
23
+ > 声音克隆(可选):需另配 Qwen3-TTS 1.7B 模型(~4.6GB),详见 `CLONE_GUIDE.md`。
24
+
25
+ ## 客户端集成方案
26
+
27
+ ### 设置页面
28
+
29
+ ```
30
+ ┌─────────────────────────────────────┐
31
+ │ 🎙️ 语音合成(TTS) │
32
+ │ │
33
+ │ 状态: ● 已启用 / ○ 未启用 │
34
+ │ │
35
+ │ 引擎: Kokoro (预设 50+ 音色) │
36
+ │ 服务: Voicebox (Docker) │
37
+ │ │
38
+ │ [ 启用语音合成 ] ← 仅未启用时显示 │
39
+ └─────────────────────────────────────┘
40
+ ```
41
+
42
+ ### status.sh 返回格式
43
+
44
+ ```json
45
+ {
46
+ "status": "enabled",
47
+ "description": "语音合成已启用",
48
+ "endpoint": "http://127.0.0.1:17493",
49
+ "checks": {
50
+ "docker": true,
51
+ "container": true,
52
+ "api": true,
53
+ "tts_script": true,
54
+ "openclaw_config": true
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### OpenClaw 自动配置项
60
+
61
+ | 配置路径 | 值 |
62
+ |----------|-----|
63
+ | `messages.tts.auto` | `off`(手动触发) |
64
+ | `messages.tts.provider` | `tts-local-cli` |
65
+ | `messages.tts.providers.tts-local-cli.command` | `workrelay-tts.sh` |
66
+ | `messages.tts.providers.tts-local-cli.args` | `["{{Text}}","{{OutputPath}}"]` |
67
+ | `messages.tts.providers.tts-local-cli.outputFormat` | `wav` |
68
+ | `messages.tts.providers.tts-local-cli.timeoutMs` | `180000` |
69
+ | `mcp.servers.voicebox.url` | `http://127.0.0.1:17493/mcp` |
70
+
71
+ ## 环境变量(可选)
72
+
73
+ | 变量 | 说明 | 默认 |
74
+ |------|------|------|
75
+ | `WR_VOICEBOX_PROFILE_ID` | 指定声音 profile(预设或克隆,不设则自动解析) | 自动 |
76
+ | `WR_VOICEBOX_HOST` | Voicebox 地址 | 127.0.0.1 |
77
+ | `WR_VOICEBOX_PORT` | Voicebox 端口 | 17493 |
78
+ | `WR_VOICEBOX_ENGINE` | TTS 引擎(不设则按 profile 自动匹配) | 自动 |
79
+ | `WR_VOICEBOX_LANG` | 语言 | zh |