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,115 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(LuxTTS 克隆声)状态检查
|
|
3
|
+
# 返回值: 0=已启用, 1=未安装, 2=部分安装, 3=Docker未运行
|
|
4
|
+
# 同时输出 JSON 给客户端解析
|
|
5
|
+
# 2026-08-15 新增能力:独立容器 voicebox-luxtts + 端口 17601 + provider tts-local-cli-luxtts
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
STATUS_OK=0
|
|
10
|
+
STATUS_NOT_INSTALLED=1
|
|
11
|
+
STATUS_PARTIAL=2
|
|
12
|
+
STATUS_DOCKER_DOWN=3
|
|
13
|
+
|
|
14
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
15
|
+
VB_PORT="${WR_VOICEBOX_LUXTTS_PORT:-17601}"
|
|
16
|
+
VB_DOCKER_PORT="${WR_VOICEBOX_LUXTTS_PORT:-17601}"
|
|
17
|
+
|
|
18
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
19
|
+
# 2026-08-18:与 install.sh 保持一致的 docker 权限自适应(sg docker / 免密 sudo)
|
|
20
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
21
|
+
if sg docker -c "docker info" &>/dev/null 2>&1; then
|
|
22
|
+
docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
|
|
23
|
+
elif sudo -n docker info &>/dev/null 2>&1; then
|
|
24
|
+
docker() { sudo docker "$@"; }
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# 检测实际端口(17601,fallback 同端口)
|
|
29
|
+
check_port=$VB_PORT
|
|
30
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1 && check_port=$VB_PORT || {
|
|
31
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_DOCKER_PORT}/mcp" >/dev/null 2>&1 && check_port=$VB_DOCKER_PORT
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# 检查1: Docker
|
|
35
|
+
check_docker=0
|
|
36
|
+
docker info &>/dev/null 2>&1 && check_docker=1 || true
|
|
37
|
+
|
|
38
|
+
# 检查2: voicebox-luxtts 容器
|
|
39
|
+
check_container=0
|
|
40
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "voicebox-luxtts" && check_container=1 || true
|
|
41
|
+
|
|
42
|
+
# 检查3: API
|
|
43
|
+
check_api=0
|
|
44
|
+
curl -s --max-time 3 "http://${VB_HOST}:${check_port}/mcp" >/dev/null 2>&1 && check_api=1 || true
|
|
45
|
+
|
|
46
|
+
# 检查4: TTS 桥接脚本
|
|
47
|
+
check_tts_script=0
|
|
48
|
+
[ -f "$HOME/.workrelay/speak-luxtts/workrelay-tts.sh" ] && check_tts_script=1 || true
|
|
49
|
+
|
|
50
|
+
# 检查5: 克隆声音 profile
|
|
51
|
+
check_profile=0
|
|
52
|
+
[ -f "$HOME/.workrelay/speak-luxtts/profile_id" ] && check_profile=1 || true
|
|
53
|
+
|
|
54
|
+
# 检查6: OpenClaw TTS 配置(2026-09-14 openclaw 升级后:config 路径 messages.tts.* → 顶层 tts.*;
|
|
55
|
+
# 不再调 openclaw CLI(升级后可能挂起卡插件队列),改为直接读 openclaw.json)
|
|
56
|
+
# 2026-09-14 互斥判定:仅当 tts.provider == tts-local-cli-luxtts 时才算"已启用"(TTS 只能启用一个)
|
|
57
|
+
check_config=0
|
|
58
|
+
check_provider=0
|
|
59
|
+
OC_JSON="${OPENCLAW_HOME:-$HOME/.openclaw}/openclaw.json"
|
|
60
|
+
if [ -f "$OC_JSON" ]; then
|
|
61
|
+
grep -q '"tts-local-cli-luxtts":' "$OC_JSON" && check_config=1 || true
|
|
62
|
+
CUR_PROVIDER=$(python3 -c '
|
|
63
|
+
import json,sys
|
|
64
|
+
try:
|
|
65
|
+
c=json.load(open(sys.argv[1]))
|
|
66
|
+
print((c.get("tts") or {}).get("provider",""))
|
|
67
|
+
except Exception:
|
|
68
|
+
print("")
|
|
69
|
+
' "$OC_JSON" 2>/dev/null)
|
|
70
|
+
[ "$CUR_PROVIDER" = "tts-local-cli-luxtts" ] && check_provider=1 || true
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# ── 汇总状态 ──
|
|
74
|
+
total=$((check_docker + check_container + check_api + check_tts_script + check_profile + check_config))
|
|
75
|
+
|
|
76
|
+
case $total in
|
|
77
|
+
6)
|
|
78
|
+
# 2026-09-14 互斥:安装齐全后再看 provider 是否指向本引擎
|
|
79
|
+
if [ "$check_provider" -eq 1 ]; then
|
|
80
|
+
status="enabled"; desc="语音合成(LuxTTS 克隆声)已启用"; exit_code=$STATUS_OK
|
|
81
|
+
else
|
|
82
|
+
status="disabled"; desc="已安装但未启用(当前语音合成为其它引擎,TTS 只能启用一个)"; exit_code=$STATUS_NOT_INSTALLED
|
|
83
|
+
fi
|
|
84
|
+
;;
|
|
85
|
+
0) status="disabled"; desc="未安装"; exit_code=$STATUS_NOT_INSTALLED ;;
|
|
86
|
+
*)
|
|
87
|
+
if [ "$check_container" -eq 0 ] && [ "$check_docker" -eq 1 ]; then
|
|
88
|
+
status="disabled"; desc="Docker 运行中但 voicebox-luxtts 未安装"; exit_code=$STATUS_NOT_INSTALLED
|
|
89
|
+
elif [ "$check_container" -eq 1 ] && [ "$check_api" -eq 0 ]; then
|
|
90
|
+
status="partial"; desc="voicebox-luxtts 容器存在但 API 未就绪(可能在下载模型)"; exit_code=$STATUS_DOCKER_DOWN
|
|
91
|
+
else
|
|
92
|
+
status="partial"; desc="部分安装"; exit_code=$STATUS_PARTIAL
|
|
93
|
+
fi
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
|
|
97
|
+
# ── 输出 JSON ──
|
|
98
|
+
cat <<EOF
|
|
99
|
+
{
|
|
100
|
+
"status": "$status",
|
|
101
|
+
"description": "$desc",
|
|
102
|
+
"endpoint": "http://${VB_HOST}:${check_port}",
|
|
103
|
+
"checks": {
|
|
104
|
+
"docker": $( [ $check_docker -eq 1 ] && echo true || echo false ),
|
|
105
|
+
"container": $( [ $check_container -eq 1 ] && echo true || echo false ),
|
|
106
|
+
"api": $( [ $check_api -eq 1 ] && echo true || echo false ),
|
|
107
|
+
"tts_script": $( [ $check_tts_script -eq 1 ] && echo true || echo false ),
|
|
108
|
+
"profile": $( [ $check_profile -eq 1 ] && echo true || echo false ),
|
|
109
|
+
"openclaw_config": $( [ $check_config -eq 1 ] && echo true || echo false ),
|
|
110
|
+
"provider_active": $( [ $check_provider -eq 1 ] && echo true || echo false )
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
EOF
|
|
114
|
+
|
|
115
|
+
exit $exit_code
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay TTS 桥接脚本(LuxTTS 引擎)- 调用本地 Voicebox (voicebox-luxtts) REST API
|
|
3
|
+
# 环境变量:
|
|
4
|
+
# WR_VOICEBOX_HOST - Voicebox 服务地址(默认 127.0.0.1)
|
|
5
|
+
# WR_VOICEBOX_LUXTTS_PORT - LuxTTS 实例端口(默认 17601,独立于 Kokoro 版)
|
|
6
|
+
# WR_VOICEBOX_PROFILE_ID - 声音 profile ID(可选,缺省读取 ~/.workrelay/speak-luxtts/profile_id)
|
|
7
|
+
# WR_VOICEBOX_LANG - 语言(默认 zh)
|
|
8
|
+
# 固定引擎 luxtts(LuxTTS 为克隆声音引擎,无预设音色,必须使用克隆 profile)
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
TEXT="$1"
|
|
13
|
+
OUT="$2"
|
|
14
|
+
|
|
15
|
+
if [ -z "$TEXT" ] || [ -z "$OUT" ]; then
|
|
16
|
+
echo "usage: $0 <text> <output_path>" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
21
|
+
VB_PORT="${WR_VOICEBOX_LUXTTS_PORT:-17601}"
|
|
22
|
+
PROFILE_ID="${WR_VOICEBOX_PROFILE_ID:-}"
|
|
23
|
+
ENGINE="luxtts"
|
|
24
|
+
LANG="${WR_VOICEBOX_LANG:-zh}"
|
|
25
|
+
|
|
26
|
+
BASE_URL="http://${VB_HOST}:${VB_PORT}"
|
|
27
|
+
if ! curl -s --max-time 2 "${BASE_URL}/mcp" >/dev/null 2>&1; then
|
|
28
|
+
echo "ERROR: Voicebox (LuxTTS) API 不可达 ($VB_HOST:$VB_PORT)" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ── profile 自动解析(install.sh 写入石头哥克隆声 profile id)──
|
|
33
|
+
if [ -z "$PROFILE_ID" ] && [ -f "$HOME/.workrelay/speak-luxtts/profile_id" ]; then
|
|
34
|
+
PROFILE_ID=$(cat "$HOME/.workrelay/speak-luxtts/profile_id" 2>/dev/null | tr -d ' \n')
|
|
35
|
+
fi
|
|
36
|
+
if [ -z "$PROFILE_ID" ]; then
|
|
37
|
+
# 兜底:从 /profiles 找克隆 profile(优先中文)
|
|
38
|
+
PROFILE_ID=$(curl --noproxy '*' -s --max-time 5 "$BASE_URL/profiles" 2>/dev/null \
|
|
39
|
+
| python3 -c '
|
|
40
|
+
import json,sys
|
|
41
|
+
out=""
|
|
42
|
+
try:
|
|
43
|
+
d=json.load(sys.stdin)
|
|
44
|
+
for p in d:
|
|
45
|
+
if p.get("voice_type")=="cloned" and p.get("language")=="zh":
|
|
46
|
+
out=p.get("id",""); break
|
|
47
|
+
if not out:
|
|
48
|
+
for p in d:
|
|
49
|
+
if p.get("voice_type")=="cloned":
|
|
50
|
+
out=p.get("id",""); break
|
|
51
|
+
print(out)
|
|
52
|
+
except Exception:
|
|
53
|
+
print("")
|
|
54
|
+
' 2>/dev/null)
|
|
55
|
+
fi
|
|
56
|
+
if [ -z "$PROFILE_ID" ]; then
|
|
57
|
+
echo "ERROR: 未找到可用的克隆声音 profile(Voicebox /profiles 为空)" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# JSON-escape text
|
|
62
|
+
TEXT_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$TEXT")
|
|
63
|
+
|
|
64
|
+
# 提交生成请求(固定 engine=luxtts)
|
|
65
|
+
RESP=$(curl --noproxy '*' -s --max-time 15 -X POST "$BASE_URL/generate" \
|
|
66
|
+
-H "Content-Type: application/json" \
|
|
67
|
+
-H "X-Voicebox-Client-Id: workrelay-tts-luxtts" \
|
|
68
|
+
-d "{\"text\": $TEXT_JSON, \"profile_id\": \"$PROFILE_ID\", \"language\": \"$LANG\", \"engine\": \"$ENGINE\"}")
|
|
69
|
+
|
|
70
|
+
GEN_ID=$(echo "$RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
71
|
+
if [ -z "$GEN_ID" ]; then
|
|
72
|
+
echo "ERROR: Voicebox 生成提交失败: $RESP" >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# 轮询等待完成(CPU 推理约 10-180 秒,视文本长度)
|
|
77
|
+
MAX_POLLS=180
|
|
78
|
+
for i in $(seq 1 $MAX_POLLS); do
|
|
79
|
+
sleep 1
|
|
80
|
+
ST=$(curl --noproxy '*' -s --max-time 10 "$BASE_URL/history/$GEN_ID" 2>/dev/null \
|
|
81
|
+
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("status",""))' 2>/dev/null)
|
|
82
|
+
if [ "$ST" = "completed" ]; then
|
|
83
|
+
break
|
|
84
|
+
fi
|
|
85
|
+
if [ "$ST" = "error" ] || [ "$ST" = "failed" ]; then
|
|
86
|
+
echo "ERROR: Voicebox 生成失败 (id=$GEN_ID, status=$ST)" >&2
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
|
|
91
|
+
# 下载音频
|
|
92
|
+
curl --noproxy '*' -s --max-time 30 -o "$OUT" "$BASE_URL/audio/$GEN_ID"
|
|
93
|
+
if [ -s "$OUT" ]; then
|
|
94
|
+
exit 0
|
|
95
|
+
else
|
|
96
|
+
echo "ERROR: Voicebox 音频下载失败 (id=$GEN_ID)" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# WorkRelay 语音合成(千问 Qwen3-TTS)扩展能力
|
|
2
|
+
|
|
3
|
+
Agent 语音回复能力:Docker 部署 **Voicebox**(**Qwen3-TTS CustomVoice 1.7B** 引擎),
|
|
4
|
+
9 种预设音色(含中文女声 Vivian),中英日韩多语种,断句/语速自然,适合视频配音。
|
|
5
|
+
|
|
6
|
+
> 2026-09-14 新增。与 `workrelay-speak`(Kokoro)、`workrelay-speak-luxtts`(LuxTTS)**互斥**:
|
|
7
|
+
> TTS 同时只能启用一个,启用本能力会自动停止其它两个 TTS 容器并接管 `tts.provider`。
|
|
8
|
+
|
|
9
|
+
## 文件说明
|
|
10
|
+
|
|
11
|
+
| 文件 | 作用 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `workrelay-tts.sh` | TTS 桥接脚本(调 voicebox-qwen REST API,固定引擎 qwen_custom_voice) |
|
|
14
|
+
| `install.sh` | 一键安装(拉镜像 → 下载千问模型 → 起容器 → 建音色 profile → 互斥 → 配 OpenClaw) |
|
|
15
|
+
| `status.sh` | 状态查询(含**互斥判定**:读 `tts.provider` 判断是否当前启用) |
|
|
16
|
+
| `manifest.json` | 前置条件清单(含镜像仓库可达性 + GPU 检测) |
|
|
17
|
+
|
|
18
|
+
## 关键参数
|
|
19
|
+
|
|
20
|
+
| 项 | 值 |
|
|
21
|
+
|---|---|
|
|
22
|
+
| 容器名 | `voicebox-qwen` |
|
|
23
|
+
| 端口 | `17602`(Kokoro 17493/17600、LuxTTS 17601) |
|
|
24
|
+
| 镜像 | `crpi-...aliyuncs.com/shiduo/workrelay-tts:20260813`(复用,已含 qwen_custom_voice 后端) |
|
|
25
|
+
| 模型 | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice`(~4.6GB,安装时经 hf-mirror.com 下载) |
|
|
26
|
+
| 音色 profile | Vivian(中文女声);其它:Serena/Uncle_Fu/Dylan/Eric/Ryan/Aiden/Ono_Anna/Sohee |
|
|
27
|
+
| OpenClaw provider | `tts-local-cli-qwen`(配置在顶层 `tts.providers.*`) |
|
|
28
|
+
| 合成超时 | 300000 ms(5 分钟,CPU 推理较慢) |
|
|
29
|
+
|
|
30
|
+
## 状态返回(status.sh)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"status": "enabled | disabled | partial",
|
|
35
|
+
"description": "…",
|
|
36
|
+
"endpoint": "http://127.0.0.1:17602",
|
|
37
|
+
"checks": {
|
|
38
|
+
"docker": true, "container": true, "api": true,
|
|
39
|
+
"tts_script": true, "profile": true,
|
|
40
|
+
"openclaw_config": true, "provider_active": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `provider_active=true` → 当前 TTS 就是本引擎(`tts.provider=tts-local-cli-qwen`)
|
|
46
|
+
- `provider_active=false` → 已安装但被其它 TTS 接管 → 状态显示"已安装但未启用"
|
|
47
|
+
|
|
48
|
+
## 管理命令
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
docker ps | grep voicebox-qwen # 查看容器
|
|
52
|
+
docker logs -f voicebox-qwen # 查看日志
|
|
53
|
+
curl -s http://127.0.0.1:17602/profiles # 查看音色 profile
|
|
54
|
+
docker stop voicebox-qwen # 停用(并把 tts.provider 切回其它引擎)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## GPU 说明
|
|
58
|
+
|
|
59
|
+
- 有 NVIDIA GPU(`nvidia-smi` 可用)→ 安装时自动 `--gpus all`,合成快
|
|
60
|
+
- 无独显 → CPU 合成,1.7B 模型约 10~60 秒/句(慢但可用)
|
|
61
|
+
|
|
62
|
+
*文档版本:2026-09-14*
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(千问 Qwen3-TTS)一键安装脚本
|
|
3
|
+
# 2026-09-14 新增能力:
|
|
4
|
+
# - 独立容器 voicebox-qwen(端口 17602),与 Kokoro(17493) / LuxTTS(17601) 互不干扰
|
|
5
|
+
# - 复用 workrelay-tts 镜像(已内置 qwen_custom_voice 后端),安装时下载 Qwen3-TTS 1.7B 模型(~4.6GB)
|
|
6
|
+
# - TTS 互斥:启用本能力会自动停止其它 TTS 容器,并把 tts.provider 切到本引擎
|
|
7
|
+
set -eo pipefail
|
|
8
|
+
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
VB_DIR="$HOME/.workrelay/voicebox-qwen"
|
|
15
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
16
|
+
# 独立端口 17602(Kokoro 17493/17600、LuxTTS 17601,避免冲突)
|
|
17
|
+
VB_PORT="${WR_VOICEBOX_QWEN_PORT:-17602}"
|
|
18
|
+
VB_CONTAINER_PORT=17493
|
|
19
|
+
# 2026-09-14 双源:国内=阿里云公共仓库;国际=ghcr.io
|
|
20
|
+
# 2026-09-14 改名为 dlinkclaw-tts(统一仓库名,tag 区分模型;千问复用同源镜像,模型安装时下载)
|
|
21
|
+
IMG_TAG="${WR_IMAGE_TAG:-qwen-20260914}"
|
|
22
|
+
# 国内源:暂未发布(留空自动跳过);发布后填入 registry.cn-hangzhou.aliyuncs.com/NAMESPACE/dlinkclaw-tts:${IMG_TAG}
|
|
23
|
+
IMG_CN="${WR_IMAGE_CN:-}"
|
|
24
|
+
IMG_INTL="${WR_IMAGE_INTL:-ghcr.io/rockychen2016/dlinkclaw-tts:${IMG_TAG}}"
|
|
25
|
+
VB_IMAGE=""
|
|
26
|
+
# 千问模型(CustomVoice 预设音色版,1.7B)
|
|
27
|
+
QWEN_MODEL="${WR_QWEN_MODEL:-Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice}"
|
|
28
|
+
# 2026-09-14:模型下载源按区域选择——国内=hf-mirror.com(直连 HF 通常不通);国际=huggingface.co
|
|
29
|
+
if [ -n "${WR_HF_ENDPOINT:-}" ]; then
|
|
30
|
+
HF_MIRROR="$WR_HF_ENDPOINT"
|
|
31
|
+
elif [ "${WR_IMAGE_REGION:-auto}" = "intl" ]; then
|
|
32
|
+
HF_MIRROR="https://huggingface.co"
|
|
33
|
+
else
|
|
34
|
+
HF_MIRROR="https://hf-mirror.com"
|
|
35
|
+
fi
|
|
36
|
+
INSTALL_DIR="$(dirname "$(readlink -f "$0")")"
|
|
37
|
+
|
|
38
|
+
# ── 2026-09-14 镜像双源(国内=阿里云公共仓库 / 国际=ghcr.io)──
|
|
39
|
+
# 按 WR_IMAGE_REGION(auto|cn|intl)优先尝试,失败自动回退另一源;WR_VOICEBOX_IMAGE 可显式覆盖。
|
|
40
|
+
IMAGE_CANDIDATES=()
|
|
41
|
+
if [ -n "${WR_VOICEBOX_IMAGE:-}" ]; then
|
|
42
|
+
IMAGE_CANDIDATES=("$WR_VOICEBOX_IMAGE")
|
|
43
|
+
else
|
|
44
|
+
# 2026-09-14:国内源暂未发布(IMG_CN 为空则自动跳过),默认国际源优先;国内源上架后自动生效
|
|
45
|
+
case "${WR_IMAGE_REGION:-auto}" in
|
|
46
|
+
cn) CAND_PRIMARY="$IMG_CN"; CAND_SECONDARY="$IMG_INTL" ;;
|
|
47
|
+
intl) CAND_PRIMARY="$IMG_INTL"; CAND_SECONDARY="$IMG_CN" ;;
|
|
48
|
+
*) CAND_PRIMARY="$IMG_INTL"; CAND_SECONDARY="$IMG_CN" ;;
|
|
49
|
+
esac
|
|
50
|
+
[ -n "$CAND_PRIMARY" ] && IMAGE_CANDIDATES+=("$CAND_PRIMARY")
|
|
51
|
+
[ -n "$CAND_SECONDARY" ] && IMAGE_CANDIDATES+=("$CAND_SECONDARY")
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
pull_image_with_fallback() {
|
|
55
|
+
local log="$1"
|
|
56
|
+
for img in "${IMAGE_CANDIDATES[@]}"; do
|
|
57
|
+
echo " → 尝试镜像源: $img"
|
|
58
|
+
if docker pull "$img" > "$log" 2>&1; then
|
|
59
|
+
VB_IMAGE="$img"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
echo " ⚠ 该源拉取失败,尝试下一个..."
|
|
63
|
+
done
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
echo "============================================"
|
|
69
|
+
echo " WorkRelay 语音合成(千问 Qwen3-TTS)- 一键安装"
|
|
70
|
+
echo "============================================"
|
|
71
|
+
echo ""
|
|
72
|
+
|
|
73
|
+
# ── 系统检查 ──
|
|
74
|
+
echo -n "[1/8] 检查系统环境... "
|
|
75
|
+
|
|
76
|
+
OS="$(uname -s)"
|
|
77
|
+
case "$OS" in
|
|
78
|
+
Linux) ;;
|
|
79
|
+
*)
|
|
80
|
+
echo -e "${RED}失败${NC}"
|
|
81
|
+
echo "错误:当前仅支持 Linux(TTS 需要 Docker)"
|
|
82
|
+
exit 1
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
|
|
86
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
87
|
+
|
|
88
|
+
if ! command -v docker &>/dev/null; then
|
|
89
|
+
echo -e "${RED}失败${NC}"
|
|
90
|
+
echo "错误:未找到 Docker,请先安装 Docker"
|
|
91
|
+
echo " Ubuntu: sudo apt install docker.io docker-compose-v2"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
96
|
+
# docker 权限多级自适应(尽量零 sudo)
|
|
97
|
+
if sg docker -c "docker info" &>/dev/null 2>&1; then
|
|
98
|
+
echo " ℹ 用户已加入 docker 组,通过 sg docker 执行(无需 sudo)"
|
|
99
|
+
docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
|
|
100
|
+
elif sudo -n docker info &>/dev/null 2>&1; then
|
|
101
|
+
echo " ⚠ 当前用户无 docker 组权限,检测到免密 sudo,自动使用 sudo 执行 docker"
|
|
102
|
+
docker() { sudo docker "$@"; }
|
|
103
|
+
else
|
|
104
|
+
echo -e "${RED}失败${NC}"
|
|
105
|
+
echo "错误:Docker 未运行或无权限。请先执行一次以下命令:"
|
|
106
|
+
echo ""
|
|
107
|
+
echo " ① 启动 Docker:sudo systemctl start docker"
|
|
108
|
+
echo " ② 把当前用户加入 docker 组:sudo usermod -aG docker \$USER"
|
|
109
|
+
echo " ③ 重启 openclaw 服务(或重新登录会话),然后重新安装"
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
echo -e "${GREEN}OK${NC} ($(docker --version | head -1), $(uname -o))"
|
|
115
|
+
|
|
116
|
+
# snap docker 项目目录适配(AppArmor 只放行 /var/snap/docker/common)
|
|
117
|
+
if [ -d /var/snap/docker/common ]; then
|
|
118
|
+
VB_DIR="/var/snap/docker/common/workrelay-voicebox-qwen"
|
|
119
|
+
echo " ℹ 检测到 snap docker,项目目录使用 $VB_DIR(AppArmor 放行读写)"
|
|
120
|
+
if [ -d "$VB_DIR" ]; then
|
|
121
|
+
sudo -n chown -R "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
122
|
+
elif ! sudo -n mkdir -p "$VB_DIR" 2>/dev/null; then
|
|
123
|
+
echo -e "${RED}失败${NC}"
|
|
124
|
+
echo "错误:无法创建项目目录 $VB_DIR(需要 root 权限)。"
|
|
125
|
+
echo "请手动执行一次以下命令后重新安装:"
|
|
126
|
+
echo " sudo mkdir -p $VB_DIR && sudo chown \$USER:\$USER $VB_DIR"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
sudo -n chown "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 磁盘空间(模型 4.6GB + 镜像,建议 ≥8GB)──
|
|
133
|
+
echo -n "[2/8] 检查磁盘空间... "
|
|
134
|
+
DF_TARGET="$VB_DIR"; [ -d "$DF_TARGET" ] || DF_TARGET="$HOME"
|
|
135
|
+
AVAIL=$(df -BM "$DF_TARGET" | tail -1 | awk '{print $4}' | sed 's/M//')
|
|
136
|
+
if [ "$AVAIL" -lt 8192 ]; then
|
|
137
|
+
echo -e "${RED}不足${NC}"
|
|
138
|
+
echo "错误:可用空间 ${AVAIL}MB,至少需要 8GB(模型 4.6GB + 运行缓存)"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
echo -e "${GREEN}OK${NC} (可用 ${AVAIL}MB)"
|
|
142
|
+
|
|
143
|
+
# ── 内存检查 ──
|
|
144
|
+
echo -n "[3/8] 检查内存... "
|
|
145
|
+
# 2026-09-15 修复:中文 locale 下 free -m 输出“内存:”,awk 匹配 Mem: 失败会误报内存不足 → 强制 LANG=C
|
|
146
|
+
RAM=$(LANG=C free -m | awk '/^Mem:/{print $2}')
|
|
147
|
+
if [ "$RAM" -lt 6000 ]; then
|
|
148
|
+
echo -e "${RED}不足 (${RAM}MB)${NC}"
|
|
149
|
+
echo "错误:至少需要 6GB 内存,当前仅 ${RAM}MB"
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
echo -e "${GREEN}OK${NC} (${RAM}MB)"
|
|
153
|
+
|
|
154
|
+
# ── GPU 检测(有 NVIDIA 则启用 CUDA 加速)──
|
|
155
|
+
echo -n "[4/8] 检测 GPU... "
|
|
156
|
+
DOCKER_GPU_ARGS=""
|
|
157
|
+
if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null 2>&1; then
|
|
158
|
+
echo -e "${GREEN}检测到 NVIDIA GPU${NC} ($(nvidia-smi -L 2>/dev/null | head -1))"
|
|
159
|
+
DOCKER_GPU_ARGS="--gpus all"
|
|
160
|
+
else
|
|
161
|
+
echo -e "${YELLOW}未检测到 N 卡,使用 CPU 合成(10~60 秒/句,较慢但可用)${NC}"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# ── 端口检查 ──
|
|
165
|
+
echo -n "[5/8] 检查端口 $VB_PORT... "
|
|
166
|
+
if ss -tlnp 2>/dev/null | grep -q ":$VB_PORT "; then
|
|
167
|
+
echo -e "${RED}已被占用${NC}"
|
|
168
|
+
echo "错误:端口 $VB_PORT 已被占用,请先停止占用进程或设置 WR_VOICEBOX_QWEN_PORT 换端口"
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
171
|
+
echo -e "${GREEN}空闲${NC}"
|
|
172
|
+
|
|
173
|
+
# ── 拉取镜像 ──
|
|
174
|
+
echo -n "[6/8] 拉取 Voicebox 镜像... "
|
|
175
|
+
mkdir -p "$VB_DIR"
|
|
176
|
+
if pull_image_with_fallback /tmp/wr-pull-qwen.log; then
|
|
177
|
+
echo -e "${GREEN}OK${NC} ($VB_IMAGE)"
|
|
178
|
+
else
|
|
179
|
+
echo -e "${RED}失败${NC}"
|
|
180
|
+
tail -5 /tmp/wr-pull-qwen.log
|
|
181
|
+
echo "错误:所有镜像源均拉取失败,检查网络连接(国内=阿里云公共仓库 / 国际=ghcr.io)"
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ── 预下载千问模型(用镜像自带的 huggingface_hub + 国内镜像源)──
|
|
186
|
+
# 说明:镜像内已内置 Kokoro 模型,但未含 Qwen 模型;把 Qwen 模型下载到独立缓存目录,
|
|
187
|
+
# 容器启动时挂载该目录作为 HF 缓存(HF_HUB_OFFLINE=1 离线加载,避免容器内联网失败)。
|
|
188
|
+
echo -n "[7/8] 下载千问模型(约 4.6GB,视网速 5~30 分钟)... "
|
|
189
|
+
HF_CACHE="$VB_DIR/hf-cache"
|
|
190
|
+
mkdir -p "$HF_CACHE"
|
|
191
|
+
if docker run --rm \
|
|
192
|
+
-v "$HF_CACHE:/hf-cache" \
|
|
193
|
+
-e HF_ENDPOINT="$HF_MIRROR" \
|
|
194
|
+
-e HF_HUB_OFFLINE=0 \
|
|
195
|
+
-e HF_HUB_DISABLE_PROGRESS_BARS=1 \
|
|
196
|
+
--entrypoint python3 \
|
|
197
|
+
"$VB_IMAGE" -c "
|
|
198
|
+
from huggingface_hub import snapshot_download
|
|
199
|
+
p = snapshot_download('$QWEN_MODEL', cache_dir='/hf-cache')
|
|
200
|
+
print('MODEL_READY:' + p)
|
|
201
|
+
" > /tmp/wr-qwen-download.log 2>&1; then
|
|
202
|
+
echo -e "${GREEN}OK${NC}"
|
|
203
|
+
chmod -R a+rX "$HF_CACHE" 2>/dev/null || true
|
|
204
|
+
else
|
|
205
|
+
echo -e "${RED}失败${NC}"
|
|
206
|
+
tail -10 /tmp/wr-qwen-download.log
|
|
207
|
+
echo "错误:千问模型下载失败,请检查网络(hf-mirror.com)后重试"
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# ── 启动容器 ──
|
|
212
|
+
echo "[8/8] 启动 Voicebox (千问) 容器..."
|
|
213
|
+
|
|
214
|
+
# 2026-09-15 修复(生产环境反馈):持久化整个 /app/data
|
|
215
|
+
# 含 voicebox.db(音色库)+ profiles + generations,避免容器重建后音色/数据丢失
|
|
216
|
+
mkdir -p "$VB_DIR/data/generations" "$VB_DIR/data/profiles" "$VB_DIR/data/cache"
|
|
217
|
+
chmod -R 777 "$VB_DIR/data" 2>/dev/null || true
|
|
218
|
+
|
|
219
|
+
docker rm -f voicebox-qwen >/dev/null 2>&1 || true
|
|
220
|
+
if ! docker run -d --name voicebox-qwen --restart unless-stopped \
|
|
221
|
+
-p "127.0.0.1:${VB_PORT}:${VB_CONTAINER_PORT}" \
|
|
222
|
+
-v "$VB_DIR/data:/app/data" \
|
|
223
|
+
-v "$HF_CACHE:/home/voicebox/.cache/huggingface" \
|
|
224
|
+
-e LOG_LEVEL=info \
|
|
225
|
+
-e NUMBA_CACHE_DIR=/tmp/numba_cache \
|
|
226
|
+
-e HF_HUB_OFFLINE=1 \
|
|
227
|
+
$DOCKER_GPU_ARGS \
|
|
228
|
+
--cpus 4 --memory 8g \
|
|
229
|
+
"$VB_IMAGE" >/dev/null 2>&1; then
|
|
230
|
+
echo -e "${RED}失败${NC}"
|
|
231
|
+
echo "错误:docker run 失败,执行 docker logs voicebox-qwen 查看日志"
|
|
232
|
+
exit 1
|
|
233
|
+
fi
|
|
234
|
+
echo " ✅ docker run 启动成功"
|
|
235
|
+
|
|
236
|
+
docker exec voicebox-qwen chmod -R 777 /app/data 2>/dev/null || true
|
|
237
|
+
|
|
238
|
+
# 等待 API 就绪
|
|
239
|
+
echo -n " 等待 Voicebox API 就绪..."
|
|
240
|
+
for i in $(seq 1 90); do
|
|
241
|
+
if curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1; then
|
|
242
|
+
echo -e " ${GREEN}OK${NC} (耗时 ${i} 次检查)"
|
|
243
|
+
break
|
|
244
|
+
fi
|
|
245
|
+
sleep 5
|
|
246
|
+
echo -n "."
|
|
247
|
+
done
|
|
248
|
+
|
|
249
|
+
if ! curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1; then
|
|
250
|
+
echo ""
|
|
251
|
+
echo -e "${RED}Voicebox 启动失败${NC}"
|
|
252
|
+
echo " 检查日志: docker logs voicebox-qwen"
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# ── 创建千问预设音色 profile(Vivian 中文女声)──
|
|
257
|
+
echo " ⚙ 配置千问预设音色(Vivian 中文女声)..."
|
|
258
|
+
|
|
259
|
+
mkdir -p "$HOME/.workrelay/speak-qwen"
|
|
260
|
+
PROFILE_FILE="$HOME/.workrelay/speak-qwen/profile_id"
|
|
261
|
+
|
|
262
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 5 "http://${VB_HOST}:${VB_PORT}/profiles" 2>/dev/null \
|
|
263
|
+
| python3 -c '
|
|
264
|
+
import json,sys
|
|
265
|
+
try:
|
|
266
|
+
d=json.load(sys.stdin)
|
|
267
|
+
for p in d:
|
|
268
|
+
if p.get("preset_engine")=="qwen_custom_voice" and p.get("preset_voice_id")=="Vivian":
|
|
269
|
+
print(p.get("id","")); break
|
|
270
|
+
except Exception:
|
|
271
|
+
print("")
|
|
272
|
+
' 2>/dev/null)
|
|
273
|
+
|
|
274
|
+
if [ -z "$EXIST_ID" ]; then
|
|
275
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 15 -X POST "http://${VB_HOST}:${VB_PORT}/profiles" \
|
|
276
|
+
-H "Content-Type: application/json" \
|
|
277
|
+
-d '{"name":"WorkRelay 千问女声","description":"WorkRelay 千问 Qwen3-TTS 预设中文女声","language":"zh","voice_type":"preset","preset_engine":"qwen_custom_voice","preset_voice_id":"Vivian"}' \
|
|
278
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if [ -n "$EXIST_ID" ]; then
|
|
282
|
+
echo "$EXIST_ID" > "$PROFILE_FILE"
|
|
283
|
+
echo " ✅ 千问预设音色已就绪"
|
|
284
|
+
else
|
|
285
|
+
echo " ⚠ 预设音色创建失败,可手动设 WR_VOICEBOX_PROFILE_ID 指定声音"
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
# ── TTS 互斥:停止其它 TTS 容器(TTS 只能启用一个)──
|
|
289
|
+
echo " 🔒 互斥处理:停止其它 TTS 容器..."
|
|
290
|
+
for other in voicebox voicebox-luxtts; do
|
|
291
|
+
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$other"; then
|
|
292
|
+
docker stop "$other" >/dev/null 2>&1 && echo " ⏹ 已停止 $other" || true
|
|
293
|
+
fi
|
|
294
|
+
done
|
|
295
|
+
|
|
296
|
+
# ── 部署脚本 & 配置 OpenClaw ──
|
|
297
|
+
echo -n "[收尾] 部署 TTS 桥接脚本 & 配置 OpenClaw... "
|
|
298
|
+
|
|
299
|
+
mkdir -p "$HOME/.workrelay/speak-qwen"
|
|
300
|
+
cp "$INSTALL_DIR/workrelay-tts.sh" "$HOME/.workrelay/speak-qwen/"
|
|
301
|
+
chmod +x "$HOME/.workrelay/speak-qwen/workrelay-tts.sh"
|
|
302
|
+
cp "$INSTALL_DIR/README.md" "$HOME/.workrelay/speak-qwen/" 2>/dev/null || true
|
|
303
|
+
|
|
304
|
+
if command -v openclaw &>/dev/null; then
|
|
305
|
+
TTS_SCRIPT="$HOME/.workrelay/speak-qwen/workrelay-tts.sh"
|
|
306
|
+
# 2026-09-14:openclaw 升级后 TTS 配置在顶层 tts.*(旧 messages.tts.* 已失效)
|
|
307
|
+
# 独立 provider 名 tts-local-cli-qwen,与 Kokoro(tts-local-cli)/LuxTTS(tts-local-cli-luxtts) 互不覆盖
|
|
308
|
+
openclaw config set tts.provider tts-local-cli-qwen 2>/dev/null || true
|
|
309
|
+
openclaw config set tts.providers.tts-local-cli-qwen.command "$TTS_SCRIPT" 2>/dev/null || true
|
|
310
|
+
openclaw config set tts.providers.tts-local-cli-qwen.args '["{{Text}}","{{OutputPath}}"]' 2>/dev/null || true
|
|
311
|
+
openclaw config set tts.providers.tts-local-cli-qwen.outputFormat wav 2>/dev/null || true
|
|
312
|
+
openclaw config set tts.providers.tts-local-cli-qwen.timeoutMs 300000 2>/dev/null || true
|
|
313
|
+
|
|
314
|
+
openclaw gateway restart 2>/dev/null || true
|
|
315
|
+
else
|
|
316
|
+
echo -e "${YELLOW}⚠ 未检测到 openclaw CLI,跳过自动配置${NC}"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
echo -e "${GREEN}OK${NC}"
|
|
320
|
+
echo ""
|
|
321
|
+
|
|
322
|
+
# ── 验证 ──
|
|
323
|
+
echo "============================================"
|
|
324
|
+
echo " 安装验证"
|
|
325
|
+
echo "============================================"
|
|
326
|
+
|
|
327
|
+
echo -n " Docker 容器: "
|
|
328
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q voicebox-qwen && echo -e "${GREEN}运行中${NC}" || echo -e "${RED}未运行${NC}"
|
|
329
|
+
|
|
330
|
+
echo -n " API 连通性: "
|
|
331
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1 \
|
|
332
|
+
&& echo -e "${GREEN}OK${NC} (http://${VB_HOST}:${VB_PORT})" \
|
|
333
|
+
|| echo -e "${RED}无法连接${NC}"
|
|
334
|
+
|
|
335
|
+
echo -n " 千问 profile: "
|
|
336
|
+
[ -f "$PROFILE_FILE" ] && echo -e "${GREEN}$(cat "$PROFILE_FILE")${NC}" || echo -e "${RED}缺失${NC}"
|
|
337
|
+
|
|
338
|
+
echo -n " TTS provider: "
|
|
339
|
+
openclaw config get tts.provider 2>/dev/null | head -1 || echo "(读取失败)"
|
|
340
|
+
|
|
341
|
+
echo ""
|
|
342
|
+
echo -e "${GREEN}✅ 千问 Qwen3-TTS 安装完成!${NC}"
|
|
343
|
+
echo " API 地址: http://${VB_HOST}:${VB_PORT}"
|
|
344
|
+
echo " 管理命令: docker ps | grep voicebox-qwen"
|
|
345
|
+
echo " 查看日志: docker logs -f voicebox-qwen"
|
|
346
|
+
echo ""
|
|
347
|
+
echo " 🎙️ 音色:Vivian(中文女声);其它预设:Serena/Eric/Dylan/Ryan/Aiden 等 9 种"
|
|
348
|
+
echo " ⏱️ 首次合成需加载 1.7B 模型到内存,会稍慢(CPU 10~60 秒/句)"
|
|
349
|
+
echo ""
|