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.
- package/LICENSE +201 -0
- package/README.en.md +46 -0
- package/README.md +15 -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 +54 -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 +117 -0
- package/dist/src/client/dl-client.js +588 -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 +1211 -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 +226 -0
- package/dist/src/client/types.js +1 -0
- package/dist/src/monitor.d.ts +18 -0
- package/dist/src/monitor.js +322 -0
- package/dist/src/outbound.d.ts +6 -0
- package/dist/src/outbound.js +78 -0
- package/dist/src/policy.d.ts +42 -0
- package/dist/src/policy.js +216 -0
- package/dist/src/runtime.d.ts +9 -0
- package/dist/src/runtime.js +65 -0
- package/dist/src/services/gateway-rpc.d.ts +37 -0
- package/dist/src/services/gateway-rpc.js +37 -0
- package/dist/src/services/index.d.ts +41 -0
- package/dist/src/services/index.js +245 -0
- package/dist/src/services/services.d.ts +169 -0
- package/dist/src/services/services.js +1160 -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 +186 -0
- package/dist/src/services/ws-client.js +896 -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 +329 -0
- package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
- package/ext_capabilities/workrelay-speak/manifest.json +63 -0
- package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
- package/ext_capabilities/workrelay-speak/status.sh +111 -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 +330 -0
- package/ext_capabilities/workrelay-speak-luxtts/manifest.json +63 -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 +115 -0
- package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
- package/ext_capabilities/workrelay-speak-qwen/README.md +62 -0
- package/ext_capabilities/workrelay-speak-qwen/install.sh +349 -0
- package/ext_capabilities/workrelay-speak-qwen/manifest.json +63 -0
- package/ext_capabilities/workrelay-speak-qwen/status.sh +106 -0
- package/ext_capabilities/workrelay-speak-qwen/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 +227 -0
- package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
- package/ext_capabilities/workrelay-transcribe/status.sh +63 -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,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 ""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workrelay-speak",
|
|
3
|
+
"displayName": "语音合成(Kokoro)",
|
|
4
|
+
"description": "Agent 语音回复:Docker 部署 Voicebox(Kokoro 引擎),50+ 预设音色,轻量开箱即用",
|
|
5
|
+
"requirements": [
|
|
6
|
+
{
|
|
7
|
+
"id": "os",
|
|
8
|
+
"label": "操作系统",
|
|
9
|
+
"expect": "Linux",
|
|
10
|
+
"check": "uname -s | grep -qi linux",
|
|
11
|
+
"hint": "TTS 依赖 Docker,仅支持 Linux(Windows 请用 WSL2)"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "docker",
|
|
15
|
+
"label": "Docker 已安装",
|
|
16
|
+
"expect": "已安装 docker",
|
|
17
|
+
"check": "command -v docker >/dev/null 2>&1",
|
|
18
|
+
"hint": "未找到 Docker,请先安装:sudo apt install docker.io docker-compose-v2"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "docker_access",
|
|
22
|
+
"label": "Docker 可访问(权限)",
|
|
23
|
+
"expect": "docker info 可正常执行",
|
|
24
|
+
"check": "docker info >/dev/null 2>&1 || (id -nG | grep -qw docker && sg docker -c 'docker info' >/dev/null 2>&1) || sudo -n docker info >/dev/null 2>&1",
|
|
25
|
+
"hint": "Docker 无权限。一次性解决(需 sudo 密码一次):① sudo systemctl start docker ② sudo usermod -aG docker $USER($USER 换成 openclaw 运行用户,whoami 可查)③ openclaw gateway restart 后重试"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "memory",
|
|
29
|
+
"label": "内存 ≥ 6GB",
|
|
30
|
+
"expect": "≥ 6GB 可用",
|
|
31
|
+
"check": "[ $(LANG=C free -m | awk '/^Mem:/{print $2}') -ge 6144 ]",
|
|
32
|
+
"hint": "当前内存不足,TTS 至少需要 6GB 内存"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "disk",
|
|
36
|
+
"label": "磁盘 ≥ 10GB",
|
|
37
|
+
"expect": "≥ 10GB 可用",
|
|
38
|
+
"check": "[ $(df -BM \"$HOME\" | tail -1 | awk '{print $4}' | sed 's/M//') -ge 10240 ]",
|
|
39
|
+
"hint": "可用空间不足,镜像+模型约需 6GB,建议预留 10GB"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "network",
|
|
43
|
+
"label": "外网可达(GitHub)",
|
|
44
|
+
"expect": "能访问 github.com",
|
|
45
|
+
"check": "curl -sI --max-time 5 https://github.com >/dev/null 2>&1",
|
|
46
|
+
"hint": "无法访问 GitHub,将无法克隆 Voicebox 仓库和拉取 Docker 镜像"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "registry",
|
|
50
|
+
"label": "镜像仓库可达(ghcr.io / 国内公共仓库,自动回退)",
|
|
51
|
+
"expect": "能访问阿里云镜像仓库",
|
|
52
|
+
"check": "timeout 4 docker manifest inspect ghcr.io/rockychen2016/dlinkclaw-tts:kokoro-20260914 >/dev/null 2>&1 || timeout 4 docker manifest inspect registry.cn-hangzhou.aliyuncs.com/shiduo/dlinkclaw-tts:kokoro-20260914 >/dev/null 2>&1",
|
|
53
|
+
"hint": "镜像源不可达。国际源: ghcr.io(GitHub Container Registry,公开免登录);国内源: registry.cn-hangzhou.aliyuncs.com(待发布)。安装时自动按区域优先并回退"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "gpu",
|
|
57
|
+
"label": "GPU 加速(可选)",
|
|
58
|
+
"expect": "检测到 NVIDIA GPU 时合成更快",
|
|
59
|
+
"check": "if command -v nvidia-smi >/dev/null 2>&1; then nvidia-smi -L >/dev/null 2>&1 && exit 0; fi; exit 0",
|
|
60
|
+
"hint": "未检测到 NVIDIA 独显,将使用 CPU 合成(慢但可用);有 NVIDIA GPU 时自动启用 CUDA 加速"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""WorkRelay TTS: patch Voicebox kokoro_backend.py — 修复中文 pipeline 英文朗读。
|
|
3
|
+
|
|
4
|
+
问题:Kokoro 中文 pipeline 默认 version=None 走 legacy_call,英文文本被原样透传给模型,
|
|
5
|
+
模型把英文字母按日文假名音逐字读出(如 "Hello" -> L.O.H.O.R.Y.O.)。
|
|
6
|
+
|
|
7
|
+
方案:把 zh pipeline 的 g2p 替换为混合 G2P——
|
|
8
|
+
- 中文段:legacy(pypinyin→IPA),Kokoro-82M 认识的音素
|
|
9
|
+
- 英文段:misaki en G2P(→IPA)
|
|
10
|
+
- 纯数字段:cn2an 转中文数字再走 legacy
|
|
11
|
+
三类音素都是 Kokoro-82M 认识的 IPA,中英混读正常。
|
|
12
|
+
|
|
13
|
+
幂等:源码已包含 _MixedZhG2P 标记则跳过。用法:python3 patch-kokoro-en.py <kokoro_backend.py>
|
|
14
|
+
"""
|
|
15
|
+
import ast
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
MARKER = "_MixedZhG2P"
|
|
19
|
+
|
|
20
|
+
ANCHOR = """ self._pipelines[kokoro_lang] = KPipeline(
|
|
21
|
+
lang_code=kokoro_lang,
|
|
22
|
+
repo_id=KOKORO_HF_REPO,
|
|
23
|
+
model=self._model,
|
|
24
|
+
)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
PATCH = ANCHOR + """
|
|
28
|
+
# 2026-08-13 修复(WorkRelay):Kokoro 中文 pipeline 的 legacy_call 会把英文原样
|
|
29
|
+
# 透传给模型,模型把字母按日文假名音逐字读出(如 Hello -> L.O.H.O.R.Y.O.)。
|
|
30
|
+
# 方案:自定义混合 G2P——中文段走 legacy(pypinyin→IPA),英文段走 misaki en G2P,
|
|
31
|
+
# 纯数字段 cn2an 转中文数字再走 legacy。三类音素都是 Kokoro-82M 认识的 IPA。
|
|
32
|
+
if kokoro_lang == "z":
|
|
33
|
+
from misaki import zh, en
|
|
34
|
+
import jieba
|
|
35
|
+
import re as _re
|
|
36
|
+
import cn2an
|
|
37
|
+
|
|
38
|
+
en_g2p = en.G2P(trf=False, british=False, unk="")
|
|
39
|
+
|
|
40
|
+
class _MixedZhG2P:
|
|
41
|
+
def __call__(self, text):
|
|
42
|
+
def zh_to_ipa(seg):
|
|
43
|
+
words = jieba.lcut(seg, cut_all=False)
|
|
44
|
+
return " ".join(zh.ZHG2P.word2ipa(w) for w in words)
|
|
45
|
+
result = ""
|
|
46
|
+
for segment in _re.findall(r"[\\u4E00-\\u9FFF]+|[^\\u4E00-\\u9FFF]+", text):
|
|
47
|
+
if _re.search(r"[\\u4E00-\\u9FFF]", segment):
|
|
48
|
+
seg = cn2an.transform(segment, "an2cn") if _re.search(r"\\d", segment) else segment
|
|
49
|
+
result += zh_to_ipa(seg)
|
|
50
|
+
elif _re.search(r"[A-Za-z]", segment):
|
|
51
|
+
ps, _ = en_g2p(segment.strip())
|
|
52
|
+
result += ps
|
|
53
|
+
elif _re.search(r"\\d", segment):
|
|
54
|
+
result += zh_to_ipa(cn2an.transform(segment, "an2cn"))
|
|
55
|
+
else:
|
|
56
|
+
result += segment
|
|
57
|
+
return result.replace(chr(815), ""), None
|
|
58
|
+
|
|
59
|
+
self._pipelines[kokoro_lang].g2p = _MixedZhG2P()
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> int:
|
|
64
|
+
if len(sys.argv) != 2:
|
|
65
|
+
print("usage: patch-kokoro-en.py <kokoro_backend.py>", file=sys.stderr)
|
|
66
|
+
return 1
|
|
67
|
+
path = sys.argv[1]
|
|
68
|
+
with open(path, encoding="utf-8") as f:
|
|
69
|
+
src = f.read()
|
|
70
|
+
if MARKER in src:
|
|
71
|
+
print("already patched, skip")
|
|
72
|
+
return 0
|
|
73
|
+
if ANCHOR not in src:
|
|
74
|
+
print("ERROR: anchor not found in", path, file=sys.stderr)
|
|
75
|
+
return 1
|
|
76
|
+
new_src = src.replace(ANCHOR, PATCH, 1)
|
|
77
|
+
ast.parse(new_src) # 语法检查
|
|
78
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
79
|
+
f.write(new_src)
|
|
80
|
+
print("patched OK")
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
sys.exit(main())
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(TTS)状态检查
|
|
3
|
+
# 返回值: 0=已启用, 1=未安装, 2=部分安装, 3=Docker未运行
|
|
4
|
+
# 同时输出 JSON 给客户端解析
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
STATUS_OK=0
|
|
9
|
+
STATUS_NOT_INSTALLED=1
|
|
10
|
+
STATUS_PARTIAL=2
|
|
11
|
+
STATUS_DOCKER_DOWN=3
|
|
12
|
+
|
|
13
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
14
|
+
VB_PORT="${WR_VOICEBOX_PORT:-17493}"
|
|
15
|
+
VB_DOCKER_PORT="${WR_VOICEBOX_DOCKER_PORT:-17600}"
|
|
16
|
+
|
|
17
|
+
# 2026-08-11 修复:与 install.sh 保持一致——补全 PATH(snap docker)+ docker 权限自适应
|
|
18
|
+
# (当前用户无 docker 组权限时,用免密 sudo 包装 docker,否则状态会误报 Docker 未运行)
|
|
19
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
20
|
+
# 2026-08-18:与 install.sh 保持一致的 docker 权限自适应(sg docker / 免密 sudo)
|
|
21
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
22
|
+
if sg docker -c "docker info" &>/dev/null 2>&1; then
|
|
23
|
+
docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
|
|
24
|
+
elif sudo -n docker info &>/dev/null 2>&1; then
|
|
25
|
+
docker() { sudo docker "$@"; }
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# 检测实际端口(优先 17493,fallback 17600)
|
|
30
|
+
check_port=$VB_PORT
|
|
31
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1 && check_port=$VB_PORT || {
|
|
32
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_DOCKER_PORT}/mcp" >/dev/null 2>&1 && check_port=$VB_DOCKER_PORT
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# 检查1: Docker
|
|
36
|
+
check_docker=0
|
|
37
|
+
docker info &>/dev/null 2>&1 && check_docker=1 || true
|
|
38
|
+
|
|
39
|
+
# 检查2: Voicebox 容器
|
|
40
|
+
check_container=0
|
|
41
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "voicebox" && check_container=1 || true
|
|
42
|
+
|
|
43
|
+
# 检查3: API
|
|
44
|
+
check_api=0
|
|
45
|
+
curl -s --max-time 3 "http://${VB_HOST}:${check_port}/mcp" >/dev/null 2>&1 && check_api=1 || true
|
|
46
|
+
|
|
47
|
+
# 检查4: TTS 桥接脚本
|
|
48
|
+
check_tts_script=0
|
|
49
|
+
[ -f "$HOME/.workrelay/speak/workrelay-tts.sh" ] && check_tts_script=1 || true
|
|
50
|
+
|
|
51
|
+
# 检查5: OpenClaw TTS 配置(2026-09-14 openclaw 升级后:1) config 路径变更 messages.tts.* → 顶层 tts.*;
|
|
52
|
+
# 2) 不再调 openclaw CLI——升级后 CLI 可能挂起卡住插件队列,改为直接读 openclaw.json,毫秒级返回)
|
|
53
|
+
# 2026-09-14 互斥判定:仅当 tts.provider == tts-local-cli 时才算"已启用"(TTS 只能启用一个)
|
|
54
|
+
check_config=0
|
|
55
|
+
check_provider=0
|
|
56
|
+
OC_JSON="${OPENCLAW_HOME:-$HOME/.openclaw}/openclaw.json"
|
|
57
|
+
if [ -f "$OC_JSON" ]; then
|
|
58
|
+
grep -q '"tts-local-cli":' "$OC_JSON" && check_config=1 || true
|
|
59
|
+
CUR_PROVIDER=$(python3 -c '
|
|
60
|
+
import json,sys
|
|
61
|
+
try:
|
|
62
|
+
c=json.load(open(sys.argv[1]))
|
|
63
|
+
print((c.get("tts") or {}).get("provider",""))
|
|
64
|
+
except Exception:
|
|
65
|
+
print("")
|
|
66
|
+
' "$OC_JSON" 2>/dev/null)
|
|
67
|
+
[ "$CUR_PROVIDER" = "tts-local-cli" ] && check_provider=1 || true
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ── 汇总状态 ──
|
|
71
|
+
total=$((check_docker + check_container + check_api + check_tts_script + check_config))
|
|
72
|
+
|
|
73
|
+
case $total in
|
|
74
|
+
5)
|
|
75
|
+
# 2026-09-14 互斥:安装齐全后再看 provider 是否指向本引擎
|
|
76
|
+
if [ "$check_provider" -eq 1 ]; then
|
|
77
|
+
status="enabled"; desc="语音合成(Kokoro)已启用"; exit_code=$STATUS_OK
|
|
78
|
+
else
|
|
79
|
+
status="disabled"; desc="已安装但未启用(当前语音合成为其它引擎,TTS 只能启用一个)"; exit_code=$STATUS_NOT_INSTALLED
|
|
80
|
+
fi
|
|
81
|
+
;;
|
|
82
|
+
0) status="disabled"; desc="未安装"; exit_code=$STATUS_NOT_INSTALLED ;;
|
|
83
|
+
*)
|
|
84
|
+
if [ "$check_container" -eq 0 ] && [ "$check_docker" -eq 1 ]; then
|
|
85
|
+
status="disabled"; desc="Docker 运行中但 Voicebox 未安装"; exit_code=$STATUS_NOT_INSTALLED
|
|
86
|
+
elif [ "$check_container" -eq 1 ] && [ "$check_api" -eq 0 ]; then
|
|
87
|
+
status="partial"; desc="Voicebox 容器存在但 API 未就绪(可能在下载模型)"; exit_code=$STATUS_DOCKER_DOWN
|
|
88
|
+
else
|
|
89
|
+
status="partial"; desc="部分安装"; exit_code=$STATUS_PARTIAL
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
# ── 输出 JSON ──
|
|
95
|
+
cat <<EOF
|
|
96
|
+
{
|
|
97
|
+
"status": "$status",
|
|
98
|
+
"description": "$desc",
|
|
99
|
+
"endpoint": "http://${VB_HOST}:${check_port}",
|
|
100
|
+
"checks": {
|
|
101
|
+
"docker": $( [ $check_docker -eq 1 ] && echo true || echo false ),
|
|
102
|
+
"container": $( [ $check_container -eq 1 ] && echo true || echo false ),
|
|
103
|
+
"api": $( [ $check_api -eq 1 ] && echo true || echo false ),
|
|
104
|
+
"tts_script": $( [ $check_tts_script -eq 1 ] && echo true || echo false ),
|
|
105
|
+
"openclaw_config": $( [ $check_config -eq 1 ] && echo true || echo false ),
|
|
106
|
+
"provider_active": $( [ $check_provider -eq 1 ] && echo true || echo false )
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
EOF
|
|
110
|
+
|
|
111
|
+
exit $exit_code
|