dlinkclaw-openclaw 2026.8.24-alpha.1
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.
- package/README.en.md +46 -0
- package/README.md +25 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1 -0
- package/dist/package.json +95 -0
- package/dist/src/account.d.ts +53 -0
- package/dist/src/account.js +394 -0
- package/dist/src/channel.d.ts +17 -0
- package/dist/src/channel.js +153 -0
- package/dist/src/client/client-manager.d.ts +4 -0
- package/dist/src/client/client-manager.js +10 -0
- package/dist/src/client/const.d.ts +37 -0
- package/dist/src/client/const.js +155 -0
- package/dist/src/client/crypto.d.ts +47 -0
- package/dist/src/client/crypto.js +260 -0
- package/dist/src/client/dl-client.d.ts +99 -0
- package/dist/src/client/dl-client.js +535 -0
- package/dist/src/client/dl-http.d.ts +27 -0
- package/dist/src/client/dl-http.js +223 -0
- package/dist/src/client/media-handler.d.ts +21 -0
- package/dist/src/client/media-handler.js +196 -0
- package/dist/src/client/message.d.ts +96 -0
- package/dist/src/client/message.js +1204 -0
- package/dist/src/client/timeout.d.ts +20 -0
- package/dist/src/client/timeout.js +36 -0
- package/dist/src/client/types.d.ts +224 -0
- package/dist/src/client/types.js +1 -0
- package/dist/src/monitor.d.ts +17 -0
- package/dist/src/monitor.js +320 -0
- package/dist/src/outbound.d.ts +4 -0
- package/dist/src/outbound.js +83 -0
- package/dist/src/policy.d.ts +42 -0
- package/dist/src/policy.js +205 -0
- package/dist/src/runtime.d.ts +9 -0
- package/dist/src/runtime.js +65 -0
- package/dist/src/services/index.d.ts +41 -0
- package/dist/src/services/index.js +225 -0
- package/dist/src/services/services.d.ts +152 -0
- package/dist/src/services/services.js +1066 -0
- package/dist/src/services/types.d.ts +391 -0
- package/dist/src/services/types.js +5 -0
- package/dist/src/services/ws-client.d.ts +174 -0
- package/dist/src/services/ws-client.js +866 -0
- package/dist/src/utils.d.ts +20 -0
- package/dist/src/utils.js +128 -0
- package/ext_capabilities/workrelay-speak/CLONE_GUIDE.md +129 -0
- package/ext_capabilities/workrelay-speak/HARDWARE.md +84 -0
- package/ext_capabilities/workrelay-speak/QWEN_GUIDE.md +151 -0
- package/ext_capabilities/workrelay-speak/README.md +79 -0
- package/ext_capabilities/workrelay-speak/__pycache__/patch-kokoro-en.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-speak/install.sh +280 -0
- package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
- package/ext_capabilities/workrelay-speak/manifest.json +49 -0
- package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
- package/ext_capabilities/workrelay-speak/status.sh +90 -0
- package/ext_capabilities/workrelay-speak/workrelay-tts.sh +118 -0
- package/ext_capabilities/workrelay-speak-luxtts/README.md +44 -0
- package/ext_capabilities/workrelay-speak-luxtts/install.sh +282 -0
- package/ext_capabilities/workrelay-speak-luxtts/manifest.json +49 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/reference_text.txt +4 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/stone-sample-6.4s.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/status.sh +94 -0
- package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
- package/ext_capabilities/workrelay-transcribe/README.md +76 -0
- package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe_server.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-transcribe/install.sh +207 -0
- package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
- package/ext_capabilities/workrelay-transcribe/status.sh +62 -0
- package/ext_capabilities/workrelay-transcribe/transcribe.py +56 -0
- package/ext_capabilities/workrelay-transcribe/transcribe_server.py +116 -0
- package/ext_capabilities/workrelay-transcribe/voicebox-transcribe.sh +88 -0
- package/openclaw.plugin.json +142 -0
- package/package.json +95 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(TTS)一键安装脚本
|
|
3
|
+
# 通过 Docker 部署 Voicebox(jamiepine/voicebox),自动配置 OpenClaw
|
|
4
|
+
set -eo pipefail
|
|
5
|
+
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
NC='\033[0m'
|
|
10
|
+
|
|
11
|
+
VB_DIR="$HOME/.workrelay/voicebox"
|
|
12
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
13
|
+
VB_PORT="${WR_VOICEBOX_PORT:-17493}"
|
|
14
|
+
VB_DOCKER_PORT="${WR_VOICEBOX_DOCKER_PORT:-17600}"
|
|
15
|
+
# 2026-08-14:改为直接拉取阿里云公开镜像(已含 Kokoro 模型 + 英文修复补丁),
|
|
16
|
+
# 不再本地 clone + patch + build。镜像由石头哥推送到阿里云个人版仓库,公开仓库拉取免登录。
|
|
17
|
+
VB_IMAGE="${WR_VOICEBOX_IMAGE:-crpi-3zj44dlldca7vawm.cn-hangzhou.personal.cr.aliyuncs.com/shiduo/workrelay-tts:20260813}"
|
|
18
|
+
INSTALL_DIR="$(dirname "$(readlink -f "$0")")"
|
|
19
|
+
|
|
20
|
+
echo "============================================"
|
|
21
|
+
echo " WorkRelay 语音合成(TTS)- 一键安装"
|
|
22
|
+
echo "============================================"
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
# ── 系统检查 ──
|
|
26
|
+
echo -n "[1/6] 检查系统环境... "
|
|
27
|
+
|
|
28
|
+
OS="$(uname -s)"
|
|
29
|
+
case "$OS" in
|
|
30
|
+
Linux) ;;
|
|
31
|
+
*)
|
|
32
|
+
echo -e "${RED}失败${NC}"
|
|
33
|
+
echo "错误:当前仅支持 Linux(TTS 需要 Docker)"
|
|
34
|
+
exit 1
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
# 2026-08-11 修复:snap 安装的 docker 位于 /snap/bin,spawn 环境的 PATH 不含该目录,
|
|
39
|
+
# 导致 command -v docker 误判"未找到"。必须在 docker 检测之前补全 PATH。
|
|
40
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
41
|
+
|
|
42
|
+
if ! command -v docker &>/dev/null; then
|
|
43
|
+
echo -e "${RED}失败${NC}"
|
|
44
|
+
echo "错误:未找到 Docker,请先安装 Docker"
|
|
45
|
+
echo " Ubuntu: sudo apt install docker.io docker-compose-v2"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
50
|
+
# 2026-08-18 增强:docker 权限多级自适应(尽量零 sudo,任何机器可用):
|
|
51
|
+
# ① 用户已在 docker 组(含会话未刷新)→ sg docker 切组执行
|
|
52
|
+
# ② 免密 sudo(sudo -n)可用 → sudo 包装
|
|
53
|
+
# ③ 都不行 → 给出一键修复指引,而不是干失败
|
|
54
|
+
if sg docker -c "docker info" &>/dev/null 2>&1; then
|
|
55
|
+
echo " ℹ 用户已加入 docker 组,通过 sg docker 执行(无需 sudo)"
|
|
56
|
+
docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
|
|
57
|
+
elif sudo -n docker info &>/dev/null 2>&1; then
|
|
58
|
+
echo " ⚠ 当前用户无 docker 组权限,检测到免密 sudo,自动使用 sudo 执行 docker"
|
|
59
|
+
docker() { sudo docker "$@"; }
|
|
60
|
+
else
|
|
61
|
+
echo -e "${RED}失败${NC}"
|
|
62
|
+
echo "错误:Docker 未运行或无权限。请先执行一次以下命令:"
|
|
63
|
+
echo ""
|
|
64
|
+
echo " ① 启动 Docker:sudo systemctl start docker"
|
|
65
|
+
echo " ② 把当前用户加入 docker 组(避免以后每次要密码):"
|
|
66
|
+
echo " sudo usermod -aG docker \$USER"
|
|
67
|
+
echo " ③ 重启 openclaw 服务(或重新登录会话),然后重新安装"
|
|
68
|
+
echo ""
|
|
69
|
+
echo " 备选:为当前用户配置免密 sudo(sudo visudo 添加:\$USER ALL=(ALL) NOPASSWD: ALL)"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo -e "${GREEN}OK${NC} ($(docker --version | head -1), $(uname -o))"
|
|
75
|
+
|
|
76
|
+
# 2026-08-13 方案A:snap 版 docker 的 compose 插件与 docker CLI 客户端受 AppArmor 隔离,
|
|
77
|
+
# 读不了 $HOME 下的文件(compose 报 permission denied、build 报 open Dockerfile: no such file),
|
|
78
|
+
# 只有 dockerd daemon 放行 $HOME(所以 docker run 正常)。检测到 snap docker 时,
|
|
79
|
+
# 项目目录改用 /var/snap/docker/common/(AppArmor 明确放行 rw),compose/build/run 全通。
|
|
80
|
+
if [ -d /var/snap/docker/common ]; then
|
|
81
|
+
VB_DIR="/var/snap/docker/common/workrelay-voicebox"
|
|
82
|
+
echo " ℹ 检测到 snap docker,项目目录使用 $VB_DIR(AppArmor 放行读写)"
|
|
83
|
+
# /var/snap/docker/common 属 root,当前用户无写权限:需 root 预创建项目目录并移交给当前用户,
|
|
84
|
+
# 后续 docker pull / docker run 挂载的 output 目录写入不再需要提权。
|
|
85
|
+
# 2026-08-18:无免密 sudo 时给出手动命令指引(不依赖 sudo 密码)。
|
|
86
|
+
if [ -d "$VB_DIR" ]; then
|
|
87
|
+
sudo -n chown -R "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
88
|
+
elif ! sudo -n mkdir -p "$VB_DIR" 2>/dev/null; then
|
|
89
|
+
echo -e "${RED}失败${NC}"
|
|
90
|
+
echo "错误:无法创建项目目录 $VB_DIR(需要 root 权限)。"
|
|
91
|
+
echo "请手动执行一次以下命令后重新安装:"
|
|
92
|
+
echo " sudo mkdir -p $VB_DIR && sudo chown \$USER:\$USER $VB_DIR"
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
sudo -n chown "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# ── 磁盘空间 ──
|
|
99
|
+
echo -n "[2/6] 检查磁盘空间... "
|
|
100
|
+
# 2026-08-13:snap 模式下 VB_DIR 在 /var/snap/docker/common(可能与 $HOME 不同分区),
|
|
101
|
+
# 磁盘检查以实际项目目录所在分区为准
|
|
102
|
+
DF_TARGET="$VB_DIR"; [ -d "$DF_TARGET" ] || DF_TARGET="$HOME"
|
|
103
|
+
AVAIL=$(df -BM "$DF_TARGET" | tail -1 | awk '{print $4}' | sed 's/M//')
|
|
104
|
+
if [ "$AVAIL" -lt 10000 ]; then
|
|
105
|
+
echo -e "${RED}不足${NC}"
|
|
106
|
+
echo "错误:可用空间 ${AVAIL}MB,至少需要 10GB(Docker 镜像 + 模型 ~6GB)"
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
echo -e "${GREEN}OK${NC} (可用 ${AVAIL}MB)"
|
|
110
|
+
|
|
111
|
+
# ── 内存检查 ──
|
|
112
|
+
echo -n "[3/6] 检查内存... "
|
|
113
|
+
RAM=$(free -m | awk '/^Mem:/{print $2}')
|
|
114
|
+
if [ "$RAM" -lt 6000 ]; then
|
|
115
|
+
echo -e "${RED}不足 (${RAM}MB)${NC}"
|
|
116
|
+
echo "错误:至少需要 6GB 内存,当前仅 ${RAM}MB"
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
echo -e "${GREEN}OK${NC} (${RAM}MB)"
|
|
120
|
+
|
|
121
|
+
# ── 端口检查 ──
|
|
122
|
+
echo -n "[4/6] 检查端口 $VB_PORT... "
|
|
123
|
+
if ss -tlnp 2>/dev/null | grep -q ":$VB_PORT "; then
|
|
124
|
+
echo -e "${YELLOW}已被占用${NC}"
|
|
125
|
+
echo "提示:端口 $VB_PORT 已被占用,将使用 $VB_DOCKER_PORT"
|
|
126
|
+
VB_ACTUAL_PORT=$VB_DOCKER_PORT
|
|
127
|
+
else
|
|
128
|
+
echo -e "${GREEN}空闲${NC}"
|
|
129
|
+
VB_ACTUAL_PORT=$VB_PORT
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 拉取 Voicebox 镜像 ──
|
|
133
|
+
echo -n "[5/6] 拉取 Voicebox 镜像... "
|
|
134
|
+
# 2026-08-14:镜像已由石头哥构建并推送到阿里云(含 Kokoro 模型 + 英文修复补丁),
|
|
135
|
+
# 直接 docker pull 即可,无需 clone 仓库 / patch / build。
|
|
136
|
+
mkdir -p "$VB_DIR"
|
|
137
|
+
if docker pull "$VB_IMAGE" > /tmp/wr-pull.log 2>&1; then
|
|
138
|
+
echo -e "${GREEN}OK${NC} ($(docker images --format '{{.Repository}}:{{.Tag}}' "$VB_IMAGE" | head -1))"
|
|
139
|
+
else
|
|
140
|
+
echo -e "${RED}失败${NC}"
|
|
141
|
+
tail -5 /tmp/wr-pull.log
|
|
142
|
+
echo "错误:拉取镜像失败,检查网络连接"
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# ── 启动容器 ──
|
|
147
|
+
echo "[6/6] 启动 Voicebox 容器..."
|
|
148
|
+
|
|
149
|
+
# 创建输出目录(容器内 voicebox 用户需要可写)
|
|
150
|
+
mkdir -p "$VB_DIR/output"
|
|
151
|
+
chmod 777 "$VB_DIR/output" 2>/dev/null || true
|
|
152
|
+
|
|
153
|
+
# 2026-08-14:镜像已内置 Kokoro 模型(/home/voicebox/.cache/huggingface)且 ENV 已含
|
|
154
|
+
# HF_HUB_OFFLINE=1 / HF_HUB_CACHE,直接 docker run 即可。
|
|
155
|
+
# ⚠ 注意:不能挂空卷到 /home/voicebox/.cache/huggingface 或 /app/data,
|
|
156
|
+
# 否则会遮蔽镜像内已内置的模型/数据(旧版 clone+build 方案才需要挂卷)。
|
|
157
|
+
docker rm -f voicebox >/dev/null 2>&1 || true
|
|
158
|
+
if ! docker run -d --name voicebox --restart unless-stopped \
|
|
159
|
+
-p "127.0.0.1:${VB_ACTUAL_PORT}:17493" \
|
|
160
|
+
-v "$VB_DIR/output:/app/data/generations" \
|
|
161
|
+
-e LOG_LEVEL=info \
|
|
162
|
+
-e NUMBA_CACHE_DIR=/tmp/numba_cache \
|
|
163
|
+
--cpus 4 --memory 8g \
|
|
164
|
+
"$VB_IMAGE" >/dev/null 2>&1; then
|
|
165
|
+
echo -e "${RED}失败${NC}"
|
|
166
|
+
echo "错误:docker run 失败,执行 docker logs voicebox 查看日志"
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
echo " ✅ docker run 启动成功"
|
|
170
|
+
|
|
171
|
+
# 等待 API 就绪
|
|
172
|
+
echo -n " 等待 Voicebox API 就绪..."
|
|
173
|
+
for i in $(seq 1 90); do
|
|
174
|
+
if curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1; then
|
|
175
|
+
echo -e " ${GREEN}OK${NC} (耗时 ${i} 次检查)"
|
|
176
|
+
break
|
|
177
|
+
fi
|
|
178
|
+
sleep 5
|
|
179
|
+
echo -n "."
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
if ! curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1; then
|
|
183
|
+
echo ""
|
|
184
|
+
echo -e "${RED}Voicebox 启动失败${NC}"
|
|
185
|
+
echo " 检查日志: docker logs voicebox"
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# ── 预设声音(安装后开箱即用)──
|
|
190
|
+
# 2026-08-14:模型已内置镜像,无需宿主机预下载/离线配置;只需创建预设声音 profile。
|
|
191
|
+
echo " ⚙ 配置预设声音..."
|
|
192
|
+
|
|
193
|
+
# 创建预设声音 profile(kokoro 中文女声 zf_xiaoxiao),id 写入 profile_id 供 workrelay-tts.sh 自动读取
|
|
194
|
+
mkdir -p "$HOME/.workrelay/speak"
|
|
195
|
+
PROFILE_FILE="$HOME/.workrelay/speak/profile_id"
|
|
196
|
+
# 先查已存在的同款 preset profile,避免重名 400
|
|
197
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 5 "http://${VB_HOST}:${VB_ACTUAL_PORT}/profiles" 2>/dev/null \
|
|
198
|
+
| python3 -c '
|
|
199
|
+
import json,sys
|
|
200
|
+
try:
|
|
201
|
+
d=json.load(sys.stdin)
|
|
202
|
+
for p in d:
|
|
203
|
+
if p.get("preset_engine")=="kokoro" and p.get("preset_voice_id")=="zf_xiaoxiao":
|
|
204
|
+
print(p.get("id","")); break
|
|
205
|
+
except Exception:
|
|
206
|
+
print("")
|
|
207
|
+
' 2>/dev/null)
|
|
208
|
+
if [ -z "$EXIST_ID" ]; then
|
|
209
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 10 -X POST "http://${VB_HOST}:${VB_ACTUAL_PORT}/profiles" \
|
|
210
|
+
-H "Content-Type: application/json" \
|
|
211
|
+
-d '{"name":"WorkRelay 默认声音","description":"WorkRelay TTS 预设中文女声","language":"zh","voice_type":"preset","preset_engine":"kokoro","preset_voice_id":"zf_xiaoxiao"}' \
|
|
212
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
213
|
+
fi
|
|
214
|
+
if [ -n "$EXIST_ID" ]; then
|
|
215
|
+
echo "$EXIST_ID" > "$PROFILE_FILE"
|
|
216
|
+
echo " ✅ 预设声音已就绪"
|
|
217
|
+
else
|
|
218
|
+
echo " ⚠ 预设声音创建失败,可手动设 WR_VOICEBOX_PROFILE_ID 指定声音"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# ── 部署脚本 & 配置 OpenClaw ──
|
|
222
|
+
echo -n "[收尾] 部署 TTS 桥接脚本 & 配置 OpenClaw... "
|
|
223
|
+
|
|
224
|
+
mkdir -p "$HOME/.workrelay/speak"
|
|
225
|
+
cp "$INSTALL_DIR/workrelay-tts.sh" "$HOME/.workrelay/speak/"
|
|
226
|
+
chmod +x "$HOME/.workrelay/speak/workrelay-tts.sh"
|
|
227
|
+
|
|
228
|
+
# 2026-08-12:随安装部署说明文档(README + 声音克隆说明书 + 千问引擎说明书),用户安装后即可查看
|
|
229
|
+
cp "$INSTALL_DIR/README.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
230
|
+
cp "$INSTALL_DIR/CLONE_GUIDE.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
231
|
+
cp "$INSTALL_DIR/QWEN_GUIDE.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
232
|
+
|
|
233
|
+
# 写入 OpenClaw TTS 配置
|
|
234
|
+
if command -v openclaw &>/dev/null; then
|
|
235
|
+
TTS_SCRIPT="$HOME/.workrelay/speak/workrelay-tts.sh"
|
|
236
|
+
openclaw config set messages.tts.auto off 2>/dev/null || true
|
|
237
|
+
openclaw config set messages.tts.provider tts-local-cli 2>/dev/null || true
|
|
238
|
+
openclaw config set messages.tts.providers.tts-local-cli.command "$TTS_SCRIPT" 2>/dev/null || true
|
|
239
|
+
openclaw config set messages.tts.providers.tts-local-cli.args '["{{Text}}","{{OutputPath}}"]' 2>/dev/null || true
|
|
240
|
+
openclaw config set messages.tts.providers.tts-local-cli.outputFormat wav 2>/dev/null || true
|
|
241
|
+
openclaw config set messages.tts.providers.tts-local-cli.timeoutMs 180000 2>/dev/null || true
|
|
242
|
+
|
|
243
|
+
# MCP 配置
|
|
244
|
+
openclaw config set mcp.servers.voicebox.url "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" 2>/dev/null || true
|
|
245
|
+
openclaw config set mcp.servers.voicebox.transport streamable-http 2>/dev/null || true
|
|
246
|
+
openclaw config set mcp.servers.voicebox.timeout 60 2>/dev/null || true
|
|
247
|
+
|
|
248
|
+
openclaw gateway restart 2>/dev/null || true
|
|
249
|
+
else
|
|
250
|
+
echo -e "${YELLOW}⚠ 未检测到 openclaw CLI,跳过自动配置${NC}"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
echo -e "${GREEN}OK${NC}"
|
|
254
|
+
echo ""
|
|
255
|
+
|
|
256
|
+
# ── 验证 ──
|
|
257
|
+
echo "============================================"
|
|
258
|
+
echo " 安装验证"
|
|
259
|
+
echo "============================================"
|
|
260
|
+
|
|
261
|
+
echo -n " Docker 容器: "
|
|
262
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q voicebox && echo -e "${GREEN}运行中${NC}" || echo -e "${RED}未运行${NC}"
|
|
263
|
+
|
|
264
|
+
echo -n " API 连通性: "
|
|
265
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1 \
|
|
266
|
+
&& echo -e "${GREEN}OK${NC} (http://${VB_HOST}:${VB_ACTUAL_PORT})" \
|
|
267
|
+
|| echo -e "${RED}无法连接${NC}"
|
|
268
|
+
|
|
269
|
+
echo -n " TTS 桥接脚本: "
|
|
270
|
+
[ -f "$HOME/.workrelay/speak/workrelay-tts.sh" ] && echo -e "${GREEN}已部署${NC}" || echo -e "${RED}缺失${NC}"
|
|
271
|
+
|
|
272
|
+
echo ""
|
|
273
|
+
echo -e "${GREEN}✅ Voicebox TTS 安装完成!${NC}"
|
|
274
|
+
echo " API 地址: http://${VB_HOST}:${VB_ACTUAL_PORT}"
|
|
275
|
+
echo " 管理命令: docker ps | grep voicebox"
|
|
276
|
+
echo " 查看日志: docker logs -f voicebox"
|
|
277
|
+
echo ""
|
|
278
|
+
echo " 🎤 预设声音:开箱即用(Voicebox 内置 50+ 音色)"
|
|
279
|
+
echo " 🎙️ 声音克隆:可选,设 WR_VOICEBOX_PROFILE_ID=你的profile_id"
|
|
280
|
+
echo ""
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(TTS)一键安装脚本
|
|
3
|
+
# 通过 Docker 部署 Voicebox(jamiepine/voicebox),自动配置 OpenClaw
|
|
4
|
+
set -eo pipefail
|
|
5
|
+
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
NC='\033[0m'
|
|
10
|
+
|
|
11
|
+
VB_DIR="$HOME/.workrelay/voicebox"
|
|
12
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
13
|
+
VB_PORT="${WR_VOICEBOX_PORT:-17493}"
|
|
14
|
+
VB_DOCKER_PORT="${WR_VOICEBOX_DOCKER_PORT:-17600}"
|
|
15
|
+
INSTALL_DIR="$(dirname "$(readlink -f "$0")")"
|
|
16
|
+
|
|
17
|
+
echo "============================================"
|
|
18
|
+
echo " WorkRelay 语音合成(TTS)- 一键安装"
|
|
19
|
+
echo "============================================"
|
|
20
|
+
echo ""
|
|
21
|
+
|
|
22
|
+
# ── 系统检查 ──
|
|
23
|
+
echo -n "[1/7] 检查系统环境... "
|
|
24
|
+
|
|
25
|
+
OS="$(uname -s)"
|
|
26
|
+
case "$OS" in
|
|
27
|
+
Linux) ;;
|
|
28
|
+
*)
|
|
29
|
+
echo -e "${RED}失败${NC}"
|
|
30
|
+
echo "错误:当前仅支持 Linux(TTS 需要 Docker)"
|
|
31
|
+
exit 1
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# 2026-08-11 修复:snap 安装的 docker 位于 /snap/bin,spawn 环境的 PATH 不含该目录,
|
|
36
|
+
# 导致 command -v docker 误判"未找到"。必须在 docker 检测之前补全 PATH。
|
|
37
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
38
|
+
|
|
39
|
+
if ! command -v docker &>/dev/null; then
|
|
40
|
+
echo -e "${RED}失败${NC}"
|
|
41
|
+
echo "错误:未找到 Docker,请先安装 Docker"
|
|
42
|
+
echo " Ubuntu: sudo apt install docker.io docker-compose-v2"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
47
|
+
# 2026-08-11 修复:docker 权限自适应。当前用户不在 docker 组时(docker.sock 属 root:root),
|
|
48
|
+
# 若检测到免密 sudo(sudo -n)可执行 docker,则用函数包装 docker 命令,脚本后续所有
|
|
49
|
+
# docker 调用(compose up / ps 等)自动走 sudo,无需用户手动加组/重新登录。
|
|
50
|
+
if sudo -n docker info &>/dev/null 2>&1; then
|
|
51
|
+
echo " ⚠ 当前用户无 docker 组权限,检测到免密 sudo,自动使用 sudo 执行 docker"
|
|
52
|
+
docker() { sudo docker "$@"; }
|
|
53
|
+
else
|
|
54
|
+
echo -e "${RED}失败${NC}"
|
|
55
|
+
echo "错误:Docker 未运行或无权限,请执行:"
|
|
56
|
+
echo " sudo systemctl start docker"
|
|
57
|
+
echo " sudo usermod -aG docker \$USER (需要重新登录)"
|
|
58
|
+
echo " 或为当前用户配置免密 sudo 后重试"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
echo -e "${GREEN}OK${NC} ($(docker --version | head -1), $(uname -o))"
|
|
64
|
+
|
|
65
|
+
# 2026-08-13 方案A:snap 版 docker 的 compose 插件与 docker CLI 客户端受 AppArmor 隔离,
|
|
66
|
+
# 读不了 $HOME 下的文件(compose 报 permission denied、build 报 open Dockerfile: no such file),
|
|
67
|
+
# 只有 dockerd daemon 放行 $HOME(所以 docker run 正常)。检测到 snap docker 时,
|
|
68
|
+
# 项目目录改用 /var/snap/docker/common/(AppArmor 明确放行 rw),compose/build/run 全通。
|
|
69
|
+
if [ -d /var/snap/docker/common ]; then
|
|
70
|
+
VB_DIR="/var/snap/docker/common/workrelay-voicebox"
|
|
71
|
+
echo " ℹ 检测到 snap docker,项目目录使用 $VB_DIR(AppArmor 放行读写)"
|
|
72
|
+
# /var/snap/docker/common 属 root,当前用户无写权限:需 sudo 预创建项目目录并移交给当前用户,
|
|
73
|
+
# 后续 git clone / compose 写文件(override/日志)不再需要提权
|
|
74
|
+
if [ -d "$VB_DIR" ]; then
|
|
75
|
+
sudo -n chown -R "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
76
|
+
elif ! sudo -n mkdir -p "$VB_DIR" 2>/dev/null; then
|
|
77
|
+
echo -e "${RED}失败${NC}"
|
|
78
|
+
echo "错误:无法创建 $VB_DIR(需要免密 sudo,或手动执行: sudo mkdir -p $VB_DIR && sudo chown \$USER:\$USER $VB_DIR)"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
sudo -n chown "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── 磁盘空间 ──
|
|
85
|
+
echo -n "[2/7] 检查磁盘空间... "
|
|
86
|
+
# 2026-08-13:snap 模式下 VB_DIR 在 /var/snap/docker/common(可能与 $HOME 不同分区),
|
|
87
|
+
# 磁盘检查以实际项目目录所在分区为准
|
|
88
|
+
DF_TARGET="$VB_DIR"; [ -d "$DF_TARGET" ] || DF_TARGET="$HOME"
|
|
89
|
+
AVAIL=$(df -BM "$DF_TARGET" | tail -1 | awk '{print $4}' | sed 's/M//')
|
|
90
|
+
if [ "$AVAIL" -lt 10000 ]; then
|
|
91
|
+
echo -e "${RED}不足${NC}"
|
|
92
|
+
echo "错误:可用空间 ${AVAIL}MB,至少需要 10GB(Docker 镜像 + 模型 ~6GB)"
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
echo -e "${GREEN}OK${NC} (可用 ${AVAIL}MB)"
|
|
96
|
+
|
|
97
|
+
# ── 内存检查 ──
|
|
98
|
+
echo -n "[3/7] 检查内存... "
|
|
99
|
+
RAM=$(free -m | awk '/^Mem:/{print $2}')
|
|
100
|
+
if [ "$RAM" -lt 6000 ]; then
|
|
101
|
+
echo -e "${RED}不足 (${RAM}MB)${NC}"
|
|
102
|
+
echo "错误:至少需要 6GB 内存,当前仅 ${RAM}MB"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
echo -e "${GREEN}OK${NC} (${RAM}MB)"
|
|
106
|
+
|
|
107
|
+
# ── 端口检查 ──
|
|
108
|
+
echo -n "[4/7] 检查端口 $VB_PORT... "
|
|
109
|
+
if ss -tlnp 2>/dev/null | grep -q ":$VB_PORT "; then
|
|
110
|
+
echo -e "${YELLOW}已被占用${NC}"
|
|
111
|
+
echo "提示:端口 $VB_PORT 已被占用,将使用 $VB_DOCKER_PORT"
|
|
112
|
+
VB_ACTUAL_PORT=$VB_DOCKER_PORT
|
|
113
|
+
else
|
|
114
|
+
echo -e "${GREEN}空闲${NC}"
|
|
115
|
+
VB_ACTUAL_PORT=$VB_PORT
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# ── 克隆 Voicebox 仓库 ──
|
|
119
|
+
echo -n "[5/7] 克隆 Voicebox 仓库... "
|
|
120
|
+
# 2026-08-13:snap 模式下 VB_DIR 可能已被 [1/7] 预创建为空目录,
|
|
121
|
+
# 空目录不能算"已存在",否则会跳过 clone 导致后面找不到 Dockerfile
|
|
122
|
+
if [ -d "$VB_DIR" ] && [ -n "$(ls -A "$VB_DIR" 2>/dev/null)" ]; then
|
|
123
|
+
echo "已存在,更新..."
|
|
124
|
+
cd "$VB_DIR" && git pull --quiet 2>/dev/null || true
|
|
125
|
+
else
|
|
126
|
+
mkdir -p "$VB_DIR"
|
|
127
|
+
git clone --quiet https://github.com/jamiepine/voicebox.git "$VB_DIR" 2>&1 || {
|
|
128
|
+
echo -e "${RED}失败${NC}"
|
|
129
|
+
echo "错误:克隆仓库失败,检查网络连接"
|
|
130
|
+
exit 1
|
|
131
|
+
}
|
|
132
|
+
fi
|
|
133
|
+
cd "$VB_DIR"
|
|
134
|
+
|
|
135
|
+
# 2026-08-13:Kokoro 中文 pipeline 英文朗读修复(英文被逐字母日式读出,如 Hello -> L.O.H.O.R.Y.O.)。
|
|
136
|
+
# clone 后自动 patch 源码,build 出的镜像自带修复——新用户启用 TTS 一步到位,无需手动操作。
|
|
137
|
+
if [ -f "$INSTALL_DIR/patch-kokoro-en.py" ]; then
|
|
138
|
+
python3 "$INSTALL_DIR/patch-kokoro-en.py" "$VB_DIR/backend/backends/kokoro_backend.py" \
|
|
139
|
+
|| echo -e "${YELLOW} ⚠ Kokoro 英文修复 patch 失败(不影响安装,英文可能仍逐字母读)${NC}"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo -e "${GREEN}OK${NC}"
|
|
143
|
+
|
|
144
|
+
# ── 启动 Docker ──
|
|
145
|
+
echo "[6/7] 启动 Voicebox 服务(首次需拉取镜像+下载模型,约5-15分钟)..."
|
|
146
|
+
|
|
147
|
+
# 创建目录
|
|
148
|
+
mkdir -p "$VB_DIR/output"
|
|
149
|
+
|
|
150
|
+
# 2026-08-12 修复:
|
|
151
|
+
# 1) snap 版 docker 的 compose 插件受 AppArmor 隔离,无法读取 $HOME/.workrelay/ 下的
|
|
152
|
+
# compose 文件(报 "open ...: permission denied"),但 docker build/docker run 正常;
|
|
153
|
+
# 故 compose 失败时自动回退到 docker build + docker run。
|
|
154
|
+
# 2) 原 `2>&1 | tail -5` 会吞掉 compose 的退出码(set -e 不覆盖管道),脚本误以为成功、
|
|
155
|
+
# 空等 7.5 分钟 API;现在把完整日志写入文件并按真实退出码判断(配合顶部 set -o pipefail)。
|
|
156
|
+
COMPOSE_LOG="$VB_DIR/compose-up.log"
|
|
157
|
+
VB_MODE="compose" # compose | run(回退模式)
|
|
158
|
+
# 2026-08-13:-p voicebox 固定 compose 项目名——项目目录名变化(voicebox → workrelay-voicebox)
|
|
159
|
+
# 时卷名保持 voicebox_huggingface-cache 不变,可复用旧模型缓存卷
|
|
160
|
+
if docker compose -p voicebox up -d --build >"$COMPOSE_LOG" 2>&1; then
|
|
161
|
+
echo " ✅ docker compose 启动成功"
|
|
162
|
+
# compose 文件固定映射 127.0.0.1:17600:17493,API 检查必须用 17600
|
|
163
|
+
VB_ACTUAL_PORT=$VB_DOCKER_PORT
|
|
164
|
+
else
|
|
165
|
+
echo -e "${YELLOW} ⚠ docker compose 启动失败,回退到 docker build + docker run${NC}"
|
|
166
|
+
tail -15 "$COMPOSE_LOG"
|
|
167
|
+
VB_MODE="run"
|
|
168
|
+
docker rm -f voicebox >/dev/null 2>&1 || true
|
|
169
|
+
echo " 正在 docker build(首次需拉取基础镜像,约几分钟)..."
|
|
170
|
+
if ! docker build -t voicebox:local "$VB_DIR" >"$COMPOSE_LOG.build" 2>&1; then
|
|
171
|
+
echo -e "${RED}失败${NC}"
|
|
172
|
+
echo "错误:docker build 失败,日志见 $COMPOSE_LOG.build:"
|
|
173
|
+
tail -20 "$COMPOSE_LOG.build"
|
|
174
|
+
exit 1
|
|
175
|
+
fi
|
|
176
|
+
echo " 正在 docker run ..."
|
|
177
|
+
# 参数与 docker-compose.yml 保持一致(端口映射用 VB_ACTUAL_PORT,与 API 检查对齐)
|
|
178
|
+
if ! docker run -d --name voicebox --restart unless-stopped \
|
|
179
|
+
-p "127.0.0.1:${VB_ACTUAL_PORT}:17493" \
|
|
180
|
+
-v "$VB_DIR/output:/app/data/generations" \
|
|
181
|
+
-v voicebox-data:/app/data \
|
|
182
|
+
-v huggingface-cache:/home/voicebox/.cache/huggingface \
|
|
183
|
+
-e LOG_LEVEL=info \
|
|
184
|
+
-e NUMBA_CACHE_DIR=/tmp/numba_cache \
|
|
185
|
+
--cpus 4 --memory 8g \
|
|
186
|
+
voicebox:local >>"$COMPOSE_LOG" 2>&1; then
|
|
187
|
+
echo -e "${RED}失败${NC}"
|
|
188
|
+
echo "错误:docker run 失败,日志见 $COMPOSE_LOG:"
|
|
189
|
+
tail -20 "$COMPOSE_LOG"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
echo " ✅ docker run 启动成功(回退模式)"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# 等待 API 就绪
|
|
196
|
+
echo -n " 等待 Voicebox API 就绪..."
|
|
197
|
+
for i in $(seq 1 90); do
|
|
198
|
+
if curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1; then
|
|
199
|
+
echo -e " ${GREEN}OK${NC} (耗时 ${i} 次检查)"
|
|
200
|
+
break
|
|
201
|
+
fi
|
|
202
|
+
sleep 5
|
|
203
|
+
echo -n "."
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
if ! curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1; then
|
|
207
|
+
echo ""
|
|
208
|
+
echo -e "${RED}Voicebox 启动失败${NC}"
|
|
209
|
+
if [ "$VB_MODE" = "compose" ]; then
|
|
210
|
+
echo " 检查日志: docker compose -p voicebox -f $VB_DIR/docker-compose.yml logs voicebox"
|
|
211
|
+
else
|
|
212
|
+
echo " 检查日志: docker logs voicebox"
|
|
213
|
+
fi
|
|
214
|
+
exit 1
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# ── 2026-08-12:语音模型预下载 + 预设声音(安装后开箱即用)──
|
|
218
|
+
# 背景:容器内直连 huggingface.co 不通(DNS 被污染),hf-mirror 也未缓存相关模型,
|
|
219
|
+
# 因此模型必须在宿主机下载后放入 huggingface 缓存卷,并让容器离线加载(HF_HUB_OFFLINE=1)。
|
|
220
|
+
echo " ⚙ 准备语音模型与预设声音(首次下载约 1-3 分钟)..."
|
|
221
|
+
|
|
222
|
+
# 1) output 目录权限:容器内 voicebox 用户需要可写(宿主机目录属主不匹配)
|
|
223
|
+
chmod 777 "$VB_DIR/output" 2>/dev/null || true
|
|
224
|
+
|
|
225
|
+
# 2) 宿主机预下载 kokoro 模型(82M,含中文声音,~350MB 落盘)到缓存卷
|
|
226
|
+
if python3 -c "import huggingface_hub" >/dev/null 2>&1; then
|
|
227
|
+
HF_LOC=$(python3 -m pip show huggingface_hub 2>/dev/null | grep Location | awk '{print $2}')
|
|
228
|
+
# 2026-08-13:compose 模式卷名带项目前缀(voicebox_huggingface-cache),
|
|
229
|
+
# run 回退模式用裸卷名(huggingface-cache),双候选兜底
|
|
230
|
+
HF_VOL=$(docker volume inspect voicebox_huggingface-cache -f '{{.Mountpoint}}' 2>/dev/null)
|
|
231
|
+
[ -z "$HF_VOL" ] && HF_VOL=$(docker volume inspect huggingface-cache -f '{{.Mountpoint}}' 2>/dev/null)
|
|
232
|
+
if [ -n "$HF_VOL" ]; then
|
|
233
|
+
echo -n " 下载语音模型 (Kokoro-82M, ~350MB)... "
|
|
234
|
+
if sudo -n env PYTHONPATH="$HF_LOC" HF_HUB_DISABLE_PROGRESS_BARS=1 python3 -c "
|
|
235
|
+
from huggingface_hub import snapshot_download
|
|
236
|
+
snapshot_download('hexgrad/Kokoro-82M', cache_dir='$HF_VOL')
|
|
237
|
+
print('done')
|
|
238
|
+
" >/tmp/wr-model-dl.log 2>&1; then
|
|
239
|
+
echo -e "${GREEN}OK${NC}"
|
|
240
|
+
else
|
|
241
|
+
echo -e "${YELLOW}失败${NC}(详见 /tmp/wr-model-dl.log,可稍后手动下载)"
|
|
242
|
+
fi
|
|
243
|
+
else
|
|
244
|
+
echo " ⚠ 未找到 huggingface 缓存卷,跳过模型预下载(合成时可能联网超时)"
|
|
245
|
+
fi
|
|
246
|
+
else
|
|
247
|
+
echo " ⚠ 宿主机无 huggingface_hub,跳过模型预下载(可用 pip3 install huggingface_hub 后重跑)"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# 3) 容器离线加载配置
|
|
251
|
+
# compose 模式:生成 override 文件(HF_HUB_CACHE 指向挂载的缓存卷 + 离线模式),重建容器生效
|
|
252
|
+
# run 模式:直接重建容器并注入环境变量
|
|
253
|
+
if [ "$VB_MODE" = "compose" ]; then
|
|
254
|
+
cat > "$VB_DIR/docker-compose.override.yml" <<'WR_EOF'
|
|
255
|
+
services:
|
|
256
|
+
voicebox:
|
|
257
|
+
environment:
|
|
258
|
+
- HF_HUB_CACHE=/home/voicebox/.cache/huggingface
|
|
259
|
+
- HF_HUB_OFFLINE=1
|
|
260
|
+
WR_EOF
|
|
261
|
+
docker compose -p voicebox up -d >/dev/null 2>&1 || true
|
|
262
|
+
else
|
|
263
|
+
docker rm -f voicebox >/dev/null 2>&1 || true
|
|
264
|
+
docker run -d --name voicebox --restart unless-stopped \
|
|
265
|
+
-p "127.0.0.1:${VB_ACTUAL_PORT}:17493" \
|
|
266
|
+
-v "$VB_DIR/output:/app/data/generations" \
|
|
267
|
+
-v voicebox-data:/app/data \
|
|
268
|
+
-v huggingface-cache:/home/voicebox/.cache/huggingface \
|
|
269
|
+
-e LOG_LEVEL=info \
|
|
270
|
+
-e NUMBA_CACHE_DIR=/tmp/numba_cache \
|
|
271
|
+
-e HF_HUB_CACHE=/home/voicebox/.cache/huggingface \
|
|
272
|
+
-e HF_HUB_OFFLINE=1 \
|
|
273
|
+
--cpus 4 --memory 8g \
|
|
274
|
+
voicebox:local >/dev/null 2>&1 || true
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# 重建后等 API 重新就绪(容器重启)
|
|
278
|
+
for i in $(seq 1 30); do
|
|
279
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1 && break
|
|
280
|
+
sleep 3
|
|
281
|
+
echo -n "."
|
|
282
|
+
done
|
|
283
|
+
|
|
284
|
+
# 4) 创建预设声音 profile(kokoro 中文女声 zf_xiaoxiao),id 写入 profile_id 供 workrelay-tts.sh 自动读取
|
|
285
|
+
mkdir -p "$HOME/.workrelay/speak"
|
|
286
|
+
PROFILE_FILE="$HOME/.workrelay/speak/profile_id"
|
|
287
|
+
# 先查已存在的同款 preset profile,避免重名 400
|
|
288
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 5 "http://${VB_HOST}:${VB_ACTUAL_PORT}/profiles" 2>/dev/null \
|
|
289
|
+
| python3 -c '
|
|
290
|
+
import json,sys
|
|
291
|
+
try:
|
|
292
|
+
d=json.load(sys.stdin)
|
|
293
|
+
for p in d:
|
|
294
|
+
if p.get("preset_engine")=="kokoro" and p.get("preset_voice_id")=="zf_xiaoxiao":
|
|
295
|
+
print(p.get("id","")); break
|
|
296
|
+
except Exception:
|
|
297
|
+
print("")
|
|
298
|
+
' 2>/dev/null)
|
|
299
|
+
if [ -z "$EXIST_ID" ]; then
|
|
300
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 10 -X POST "http://${VB_HOST}:${VB_ACTUAL_PORT}/profiles" \
|
|
301
|
+
-H "Content-Type: application/json" \
|
|
302
|
+
-d '{"name":"WorkRelay 默认声音","description":"WorkRelay TTS 预设中文女声","language":"zh","voice_type":"preset","preset_engine":"kokoro","preset_voice_id":"zf_xiaoxiao"}' \
|
|
303
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
304
|
+
fi
|
|
305
|
+
if [ -n "$EXIST_ID" ]; then
|
|
306
|
+
echo "$EXIST_ID" > "$PROFILE_FILE"
|
|
307
|
+
echo " ✅ 预设声音已就绪"
|
|
308
|
+
else
|
|
309
|
+
echo " ⚠ 预设声音创建失败,可手动设 WR_VOICEBOX_PROFILE_ID 指定声音"
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
# ── 部署脚本 & 配置 OpenClaw ──
|
|
313
|
+
echo -n "[7/7] 部署 TTS 桥接脚本 & 配置 OpenClaw... "
|
|
314
|
+
|
|
315
|
+
mkdir -p "$HOME/.workrelay/speak"
|
|
316
|
+
cp "$INSTALL_DIR/workrelay-tts.sh" "$HOME/.workrelay/speak/"
|
|
317
|
+
chmod +x "$HOME/.workrelay/speak/workrelay-tts.sh"
|
|
318
|
+
|
|
319
|
+
# 2026-08-12:随安装部署说明文档(README + 声音克隆说明书 + 千问引擎说明书),用户安装后即可查看
|
|
320
|
+
cp "$INSTALL_DIR/README.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
321
|
+
cp "$INSTALL_DIR/CLONE_GUIDE.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
322
|
+
cp "$INSTALL_DIR/QWEN_GUIDE.md" "$HOME/.workrelay/speak/" 2>/dev/null || true
|
|
323
|
+
|
|
324
|
+
# 写入 OpenClaw TTS 配置
|
|
325
|
+
if command -v openclaw &>/dev/null; then
|
|
326
|
+
TTS_SCRIPT="$HOME/.workrelay/speak/workrelay-tts.sh"
|
|
327
|
+
openclaw config set messages.tts.auto off 2>/dev/null || true
|
|
328
|
+
openclaw config set messages.tts.provider tts-local-cli 2>/dev/null || true
|
|
329
|
+
openclaw config set messages.tts.providers.tts-local-cli.command "$TTS_SCRIPT" 2>/dev/null || true
|
|
330
|
+
openclaw config set messages.tts.providers.tts-local-cli.args '["{{Text}}","{{OutputPath}}"]' 2>/dev/null || true
|
|
331
|
+
openclaw config set messages.tts.providers.tts-local-cli.outputFormat wav 2>/dev/null || true
|
|
332
|
+
openclaw config set messages.tts.providers.tts-local-cli.timeoutMs 180000 2>/dev/null || true
|
|
333
|
+
|
|
334
|
+
# MCP 配置
|
|
335
|
+
openclaw config set mcp.servers.voicebox.url "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" 2>/dev/null || true
|
|
336
|
+
openclaw config set mcp.servers.voicebox.transport streamable-http 2>/dev/null || true
|
|
337
|
+
openclaw config set mcp.servers.voicebox.timeout 60 2>/dev/null || true
|
|
338
|
+
|
|
339
|
+
openclaw gateway restart 2>/dev/null || true
|
|
340
|
+
else
|
|
341
|
+
echo -e "${YELLOW}⚠ 未检测到 openclaw CLI,跳过自动配置${NC}"
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
echo -e "${GREEN}OK${NC}"
|
|
345
|
+
echo ""
|
|
346
|
+
|
|
347
|
+
# ── 验证 ──
|
|
348
|
+
echo "============================================"
|
|
349
|
+
echo " 安装验证"
|
|
350
|
+
echo "============================================"
|
|
351
|
+
|
|
352
|
+
echo -n " Docker 容器: "
|
|
353
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q voicebox && echo -e "${GREEN}运行中${NC}" || echo -e "${RED}未运行${NC}"
|
|
354
|
+
|
|
355
|
+
echo -n " API 连通性: "
|
|
356
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_ACTUAL_PORT}/mcp" >/dev/null 2>&1 \
|
|
357
|
+
&& echo -e "${GREEN}OK${NC} (http://${VB_HOST}:${VB_ACTUAL_PORT})" \
|
|
358
|
+
|| echo -e "${RED}无法连接${NC}"
|
|
359
|
+
|
|
360
|
+
echo -n " TTS 桥接脚本: "
|
|
361
|
+
[ -f "$HOME/.workrelay/speak/workrelay-tts.sh" ] && echo -e "${GREEN}已部署${NC}" || echo -e "${RED}缺失${NC}"
|
|
362
|
+
|
|
363
|
+
echo ""
|
|
364
|
+
echo -e "${GREEN}✅ Voicebox TTS 安装完成!${NC}"
|
|
365
|
+
echo " API 地址: http://${VB_HOST}:${VB_ACTUAL_PORT}"
|
|
366
|
+
if [ "$VB_MODE" = "compose" ]; then
|
|
367
|
+
echo " 管理命令: cd $VB_DIR && docker compose -p voicebox ps"
|
|
368
|
+
echo " 查看日志: cd $VB_DIR && docker compose -p voicebox logs -f voicebox"
|
|
369
|
+
else
|
|
370
|
+
echo " 管理命令: docker ps | grep voicebox"
|
|
371
|
+
echo " 查看日志: docker logs -f voicebox"
|
|
372
|
+
fi
|
|
373
|
+
echo ""
|
|
374
|
+
echo " 🎤 预设声音:开箱即用(Voicebox 内置 50+ 音色)"
|
|
375
|
+
echo " 🎙️ 声音克隆:可选,设 WR_VOICEBOX_PROFILE_ID=你的profile_id"
|
|
376
|
+
echo ""
|