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,118 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay TTS 桥接脚本 - 调用本地 Voicebox REST API
|
|
3
|
+
# 环境变量:
|
|
4
|
+
# WR_VOICEBOX_HOST - Voicebox 服务地址(默认 127.0.0.1)
|
|
5
|
+
# WR_VOICEBOX_PORT - Voicebox 端口(默认 17493)
|
|
6
|
+
# WR_VOICEBOX_PROFILE_ID - 声音 profile ID(可选,缺省时自动解析:
|
|
7
|
+
# ① ~/.workrelay/speak/profile_id(install.sh 写入)
|
|
8
|
+
# ② 从 Voicebox /profiles 查找 preset profile)
|
|
9
|
+
# WR_VOICEBOX_ENGINE - TTS 引擎(可选,缺省时按 profile 的 default_engine 自动匹配)
|
|
10
|
+
# WR_VOICEBOX_LANG - 语言(默认 zh)
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
TEXT="$1"
|
|
15
|
+
OUT="$2"
|
|
16
|
+
|
|
17
|
+
if [ -z "$TEXT" ] || [ -z "$OUT" ]; then
|
|
18
|
+
echo "usage: $0 <text> <output_path>" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
23
|
+
VB_PORT="${WR_VOICEBOX_PORT:-17493}"
|
|
24
|
+
VB_DOCKER_PORT="${WR_VOICEBOX_DOCKER_PORT:-17600}"
|
|
25
|
+
PROFILE_ID="${WR_VOICEBOX_PROFILE_ID:-}"
|
|
26
|
+
ENGINE="${WR_VOICEBOX_ENGINE:-}"
|
|
27
|
+
LANG="${WR_VOICEBOX_LANG:-zh}"
|
|
28
|
+
|
|
29
|
+
# 自动探测端口
|
|
30
|
+
BASE_URL="http://${VB_HOST}:${VB_PORT}"
|
|
31
|
+
curl -s --max-time 2 "${BASE_URL}/mcp" >/dev/null 2>&1 || {
|
|
32
|
+
BASE_URL="http://${VB_HOST}:${VB_DOCKER_PORT}"
|
|
33
|
+
curl -s --max-time 2 "${BASE_URL}/mcp" >/dev/null 2>&1 || {
|
|
34
|
+
echo "ERROR: Voicebox API 不可达 ($VB_HOST:$VB_PORT / $VB_DOCKER_PORT)" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# ── 2026-08-12:profile 自动解析(安装后开箱即用,无需手动传 ID)──
|
|
40
|
+
if [ -z "$PROFILE_ID" ] && [ -f "$HOME/.workrelay/speak/profile_id" ]; then
|
|
41
|
+
PROFILE_ID=$(cat "$HOME/.workrelay/speak/profile_id" 2>/dev/null | tr -d ' \n')
|
|
42
|
+
fi
|
|
43
|
+
if [ -z "$PROFILE_ID" ]; then
|
|
44
|
+
# 兜底:从 /profiles 找 preset profile(优先 kokoro + 中文)
|
|
45
|
+
PROFILE_ID=$(curl --noproxy '*' -s --max-time 5 "$BASE_URL/profiles" 2>/dev/null \
|
|
46
|
+
| python3 -c '
|
|
47
|
+
import json,sys
|
|
48
|
+
out=""
|
|
49
|
+
try:
|
|
50
|
+
d=json.load(sys.stdin)
|
|
51
|
+
for p in d:
|
|
52
|
+
if p.get("preset_engine")=="kokoro" and p.get("language")=="zh":
|
|
53
|
+
out=p.get("id",""); break
|
|
54
|
+
if not out:
|
|
55
|
+
for p in d:
|
|
56
|
+
if p.get("voice_type")=="preset":
|
|
57
|
+
out=p.get("id",""); break
|
|
58
|
+
print(out)
|
|
59
|
+
except Exception:
|
|
60
|
+
print("")
|
|
61
|
+
' 2>/dev/null)
|
|
62
|
+
fi
|
|
63
|
+
if [ -z "$PROFILE_ID" ]; then
|
|
64
|
+
echo "ERROR: 未找到可用的语音 profile(Voicebox /profiles 为空)" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# ── 2026-08-12:引擎未指定时按 profile 的 default_engine 自动匹配 ──
|
|
69
|
+
if [ -z "$ENGINE" ]; then
|
|
70
|
+
ENGINE=$(curl --noproxy '*' -s --max-time 5 "$BASE_URL/profiles/$PROFILE_ID" 2>/dev/null \
|
|
71
|
+
| python3 -c '
|
|
72
|
+
import json,sys
|
|
73
|
+
try:
|
|
74
|
+
d=json.load(sys.stdin)
|
|
75
|
+
print(d.get("default_engine") or d.get("preset_engine") or "qwen")
|
|
76
|
+
except Exception:
|
|
77
|
+
print("qwen")
|
|
78
|
+
' 2>/dev/null)
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# JSON-escape text
|
|
82
|
+
TEXT_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$TEXT")
|
|
83
|
+
|
|
84
|
+
# 提交生成请求
|
|
85
|
+
RESP=$(curl --noproxy '*' -s --max-time 15 -X POST "$BASE_URL/generate" \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-H "X-Voicebox-Client-Id: workrelay-tts" \
|
|
88
|
+
-d "{\"text\": $TEXT_JSON, \"profile_id\": \"$PROFILE_ID\", \"language\": \"$LANG\", \"engine\": \"$ENGINE\"}")
|
|
89
|
+
|
|
90
|
+
GEN_ID=$(echo "$RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
91
|
+
if [ -z "$GEN_ID" ]; then
|
|
92
|
+
echo "ERROR: Voicebox 生成提交失败: $RESP" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# 轮询等待完成(CPU 推理约 10-180 秒,视文本长度)
|
|
97
|
+
MAX_POLLS=180
|
|
98
|
+
for i in $(seq 1 $MAX_POLLS); do
|
|
99
|
+
sleep 1
|
|
100
|
+
ST=$(curl --noproxy '*' -s --max-time 10 "$BASE_URL/history/$GEN_ID" 2>/dev/null \
|
|
101
|
+
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("status",""))' 2>/dev/null)
|
|
102
|
+
if [ "$ST" = "completed" ]; then
|
|
103
|
+
break
|
|
104
|
+
fi
|
|
105
|
+
if [ "$ST" = "error" ] || [ "$ST" = "failed" ]; then
|
|
106
|
+
echo "ERROR: Voicebox 生成失败 (id=$GEN_ID, status=$ST)" >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
|
|
111
|
+
# 下载音频
|
|
112
|
+
curl --noproxy '*' -s --max-time 30 -o "$OUT" "$BASE_URL/audio/$GEN_ID"
|
|
113
|
+
if [ -s "$OUT" ]; then
|
|
114
|
+
exit 0
|
|
115
|
+
else
|
|
116
|
+
echo "ERROR: Voicebox 音频下载失败 (id=$GEN_ID)" >&2
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# WorkRelay 语音合成(LuxTTS 克隆声)
|
|
2
|
+
|
|
3
|
+
Agent 语音回复能力:Docker 部署 Voicebox(**LuxTTS 引擎**),内置**石头哥克隆声音**,音色自然。
|
|
4
|
+
|
|
5
|
+
## 特点
|
|
6
|
+
|
|
7
|
+
- **独立容器**:容器名 `voicebox-luxtts`,端口 `17601`,与 Kokoro 版(`voicebox` / 17493)**互不干扰,可同时安装**
|
|
8
|
+
- **克隆声音**:安装时自动创建克隆 profile(内置 4 个声音样本),开箱即有"石头哥克隆声"
|
|
9
|
+
- **引擎固定**:LuxTTS 为纯克隆声音引擎(无预设音色),桥接脚本固定 `engine=luxtts`
|
|
10
|
+
- **生产可用**:镜像含 LuxTTS 模型(1.1G),拉取即用,无需再下载模型
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
扩展能力页 → 语音合成(LuxTTS 克隆声)→ 启用,或手动执行:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bash install.sh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
前置条件:Linux + Docker(6GB 内存、10GB 磁盘)。
|
|
21
|
+
|
|
22
|
+
## 使用
|
|
23
|
+
|
|
24
|
+
安装后 OpenClaw 自动配置 `messages.tts.provider=tts-local-cli-luxtts`,
|
|
25
|
+
桥接脚本 `~/.workrelay/speak-luxtts/workrelay-tts.sh` 调用 `http://127.0.0.1:17601`。
|
|
26
|
+
|
|
27
|
+
声音 profile 记录在 `~/.workrelay/speak-luxtts/profile_id`,可用环境变量覆盖:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
WR_VOICEBOX_PROFILE_ID=<profile_id> # 指定其它克隆声音
|
|
31
|
+
WR_VOICEBOX_LUXTTS_PORT=17601 # 覆盖端口
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 与 Kokoro 版(workrelay-speak)关系
|
|
35
|
+
|
|
36
|
+
| 项 | workrelay-speak | workrelay-speak-luxtts |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| 容器名 | voicebox | voicebox-luxtts |
|
|
39
|
+
| 端口 | 17493/17600 | 17601 |
|
|
40
|
+
| 引擎 | Kokoro-82M | LuxTTS |
|
|
41
|
+
| 声音 | 50+ 预设音色 | 石头哥克隆声(可换) |
|
|
42
|
+
| OpenClaw provider | tts-local-cli | tts-local-cli-luxtts |
|
|
43
|
+
|
|
44
|
+
两者可共存,OpenClaw 当前生效的 TTS 由 `messages.tts.provider` 决定(后启用者生效)。
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorkRelay 语音合成(LuxTTS 克隆声)一键安装脚本
|
|
3
|
+
# 独立容器 voicebox-luxtts(端口 17601),拉取含 LuxTTS 模型的阿里镜像,
|
|
4
|
+
# 自动创建"石头哥克隆声音"profile(内置样本),配置 OpenClaw TTS。
|
|
5
|
+
# 2026-08-15 新增能力:与 workrelay-speak(Kokoro)互不干扰,可同时安装。
|
|
6
|
+
set -eo pipefail
|
|
7
|
+
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
NC='\033[0m'
|
|
12
|
+
|
|
13
|
+
VB_DIR="$HOME/.workrelay/voicebox-luxtts"
|
|
14
|
+
VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
|
|
15
|
+
# 独立端口 17601(Kokoro 版占用 17493/17600,避免冲突)
|
|
16
|
+
VB_PORT="${WR_VOICEBOX_LUXTTS_PORT:-17601}"
|
|
17
|
+
VB_CONTAINER_PORT=17493
|
|
18
|
+
# 阿里云镜像:石头哥推送后使用(含 LuxTTS 模型 + Kokoro + 英文补丁)
|
|
19
|
+
# 2026-09-14 双源:国内=阿里云公共仓库;国际=GitHub Container Registry (ghcr.io)
|
|
20
|
+
# 2026-09-14 改名为 dlinkclaw-tts(统一仓库名,tag 区分模型)
|
|
21
|
+
IMG_TAG="${WR_IMAGE_TAG:-luxtts-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
|
+
INSTALL_DIR="$(dirname "$(readlink -f "$0")")"
|
|
27
|
+
|
|
28
|
+
# ── 2026-09-14 镜像双源(国内=阿里云公共仓库 / 国际=ghcr.io)──
|
|
29
|
+
# 按 WR_IMAGE_REGION(auto|cn|intl)优先尝试,失败自动回退另一源;WR_VOICEBOX_IMAGE 可显式覆盖。
|
|
30
|
+
IMAGE_CANDIDATES=()
|
|
31
|
+
if [ -n "${WR_VOICEBOX_IMAGE:-}" ]; then
|
|
32
|
+
IMAGE_CANDIDATES=("$WR_VOICEBOX_IMAGE")
|
|
33
|
+
else
|
|
34
|
+
# 2026-09-14:国内源暂未发布(IMG_CN 为空则自动跳过),默认国际源优先;国内源上架后自动生效
|
|
35
|
+
case "${WR_IMAGE_REGION:-auto}" in
|
|
36
|
+
cn) CAND_PRIMARY="$IMG_CN"; CAND_SECONDARY="$IMG_INTL" ;;
|
|
37
|
+
intl) CAND_PRIMARY="$IMG_INTL"; CAND_SECONDARY="$IMG_CN" ;;
|
|
38
|
+
*) CAND_PRIMARY="$IMG_INTL"; CAND_SECONDARY="$IMG_CN" ;;
|
|
39
|
+
esac
|
|
40
|
+
[ -n "$CAND_PRIMARY" ] && IMAGE_CANDIDATES+=("$CAND_PRIMARY")
|
|
41
|
+
[ -n "$CAND_SECONDARY" ] && IMAGE_CANDIDATES+=("$CAND_SECONDARY")
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
pull_image_with_fallback() {
|
|
45
|
+
local log="$1"
|
|
46
|
+
for img in "${IMAGE_CANDIDATES[@]}"; do
|
|
47
|
+
echo " → 尝试镜像源: $img"
|
|
48
|
+
if docker pull "$img" > "$log" 2>&1; then
|
|
49
|
+
VB_IMAGE="$img"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
echo " ⚠ 该源拉取失败,尝试下一个..."
|
|
53
|
+
done
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
echo "============================================"
|
|
59
|
+
echo " WorkRelay 语音合成(LuxTTS 克隆声)- 一键安装"
|
|
60
|
+
echo "============================================"
|
|
61
|
+
echo ""
|
|
62
|
+
|
|
63
|
+
# ── 系统检查 ──
|
|
64
|
+
echo -n "[1/6] 检查系统环境... "
|
|
65
|
+
|
|
66
|
+
OS="$(uname -s)"
|
|
67
|
+
case "$OS" in
|
|
68
|
+
Linux) ;;
|
|
69
|
+
*)
|
|
70
|
+
echo -e "${RED}失败${NC}"
|
|
71
|
+
echo "错误:当前仅支持 Linux(TTS 需要 Docker)"
|
|
72
|
+
exit 1
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
export PATH="$PATH:/snap/bin:/usr/local/bin"
|
|
77
|
+
|
|
78
|
+
if ! command -v docker &>/dev/null; then
|
|
79
|
+
echo -e "${RED}失败${NC}"
|
|
80
|
+
echo "错误:未找到 Docker,请先安装 Docker"
|
|
81
|
+
echo " Ubuntu: sudo apt install docker.io docker-compose-v2"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
86
|
+
# 2026-08-18 增强:docker 权限多级自适应(尽量零 sudo,任何机器可用):
|
|
87
|
+
# ① 用户已在 docker 组(含会话未刷新)→ sg docker 切组执行
|
|
88
|
+
# ② 免密 sudo(sudo -n)可用 → sudo 包装
|
|
89
|
+
# ③ 都不行 → 给出一键修复指引,而不是干失败
|
|
90
|
+
if sg docker -c "docker info" &>/dev/null 2>&1; then
|
|
91
|
+
echo " ℹ 用户已加入 docker 组,通过 sg docker 执行(无需 sudo)"
|
|
92
|
+
docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
|
|
93
|
+
elif sudo -n docker info &>/dev/null 2>&1; then
|
|
94
|
+
echo " ⚠ 当前用户无 docker 组权限,检测到免密 sudo,自动使用 sudo 执行 docker"
|
|
95
|
+
docker() { sudo docker "$@"; }
|
|
96
|
+
else
|
|
97
|
+
echo -e "${RED}失败${NC}"
|
|
98
|
+
echo "错误:Docker 未运行或无权限。请先执行一次以下命令:"
|
|
99
|
+
echo ""
|
|
100
|
+
echo " ① 启动 Docker:sudo systemctl start docker"
|
|
101
|
+
echo " ② 把当前用户加入 docker 组(避免以后每次要密码):"
|
|
102
|
+
echo " sudo usermod -aG docker \$USER"
|
|
103
|
+
echo " ③ 重启 openclaw 服务(或重新登录会话),然后重新安装"
|
|
104
|
+
echo ""
|
|
105
|
+
echo " 备选:为当前用户配置免密 sudo(sudo visudo 添加:\$USER ALL=(ALL) NOPASSWD: ALL)"
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
echo -e "${GREEN}OK${NC} ($(docker --version | head -1), $(uname -o))"
|
|
111
|
+
|
|
112
|
+
# snap docker 项目目录适配(与 workrelay-speak 一致)
|
|
113
|
+
if [ -d /var/snap/docker/common ]; then
|
|
114
|
+
VB_DIR="/var/snap/docker/common/workrelay-voicebox-luxtts"
|
|
115
|
+
echo " ℹ 检测到 snap docker,项目目录使用 $VB_DIR(AppArmor 放行读写)"
|
|
116
|
+
# 2026-08-18:无免密 sudo 时给出手动命令指引(不依赖 sudo 密码)。
|
|
117
|
+
if [ -d "$VB_DIR" ]; then
|
|
118
|
+
sudo -n chown -R "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
119
|
+
elif ! sudo -n mkdir -p "$VB_DIR" 2>/dev/null; then
|
|
120
|
+
echo -e "${RED}失败${NC}"
|
|
121
|
+
echo "错误:无法创建项目目录 $VB_DIR(需要 root 权限)。"
|
|
122
|
+
echo "请手动执行一次以下命令后重新安装:"
|
|
123
|
+
echo " sudo mkdir -p $VB_DIR && sudo chown \$USER:\$USER $VB_DIR"
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
sudo -n chown "$(id -u):$(id -g)" "$VB_DIR" 2>/dev/null || true
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ── 磁盘空间 ──
|
|
130
|
+
echo -n "[2/6] 检查磁盘空间... "
|
|
131
|
+
DF_TARGET="$VB_DIR"; [ -d "$DF_TARGET" ] || DF_TARGET="$HOME"
|
|
132
|
+
AVAIL=$(df -BM "$DF_TARGET" | tail -1 | awk '{print $4}' | sed 's/M//')
|
|
133
|
+
if [ "$AVAIL" -lt 10000 ]; then
|
|
134
|
+
echo -e "${RED}不足${NC}"
|
|
135
|
+
echo "错误:可用空间 ${AVAIL}MB,至少需要 10GB(Docker 镜像 + 模型 ~6GB)"
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
echo -e "${GREEN}OK${NC} (可用 ${AVAIL}MB)"
|
|
139
|
+
|
|
140
|
+
# ── 内存检查 ──
|
|
141
|
+
echo -n "[3/6] 检查内存... "
|
|
142
|
+
# 2026-09-15 修复:中文 locale 下 free -m 输出“内存:”,awk 匹配 Mem: 失败会误报内存不足 → 强制 LANG=C
|
|
143
|
+
RAM=$(LANG=C free -m | awk '/^Mem:/{print $2}')
|
|
144
|
+
if [ "$RAM" -lt 6000 ]; then
|
|
145
|
+
echo -e "${RED}不足 (${RAM}MB)${NC}"
|
|
146
|
+
echo "错误:至少需要 6GB 内存,当前仅 ${RAM}MB"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
echo -e "${GREEN}OK${NC} (${RAM}MB)"
|
|
150
|
+
|
|
151
|
+
# ── 端口检查 ──
|
|
152
|
+
echo -n "[4/6] 检查端口 $VB_PORT... "
|
|
153
|
+
if ss -tlnp 2>/dev/null | grep -q ":$VB_PORT "; then
|
|
154
|
+
echo -e "${RED}已被占用${NC}"
|
|
155
|
+
echo "错误:端口 $VB_PORT 已被占用,请先停止占用进程或设置 WR_VOICEBOX_LUXTTS_PORT 换端口"
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
echo -e "${GREEN}空闲${NC}"
|
|
159
|
+
|
|
160
|
+
# ── 拉取镜像 ──
|
|
161
|
+
echo -n "[5/6] 拉取 Voicebox (LuxTTS) 镜像... "
|
|
162
|
+
mkdir -p "$VB_DIR"
|
|
163
|
+
if pull_image_with_fallback /tmp/wr-pull-luxtts.log; then
|
|
164
|
+
echo -e "${GREEN}OK${NC} ($VB_IMAGE)"
|
|
165
|
+
else
|
|
166
|
+
echo -e "${RED}失败${NC}"
|
|
167
|
+
tail -5 /tmp/wr-pull-luxtts.log
|
|
168
|
+
echo "错误:所有镜像源均拉取失败,检查网络连接(国内=阿里云公共仓库 / 国际=ghcr.io)"
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# ── 启动容器 ──
|
|
173
|
+
echo "[6/6] 启动 Voicebox (LuxTTS) 容器..."
|
|
174
|
+
|
|
175
|
+
# 2026-09-15 修复(生产环境反馈):持久化整个 /app/data
|
|
176
|
+
# 含 voicebox.db(音色库)+ profiles + generations,避免容器重建后音色/数据丢失
|
|
177
|
+
mkdir -p "$VB_DIR/data/generations" "$VB_DIR/data/profiles" "$VB_DIR/data/cache"
|
|
178
|
+
chmod -R 777 "$VB_DIR/data" 2>/dev/null || true
|
|
179
|
+
|
|
180
|
+
docker rm -f voicebox-luxtts >/dev/null 2>&1 || true
|
|
181
|
+
if ! docker run -d --name voicebox-luxtts --restart unless-stopped \
|
|
182
|
+
-p "127.0.0.1:${VB_PORT}:${VB_CONTAINER_PORT}" \
|
|
183
|
+
-v "$VB_DIR/data:/app/data" \
|
|
184
|
+
-e LOG_LEVEL=info \
|
|
185
|
+
-e NUMBA_CACHE_DIR=/tmp/numba_cache \
|
|
186
|
+
--cpus 4 --memory 8g \
|
|
187
|
+
"$VB_IMAGE" >/dev/null 2>&1; then
|
|
188
|
+
echo -e "${RED}失败${NC}"
|
|
189
|
+
echo "错误:docker run 失败,执行 docker logs voicebox-luxtts 查看日志"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
echo " ✅ docker run 启动成功"
|
|
193
|
+
|
|
194
|
+
# 2026-08-15 权限兜底:snap docker 的 bind mount 目录在容器内可能显示 root:root 755,
|
|
195
|
+
# voicebox 用户(uid 999)无法写入 /app/data/generations 导致生成保存失败。
|
|
196
|
+
# 容器内 chmod 777 兜底(docker run 后立即执行,幂等)。
|
|
197
|
+
docker exec voicebox-luxtts chmod -R 777 /app/data 2>/dev/null || true
|
|
198
|
+
|
|
199
|
+
# 等待 API 就绪
|
|
200
|
+
echo -n " 等待 Voicebox API 就绪..."
|
|
201
|
+
for i in $(seq 1 90); do
|
|
202
|
+
if curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1; then
|
|
203
|
+
echo -e " ${GREEN}OK${NC} (耗时 ${i} 次检查)"
|
|
204
|
+
break
|
|
205
|
+
fi
|
|
206
|
+
sleep 5
|
|
207
|
+
echo -n "."
|
|
208
|
+
done
|
|
209
|
+
|
|
210
|
+
if ! curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1; then
|
|
211
|
+
echo ""
|
|
212
|
+
echo -e "${RED}Voicebox 启动失败${NC}"
|
|
213
|
+
echo " 检查日志: docker logs voicebox-luxtts"
|
|
214
|
+
exit 1
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# ── 创建石头哥克隆声音 profile ──
|
|
218
|
+
echo " ⚙ 配置克隆声音(石头哥克隆声)..."
|
|
219
|
+
|
|
220
|
+
mkdir -p "$HOME/.workrelay/speak-luxtts"
|
|
221
|
+
PROFILE_FILE="$HOME/.workrelay/speak-luxtts/profile_id"
|
|
222
|
+
SAMPLE_DIR="$INSTALL_DIR/samples"
|
|
223
|
+
|
|
224
|
+
# 查已存在的同款克隆 profile(按名字匹配,避免重复创建)
|
|
225
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 5 "http://${VB_HOST}:${VB_PORT}/profiles" 2>/dev/null \
|
|
226
|
+
| python3 -c '
|
|
227
|
+
import json,sys
|
|
228
|
+
try:
|
|
229
|
+
d=json.load(sys.stdin)
|
|
230
|
+
for p in d:
|
|
231
|
+
if p.get("name")=="石头哥克隆声" and p.get("voice_type")=="cloned":
|
|
232
|
+
print(p.get("id","")); break
|
|
233
|
+
except Exception:
|
|
234
|
+
print("")
|
|
235
|
+
' 2>/dev/null)
|
|
236
|
+
|
|
237
|
+
if [ -z "$EXIST_ID" ]; then
|
|
238
|
+
# 创建克隆 profile
|
|
239
|
+
EXIST_ID=$(curl --noproxy '*' -s --max-time 10 -X POST "http://${VB_HOST}:${VB_PORT}/profiles" \
|
|
240
|
+
-H "Content-Type: application/json" \
|
|
241
|
+
-d '{"name":"石头哥克隆声","description":"WorkRelay LuxTTS 内置克隆声音(石头哥)","language":"zh","voice_type":"cloned"}' \
|
|
242
|
+
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null)
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
if [ -n "$EXIST_ID" ]; then
|
|
246
|
+
# 上传样本(file + reference_text 多文件)
|
|
247
|
+
SAMPLE_COUNT=0
|
|
248
|
+
while IFS='|' read -r fname reftext; do
|
|
249
|
+
[ -z "$fname" ] && continue
|
|
250
|
+
fpath="$SAMPLE_DIR/$fname"
|
|
251
|
+
if [ -f "$fpath" ]; then
|
|
252
|
+
if curl --noproxy '*' -s --max-time 15 -X POST "http://${VB_HOST}:${VB_PORT}/profiles/${EXIST_ID}/samples" \
|
|
253
|
+
-F "file=@${fpath}" -F "reference_text=${reftext}" \
|
|
254
|
+
| python3 -c 'import json,sys; d=json.load(sys.stdin); print("ok" if d.get("id") else "fail")' 2>/dev/null | grep -q ok; then
|
|
255
|
+
SAMPLE_COUNT=$((SAMPLE_COUNT+1))
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
done < "$SAMPLE_DIR/reference_text.txt"
|
|
259
|
+
echo " ✅ 克隆声音 profile 已就绪(${SAMPLE_COUNT} 个样本)"
|
|
260
|
+
echo "$EXIST_ID" > "$PROFILE_FILE"
|
|
261
|
+
else
|
|
262
|
+
echo " ⚠ 克隆 profile 创建失败,可手动设 WR_VOICEBOX_PROFILE_ID 指定声音"
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
# ── TTS 互斥:停止其它 TTS 容器(2026-09-14:TTS 只能启用一个)──
|
|
266
|
+
echo " 🔒 互斥处理:停止其它 TTS 容器..."
|
|
267
|
+
for other in voicebox voicebox-qwen; do
|
|
268
|
+
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$other"; then
|
|
269
|
+
docker stop "$other" >/dev/null 2>&1 && echo " ⏹ 已停止 $other" || true
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
# ── 部署脚本 & 配置 OpenClaw ──
|
|
274
|
+
echo -n "[收尾] 部署 TTS 桥接脚本 & 配置 OpenClaw... "
|
|
275
|
+
|
|
276
|
+
mkdir -p "$HOME/.workrelay/speak-luxtts"
|
|
277
|
+
cp "$INSTALL_DIR/workrelay-tts.sh" "$HOME/.workrelay/speak-luxtts/"
|
|
278
|
+
chmod +x "$HOME/.workrelay/speak-luxtts/workrelay-tts.sh"
|
|
279
|
+
cp "$INSTALL_DIR/README.md" "$HOME/.workrelay/speak-luxtts/" 2>/dev/null || true
|
|
280
|
+
|
|
281
|
+
if command -v openclaw &>/dev/null; then
|
|
282
|
+
TTS_SCRIPT="$HOME/.workrelay/speak-luxtts/workrelay-tts.sh"
|
|
283
|
+
# 独立 provider 名(tts-local-cli-luxtts),与 Kokoro 版 tts-local-cli 互不覆盖
|
|
284
|
+
# 2026-09-14:openclaw 升级后 TTS 配置在顶层 tts.*(旧 messages.tts.* 已失效)
|
|
285
|
+
openclaw config set tts.provider tts-local-cli-luxtts 2>/dev/null || true
|
|
286
|
+
openclaw config set tts.providers.tts-local-cli-luxtts.command "$TTS_SCRIPT" 2>/dev/null || true
|
|
287
|
+
openclaw config set tts.providers.tts-local-cli-luxtts.args '["{{Text}}","{{OutputPath}}"]' 2>/dev/null || true
|
|
288
|
+
openclaw config set tts.providers.tts-local-cli-luxtts.outputFormat wav 2>/dev/null || true
|
|
289
|
+
openclaw config set tts.providers.tts-local-cli-luxtts.timeoutMs 180000 2>/dev/null || true
|
|
290
|
+
|
|
291
|
+
# MCP 配置(独立 server 名 voicebox-luxtts)
|
|
292
|
+
openclaw config set mcp.servers.voicebox-luxtts.url "http://${VB_HOST}:${VB_PORT}/mcp" 2>/dev/null || true
|
|
293
|
+
openclaw config set mcp.servers.voicebox-luxtts.transport streamable-http 2>/dev/null || true
|
|
294
|
+
openclaw config set mcp.servers.voicebox-luxtts.timeout 60 2>/dev/null || true
|
|
295
|
+
|
|
296
|
+
openclaw gateway restart 2>/dev/null || true
|
|
297
|
+
else
|
|
298
|
+
echo -e "${YELLOW}⚠ 未检测到 openclaw CLI,跳过自动配置${NC}"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
echo -e "${GREEN}OK${NC}"
|
|
302
|
+
echo ""
|
|
303
|
+
|
|
304
|
+
# ── 验证 ──
|
|
305
|
+
echo "============================================"
|
|
306
|
+
echo " 安装验证"
|
|
307
|
+
echo "============================================"
|
|
308
|
+
|
|
309
|
+
echo -n " Docker 容器: "
|
|
310
|
+
docker ps --format '{{.Names}}' 2>/dev/null | grep -q voicebox-luxtts && echo -e "${GREEN}运行中${NC}" || echo -e "${RED}未运行${NC}"
|
|
311
|
+
|
|
312
|
+
echo -n " API 连通性: "
|
|
313
|
+
curl -s --max-time 2 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1 \
|
|
314
|
+
&& echo -e "${GREEN}OK${NC} (http://${VB_HOST}:${VB_PORT})" \
|
|
315
|
+
|| echo -e "${RED}无法连接${NC}"
|
|
316
|
+
|
|
317
|
+
echo -n " TTS 桥接脚本: "
|
|
318
|
+
[ -f "$HOME/.workrelay/speak-luxtts/workrelay-tts.sh" ] && echo -e "${GREEN}已部署${NC}" || echo -e "${RED}缺失${NC}"
|
|
319
|
+
|
|
320
|
+
echo -n " 克隆声音 profile: "
|
|
321
|
+
[ -f "$PROFILE_FILE" ] && echo -e "${GREEN}$(cat "$PROFILE_FILE")${NC}" || echo -e "${RED}缺失${NC}"
|
|
322
|
+
|
|
323
|
+
echo ""
|
|
324
|
+
echo -e "${GREEN}✅ Voicebox (LuxTTS) 安装完成!${NC}"
|
|
325
|
+
echo " API 地址: http://${VB_HOST}:${VB_PORT}"
|
|
326
|
+
echo " 管理命令: docker ps | grep voicebox-luxtts"
|
|
327
|
+
echo " 查看日志: docker logs -f voicebox-luxtts"
|
|
328
|
+
echo ""
|
|
329
|
+
echo " 🎙️ 声音:内置石头哥克隆声(LuxTTS 引擎)"
|
|
330
|
+
echo ""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workrelay-speak-luxtts",
|
|
3
|
+
"displayName": "语音合成(LuxTTS 克隆声)",
|
|
4
|
+
"description": "Agent 语音回复:Docker 部署 Voicebox(LuxTTS 引擎),内置石头哥克隆声音,音色自然,与其它 TTS 互斥(同时只能启用一个)",
|
|
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": "外网可达(镜像仓库)",
|
|
44
|
+
"expect": "能访问阿里云镜像仓库",
|
|
45
|
+
"check": "curl -sI --max-time 5 https://crpi-3zj44dlldca7vawm.cn-hangzhou.personal.cr.aliyuncs.com >/dev/null 2>&1",
|
|
46
|
+
"hint": "无法访问阿里云镜像仓库,将无法拉取 Docker 镜像"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "registry",
|
|
50
|
+
"label": "镜像仓库可达(ghcr.io / 国内公共仓库,自动回退)",
|
|
51
|
+
"expect": "能访问阿里云镜像仓库",
|
|
52
|
+
"check": "timeout 4 docker manifest inspect ghcr.io/rockychen2016/dlinkclaw-tts:luxtts-20260914 >/dev/null 2>&1 || timeout 4 docker manifest inspect registry.cn-hangzhou.aliyuncs.com/shiduo/dlinkclaw-tts:luxtts-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
|
+
}
|
package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav
ADDED
|
Binary file
|
package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav
ADDED
|
Binary file
|
package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav
ADDED
|
Binary file
|