dlinkclaw-openclaw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/README.en.md +46 -0
  3. package/README.md +15 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.js +1 -0
  6. package/dist/package.json +95 -0
  7. package/dist/src/account.d.ts +54 -0
  8. package/dist/src/account.js +394 -0
  9. package/dist/src/channel.d.ts +17 -0
  10. package/dist/src/channel.js +153 -0
  11. package/dist/src/client/client-manager.d.ts +4 -0
  12. package/dist/src/client/client-manager.js +10 -0
  13. package/dist/src/client/const.d.ts +37 -0
  14. package/dist/src/client/const.js +155 -0
  15. package/dist/src/client/crypto.d.ts +47 -0
  16. package/dist/src/client/crypto.js +260 -0
  17. package/dist/src/client/dl-client.d.ts +117 -0
  18. package/dist/src/client/dl-client.js +588 -0
  19. package/dist/src/client/dl-http.d.ts +27 -0
  20. package/dist/src/client/dl-http.js +223 -0
  21. package/dist/src/client/media-handler.d.ts +21 -0
  22. package/dist/src/client/media-handler.js +196 -0
  23. package/dist/src/client/message.d.ts +96 -0
  24. package/dist/src/client/message.js +1211 -0
  25. package/dist/src/client/timeout.d.ts +20 -0
  26. package/dist/src/client/timeout.js +36 -0
  27. package/dist/src/client/types.d.ts +226 -0
  28. package/dist/src/client/types.js +1 -0
  29. package/dist/src/monitor.d.ts +18 -0
  30. package/dist/src/monitor.js +322 -0
  31. package/dist/src/outbound.d.ts +6 -0
  32. package/dist/src/outbound.js +78 -0
  33. package/dist/src/policy.d.ts +42 -0
  34. package/dist/src/policy.js +216 -0
  35. package/dist/src/runtime.d.ts +9 -0
  36. package/dist/src/runtime.js +65 -0
  37. package/dist/src/services/gateway-rpc.d.ts +37 -0
  38. package/dist/src/services/gateway-rpc.js +37 -0
  39. package/dist/src/services/index.d.ts +41 -0
  40. package/dist/src/services/index.js +245 -0
  41. package/dist/src/services/services.d.ts +169 -0
  42. package/dist/src/services/services.js +1160 -0
  43. package/dist/src/services/types.d.ts +391 -0
  44. package/dist/src/services/types.js +5 -0
  45. package/dist/src/services/ws-client.d.ts +186 -0
  46. package/dist/src/services/ws-client.js +896 -0
  47. package/dist/src/utils.d.ts +20 -0
  48. package/dist/src/utils.js +128 -0
  49. package/ext_capabilities/workrelay-speak/CLONE_GUIDE.md +129 -0
  50. package/ext_capabilities/workrelay-speak/HARDWARE.md +84 -0
  51. package/ext_capabilities/workrelay-speak/QWEN_GUIDE.md +151 -0
  52. package/ext_capabilities/workrelay-speak/README.md +79 -0
  53. package/ext_capabilities/workrelay-speak/__pycache__/patch-kokoro-en.cpython-312.pyc +0 -0
  54. package/ext_capabilities/workrelay-speak/install.sh +329 -0
  55. package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
  56. package/ext_capabilities/workrelay-speak/manifest.json +63 -0
  57. package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
  58. package/ext_capabilities/workrelay-speak/status.sh +111 -0
  59. package/ext_capabilities/workrelay-speak/workrelay-tts.sh +118 -0
  60. package/ext_capabilities/workrelay-speak-luxtts/README.md +44 -0
  61. package/ext_capabilities/workrelay-speak-luxtts/install.sh +330 -0
  62. package/ext_capabilities/workrelay-speak-luxtts/manifest.json +63 -0
  63. package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav +0 -0
  64. package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav +0 -0
  65. package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav +0 -0
  66. package/ext_capabilities/workrelay-speak-luxtts/samples/reference_text.txt +4 -0
  67. package/ext_capabilities/workrelay-speak-luxtts/samples/stone-sample-6.4s.wav +0 -0
  68. package/ext_capabilities/workrelay-speak-luxtts/status.sh +115 -0
  69. package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
  70. package/ext_capabilities/workrelay-speak-qwen/README.md +62 -0
  71. package/ext_capabilities/workrelay-speak-qwen/install.sh +349 -0
  72. package/ext_capabilities/workrelay-speak-qwen/manifest.json +63 -0
  73. package/ext_capabilities/workrelay-speak-qwen/status.sh +106 -0
  74. package/ext_capabilities/workrelay-speak-qwen/workrelay-tts.sh +98 -0
  75. package/ext_capabilities/workrelay-transcribe/README.md +76 -0
  76. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe.cpython-312.pyc +0 -0
  77. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe_server.cpython-312.pyc +0 -0
  78. package/ext_capabilities/workrelay-transcribe/install.sh +227 -0
  79. package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
  80. package/ext_capabilities/workrelay-transcribe/status.sh +63 -0
  81. package/ext_capabilities/workrelay-transcribe/transcribe.py +56 -0
  82. package/ext_capabilities/workrelay-transcribe/transcribe_server.py +116 -0
  83. package/ext_capabilities/workrelay-transcribe/voicebox-transcribe.sh +88 -0
  84. package/openclaw.plugin.json +142 -0
  85. package/package.json +95 -0
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "workrelay-speak-qwen",
3
+ "displayName": "语音合成(千问 Qwen3-TTS)",
4
+ "description": "Agent 语音回复:Docker 部署 Voicebox(Qwen3-TTS CustomVoice 引擎),9 种预设音色,中英日韩多语种,音质自然、断句好,适合视频配音。独立容器(端口 17602);与 Kokoro / LuxTTS 互斥,启用本能力会自动停止其它 TTS。首次安装需下载模型约 4.6GB",
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 ③ openclaw gateway restart 后重试"
26
+ },
27
+ {
28
+ "id": "memory",
29
+ "label": "内存 ≥ 6GB(建议 8GB+)",
30
+ "expect": "≥ 6GB 可用",
31
+ "check": "[ $(LANG=C free -m | awk '/^Mem:/{print $2}') -ge 6144 ]",
32
+ "hint": "当前内存不足,Qwen3-TTS 1.7B 至少需要 6GB 内存,建议 8GB 以上"
33
+ },
34
+ {
35
+ "id": "disk",
36
+ "label": "磁盘 ≥ 8GB",
37
+ "expect": "≥ 8GB 可用(模型 4.6GB + 运行缓存)",
38
+ "check": "[ $(df -BM \"$HOME\" | tail -1 | awk '{print $4}' | sed 's/M//') -ge 8192 ]",
39
+ "hint": "可用空间不足,Qwen3-TTS 模型约需 4.6GB,建议预留 8GB"
40
+ },
41
+ {
42
+ "id": "registry",
43
+ "label": "镜像仓库可达(ghcr.io / 国内公共仓库,自动回退)",
44
+ "expect": "能访问阿里云镜像仓库",
45
+ "check": "timeout 4 docker manifest inspect ghcr.io/rockychen2016/dlinkclaw-tts:qwen-20260914 >/dev/null 2>&1 || timeout 4 docker manifest inspect registry.cn-hangzhou.aliyuncs.com/shiduo/dlinkclaw-tts:qwen-20260914 >/dev/null 2>&1",
46
+ "hint": "镜像源不可达。国际源: ghcr.io(GitHub Container Registry,公开免登录);国内源: registry.cn-hangzhou.aliyuncs.com(待发布)。安装时自动按区域优先并回退"
47
+ },
48
+ {
49
+ "id": "model_source",
50
+ "label": "模型源可达(HF 国内镜像)",
51
+ "expect": "能访问 hf-mirror.com",
52
+ "check": "timeout 4 curl -sI https://hf-mirror.com >/dev/null 2>&1 || timeout 4 curl -sI https://huggingface.co >/dev/null 2>&1",
53
+ "hint": "无法访问模型源(国内 hf-mirror.com / 国际 huggingface.co),Qwen 模型(4.6GB)将无法下载,请检查网络"
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 合成(Qwen 1.7B 约 10~60 秒/句,慢但可用);有 NVIDIA GPU 时自动启用 CUDA 加速"
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+ # WorkRelay 语音合成(千问 Qwen3-TTS)状态检查
3
+ # 返回值: 0=已启用, 1=未安装, 2=部分安装, 3=容器存在但API未就绪
4
+ # 2026-09-14 新增能力:独立容器 voicebox-qwen + 端口 17602 + provider tts-local-cli-qwen
5
+ # 2026-09-14 互斥:TTS 三选一——读 openclaw.json 的 tts.provider,不是本引擎则视为"未启用"
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_QWEN_PORT:-17602}"
15
+
16
+ export PATH="$PATH:/snap/bin:/usr/local/bin"
17
+ if ! docker info &>/dev/null 2>&1; then
18
+ if sg docker -c "docker info" &>/dev/null 2>&1; then
19
+ docker() { sg docker -c "docker $(printf '%q ' "$@")"; }
20
+ elif sudo -n docker info &>/dev/null 2>&1; then
21
+ docker() { sudo docker "$@"; }
22
+ fi
23
+ fi
24
+
25
+ # 检查1: Docker
26
+ check_docker=0
27
+ docker info &>/dev/null 2>&1 && check_docker=1 || true
28
+
29
+ # 检查2: voicebox-qwen 容器
30
+ check_container=0
31
+ docker ps --format '{{.Names}}' 2>/dev/null | grep -q "voicebox-qwen" && check_container=1 || true
32
+
33
+ # 检查3: API
34
+ check_api=0
35
+ curl -s --max-time 3 "http://${VB_HOST}:${VB_PORT}/mcp" >/dev/null 2>&1 && check_api=1 || true
36
+
37
+ # 检查4: TTS 桥接脚本
38
+ check_tts_script=0
39
+ [ -f "$HOME/.workrelay/speak-qwen/workrelay-tts.sh" ] && check_tts_script=1 || true
40
+
41
+ # 检查5: 千问预设音色 profile
42
+ check_profile=0
43
+ [ -f "$HOME/.workrelay/speak-qwen/profile_id" ] && check_profile=1 || true
44
+
45
+ # 检查6: OpenClaw TTS 配置(2026-09-14 新版顶层路径 tts.providers.*,不再读旧 messages.tts.*)
46
+ # 2026-09-14 互斥判定:仅当 tts.provider == tts-local-cli-qwen 时才算"已启用"
47
+ check_config=0
48
+ check_provider=0
49
+ OC_JSON="${OPENCLAW_HOME:-$HOME/.openclaw}/openclaw.json"
50
+ if [ -f "$OC_JSON" ]; then
51
+ grep -q '"tts-local-cli-qwen"' "$OC_JSON" && check_config=1 || true
52
+ # 当前生效的 TTS provider(互斥判定):取 "provider": "xxx" 中第一个(tts 段内)
53
+ CUR_PROVIDER=$(python3 -c '
54
+ import json,sys
55
+ try:
56
+ c=json.load(open(sys.argv[1]))
57
+ print((c.get("tts") or {}).get("provider",""))
58
+ except Exception:
59
+ print("")
60
+ ' "$OC_JSON" 2>/dev/null)
61
+ [ "$CUR_PROVIDER" = "tts-local-cli-qwen" ] && check_provider=1 || true
62
+ fi
63
+
64
+ # ── 汇总状态 ──
65
+ total=$((check_docker + check_container + check_api + check_tts_script + check_profile + check_config))
66
+
67
+ case $total in
68
+ 6)
69
+ # 已安装齐全:再按互斥判定是否"启用"(provider 指向本引擎)
70
+ if [ "$check_provider" -eq 1 ]; then
71
+ status="enabled"; desc="语音合成(千问 Qwen3-TTS)已启用"; exit_code=$STATUS_OK
72
+ else
73
+ status="disabled"; desc="已安装但未启用(当前语音合成为其它引擎,TTS 只能启用一个)"; exit_code=$STATUS_NOT_INSTALLED
74
+ fi
75
+ ;;
76
+ 0) status="disabled"; desc="未安装"; exit_code=$STATUS_NOT_INSTALLED ;;
77
+ *)
78
+ if [ "$check_container" -eq 0 ] && [ "$check_docker" -eq 1 ]; then
79
+ status="disabled"; desc="Docker 运行中但 voicebox-qwen 未安装"; exit_code=$STATUS_NOT_INSTALLED
80
+ elif [ "$check_container" -eq 1 ] && [ "$check_api" -eq 0 ]; then
81
+ status="partial"; desc="voicebox-qwen 容器存在但 API 未就绪(可能在加载模型)"; exit_code=$STATUS_DOCKER_DOWN
82
+ else
83
+ status="partial"; desc="部分安装"; exit_code=$STATUS_PARTIAL
84
+ fi
85
+ ;;
86
+ esac
87
+
88
+ # ── 输出 JSON ──
89
+ cat <<JSONEOF
90
+ {
91
+ "status": "$status",
92
+ "description": "$desc",
93
+ "endpoint": "http://${VB_HOST}:${VB_PORT}",
94
+ "checks": {
95
+ "docker": $( [ $check_docker -eq 1 ] && echo true || echo false ),
96
+ "container": $( [ $check_container -eq 1 ] && echo true || echo false ),
97
+ "api": $( [ $check_api -eq 1 ] && echo true || echo false ),
98
+ "tts_script": $( [ $check_tts_script -eq 1 ] && echo true || echo false ),
99
+ "profile": $( [ $check_profile -eq 1 ] && echo true || echo false ),
100
+ "openclaw_config": $( [ $check_config -eq 1 ] && echo true || echo false ),
101
+ "provider_active": $( [ $check_provider -eq 1 ] && echo true || echo false )
102
+ }
103
+ }
104
+ JSONEOF
105
+
106
+ exit $exit_code
@@ -0,0 +1,98 @@
1
+ #!/bin/bash
2
+ # WorkRelay TTS 桥接脚本(千问 Qwen3-TTS 引擎)
3
+ # 调用本地 Voicebox (voicebox-qwen, 端口 17602) REST API
4
+ # 环境变量:
5
+ # WR_VOICEBOX_QWEN_PORT - 千问实例端口(默认 17602,独立于 Kokoro 17493/17600、LuxTTS 17601)
6
+ # WR_VOICEBOX_PROFILE_ID - 声音 profile ID(可选,缺省读取 ~/.workrelay/speak-qwen/profile_id)
7
+ # WR_VOICEBOX_LANG - 语言(默认 zh)
8
+ # 2026-09-14 新增:千问 Qwen3-TTS 能力,固定引擎 qwen_custom_voice(预设音色)
9
+ set -e
10
+
11
+ TEXT="$1"
12
+ OUT="$2"
13
+
14
+ if [ -z "$TEXT" ] || [ -z "$OUT" ]; then
15
+ echo "usage: $0 <text> <output_path>" >&2
16
+ exit 1
17
+ fi
18
+
19
+ VB_HOST="${WR_VOICEBOX_HOST:-127.0.0.1}"
20
+ VB_PORT="${WR_VOICEBOX_QWEN_PORT:-17602}"
21
+ PROFILE_ID="${WR_VOICEBOX_PROFILE_ID:-}"
22
+ # 固定引擎:千问预设音色引擎
23
+ ENGINE="qwen_custom_voice"
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 (千问) 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-qwen/profile_id" ]; then
34
+ PROFILE_ID=$(cat "$HOME/.workrelay/speak-qwen/profile_id" 2>/dev/null | tr -d ' \n')
35
+ fi
36
+ if [ -z "$PROFILE_ID" ]; then
37
+ # 兜底:从 /profiles 找千问预设 profile(优先中文女声 Vivian)
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("preset_engine")=="qwen_custom_voice" and p.get("language")=="zh":
46
+ out=p.get("id",""); break
47
+ if not out:
48
+ for p in d:
49
+ if p.get("preset_engine")=="qwen_custom_voice":
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=qwen_custom_voice)
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-qwen" \
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
+ # 轮询等待完成(千问 1.7B CPU 推理约 10~180 秒,GPU 更快)
77
+ MAX_POLLS=300
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 60 -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,76 @@
1
+ # WorkRelay 语音转录模块
2
+
3
+ ## 文件说明
4
+
5
+ | 文件 | 作用 |
6
+ |------|------|
7
+ | `transcribe.py` | faster-whisper 转录引擎(纯 Python,CPU 推理) |
8
+ | `transcribe_server.py` | **常驻转写服务**(2026-08-12 新增):模型加载一次常驻内存,免每次重新加载 |
9
+ | `voicebox-transcribe.sh` | 转录桥接:优先常驻服务 → 直接本地 → 可选远程 fallback |
10
+ | `install.sh` | 一键安装(依赖→模型→启动常驻服务→OpenClaw 配置) |
11
+ | `status.sh` | 状态查询,输出 JSON,客户端判断"已启用/未启用" |
12
+
13
+ ## 客户端集成方案
14
+
15
+ ### 设置页面
16
+
17
+ ```
18
+ ┌─────────────────────────────────────┐
19
+ │ ⚙️ 语音转录 │
20
+ │ │
21
+ │ 状态: ● 已启用 / ○ 未启用 │
22
+ │ │
23
+ │ 模型: faster-whisper-small │
24
+ │ 存储: ~460MB(常驻内存) │
25
+ │ │
26
+ │ [ 启用语音转录 ] ← 仅未启用时显示 │
27
+ └─────────────────────────────────────┘
28
+ ```
29
+
30
+ ### 交互流程
31
+
32
+ ```
33
+ 客户端设置页 → 点击[启用] → 发送指令给 Agent
34
+ → Agent 执行 install.sh
35
+ → 返回进度/结果
36
+ → 客户端刷新状态
37
+
38
+ 客户端设置页 → 加载时 → 调用 status.sh
39
+ → 解析 JSON
40
+ → 显示状态
41
+ ```
42
+
43
+ ### status.sh 返回格式
44
+
45
+ ```json
46
+ {
47
+ "status": "enabled", // enabled | disabled | partial
48
+ "description": "语音转录已启用",
49
+ "checks": {
50
+ "faster_whisper": true,
51
+ "model": true,
52
+ "scripts": true,
53
+ "openclaw_config": true
54
+ }
55
+ }
56
+ ```
57
+
58
+ status 为 `enabled` 时四项全部为 true。
59
+
60
+ ## 环境变量(可选)
61
+
62
+ | 变量 | 默认值 | 说明 |
63
+ |------|--------|------|
64
+ | `WR_WHISPER_MODEL` | `small` | 模型大小(small 默认,广东话/普通话均可用) |
65
+ | `WR_WHISPER_CACHE` | `~/.cache/faster-whisper` | 模型缓存目录 |
66
+ | `WR_WHISPER_PORT` | `17890` | 常驻服务端口 |
67
+ | `WR_INSTALL_DIR` | `~/.workrelay/transcribe` | 脚本安装目录 |
68
+ | `WR_FALLBACK_URL` | 无 | 远程转录服务 URL(不设则仅本地) |
69
+
70
+ ## 限制
71
+
72
+ - 仅支持 **Linux / macOS**(Windows 用户需 WSL2)
73
+ - 需要 **1GB+** 可用磁盘空间
74
+ - 需要可访问 HuggingFace(下载模型,small ~460MB)
75
+ - 常驻服务首次启动加载模型约 5-10 秒,之后转写即时(3-5 秒)
76
+ - OpenClaw 内置 `MIN_AUDIO_FILE_BYTES=1024`,小于 1KB 的语音不会触发转录
@@ -0,0 +1,227 @@
1
+ #!/bin/bash
2
+ # WorkRelay 语音转录一键安装脚本
3
+ # 自动完成:依赖安装 → 模型下载 → OpenClaw 配置
4
+ set -e
5
+
6
+ RED='\033[0;31m'
7
+ GREEN='\033[0;32m'
8
+ YELLOW='\033[1;33m'
9
+ NC='\033[0m'
10
+
11
+ INSTALL_DIR="$HOME/.workrelay/transcribe"
12
+ MODEL_CACHE="$HOME/.cache/faster-whisper"
13
+ WR_DIR="$(dirname "$(readlink -f "$0")")"
14
+
15
+ echo "============================================"
16
+ echo " WorkRelay 语音转录 - 一键安装"
17
+ echo "============================================"
18
+ echo ""
19
+
20
+ # ── 系统检查 ──
21
+ echo -n "[1/5] 检查系统环境... "
22
+
23
+ OS="$(uname -s)"
24
+ case "$OS" in
25
+ Linux) ;;
26
+ Darwin) ;;
27
+ *)
28
+ echo -e "${RED}失败${NC}"
29
+ echo "错误:当前仅支持 Linux/macOS,不支持 $OS"
30
+ echo "提示:Windows 用户请通过 WSL2 运行本脚本"
31
+ exit 1
32
+ ;;
33
+ esac
34
+
35
+ if ! command -v python3 &>/dev/null; then
36
+ echo -e "${RED}失败${NC}"
37
+ echo "错误:未找到 python3,请先安装 Python 3.8+"
38
+ exit 1
39
+ fi
40
+
41
+ PYVER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
42
+ echo -e "${GREEN}OK${NC} (Python $PYVER, $(uname -o))"
43
+
44
+ # ── 磁盘空间检查 ──
45
+ echo -n "[2/5] 检查磁盘空间... "
46
+ AVAIL=$(df -BM "$HOME" | tail -1 | awk '{print $4}' | sed 's/M//')
47
+ if [ "$AVAIL" -lt 2500 ]; then
48
+ echo -e "${RED}不足${NC}"
49
+ echo "错误:可用空间 ${AVAIL}MB,至少需要 1GB(模型 ~460MB)"
50
+ exit 1
51
+ fi
52
+ echo -e "${GREEN}OK${NC} (可用 ${AVAIL}MB)"
53
+
54
+ # ── 安装 Python 依赖 ──
55
+ echo -n "[3/5] 安装 Python 依赖... "
56
+
57
+ # 2026-08-18 修复:彻底移除 sudo 依赖,改用户级安装(pip --user / ensurepip --user)。
58
+ # 纯 Linux 上 sudo 通常需要密码,spawn 非交互环境无法输入 → 原 sudo apt-get 方案必失败。
59
+ # 用户级安装无需任何提权,任何机器可用;--break-system-packages 兼容 PEP668 系统。
60
+ export PATH="$PATH:$HOME/.local/bin:/usr/local/bin:/snap/bin"
61
+
62
+ # 统一 pip 调用入口(pip3 优先,否则 python3 -m pip)
63
+ pip_install() {
64
+ if command -v pip3 >/dev/null 2>&1; then
65
+ pip3 "$@"
66
+ else
67
+ python3 -m pip "$@"
68
+ fi
69
+ }
70
+
71
+ # 确保 pip 可用:python3 -m pip / pip3 都没有时,用 ensurepip --user 自举(Python 自带,无需 sudo)
72
+ if ! python3 -m pip --version &>/dev/null 2>&1 && ! command -v pip3 &>/dev/null; then
73
+ echo ""
74
+ echo " 未检测到 pip,使用 python3 -m ensurepip --user 自举安装 ..."
75
+ if ! python3 -m ensurepip --user >/tmp/wr-pip-err.log 2>&1; then
76
+ echo -e "${RED}失败${NC}"
77
+ echo "错误:pip 自举失败,请手动执行:"
78
+ echo " python3 -m ensurepip --user"
79
+ echo " 或安装系统 pip(需 root):sudo apt install python3-pip"
80
+ cat /tmp/wr-pip-err.log
81
+ exit 1
82
+ fi
83
+ export PATH="$PATH:$HOME/.local/bin"
84
+ fi
85
+
86
+ # 安装 faster-whisper(用户级 --user,不写系统目录,无需 sudo)
87
+ echo " pip 可用,安装 faster-whisper(用户级安装)..."
88
+ if ! pip_install install --user --quiet --break-system-packages faster-whisper 2>/tmp/wr-pip-err.log; then
89
+ # fallback:老系统无 PEP668,无需 --break-system-packages
90
+ if ! pip_install install --user --quiet faster-whisper 2>>/tmp/wr-pip-err.log; then
91
+ echo -e "${RED}失败${NC}"
92
+ echo "错误:faster-whisper 安装失败,请手动执行:"
93
+ echo " pip3 install --user --break-system-packages faster-whisper"
94
+ cat /tmp/wr-pip-err.log
95
+ exit 1
96
+ fi
97
+ fi
98
+ echo -e "${GREEN}OK${NC}"
99
+
100
+ # ── 下载模型 ──
101
+ echo -n "[4/5] 下载 Whisper 模型 (~460MB)... "
102
+ echo ""
103
+ # 2026-08-18 修复:systemd 透传的 all_proxy 可能是 socks5://(如 clash),
104
+ # 而 httpx 初始化 SOCKS 代理需要 socksio 包(默认未装)→ ImportError 导致下载失败。
105
+ # clash 混合端口(7890)同时支持 HTTP,这里将 socks5:// 降级为 http://,无需额外装包。
106
+ if [[ "${all_proxy:-}${ALL_PROXY:-}" == *"socks5://"* ]]; then
107
+ echo " ℹ 检测到 socks5 代理,降级为 http 代理(clash 混合端口兼容),避免 socksio 依赖"
108
+ export all_proxy="${all_proxy//socks5:/http:}"
109
+ export ALL_PROXY="${ALL_PROXY//socks5:/http:}"
110
+ fi
111
+ # 2026-08-18 修复:国内机器直连 HuggingFace 经常 Network unreachable,
112
+ # 增加 hf-mirror.com 镜像自动 fallback(HF_ENDPOINT 环境变量由 huggingface_hub 原生支持)。
113
+ # 直连失败 → 镜像重试 → 仍失败给出手动指引。
114
+ dl_model() {
115
+ python3 -c "
116
+ import os
117
+ from huggingface_hub import snapshot_download
118
+ endpoint = os.environ.get('HF_ENDPOINT', 'https://huggingface.co')
119
+ print(f' 正在从 {endpoint} 下载 faster-whisper-small ...')
120
+ snapshot_download('Systran/faster-whisper-small',
121
+ cache_dir='$MODEL_CACHE')
122
+ print(' 模型下载完成')
123
+ " 2>&1
124
+ }
125
+ # 2026-09-14 优化(石头哥):按区域决定下载顺序
126
+ # 国内(WR_IMAGE_REGION=cn,客户端中文界面传入)→ 镜像优先,省掉直连超时等待
127
+ # 国际/未指定 → 直连优先;两种顺序都保留"失败自动回退另一种"+ 手动指引
128
+ HF_MIRROR_URL="${WR_HF_ENDPOINT:-https://hf-mirror.com}"
129
+ dl_fail_msg() {
130
+ echo -e "${RED}模型下载失败${NC}"
131
+ echo "提示:检查网络连接,或手动设置 WR_WHISPER_CACHE 到已有模型目录"
132
+ echo " 也可手动下载:HF_ENDPOINT=${HF_MIRROR_URL} python3 -c \"from huggingface_hub import snapshot_download; snapshot_download('Systran/faster-whisper-small', cache_dir='$MODEL_CACHE')\""
133
+ }
134
+ if [ "${WR_IMAGE_REGION:-auto}" = "cn" ] && [ -z "${WR_HF_ENDPOINT:-}" ]; then
135
+ # 国内:镜像优先(避免先直连超时白等)
136
+ if HF_ENDPOINT="$HF_MIRROR_URL" dl_model; then
137
+ echo " ✅ 已通过国内镜像 hf-mirror.com 下载(国内优先,跳过直连等待)"
138
+ elif dl_model; then
139
+ echo " ℹ 国内镜像不可用,已直连 HuggingFace 下载成功"
140
+ else
141
+ dl_fail_msg
142
+ exit 1
143
+ fi
144
+ else
145
+ # 国际/自动:直连优先,失败回退镜像
146
+ if dl_model; then
147
+ :
148
+ elif HF_ENDPOINT="$HF_MIRROR_URL" dl_model; then
149
+ echo " ℹ 直连 HuggingFace 失败,已通过国内镜像 hf-mirror.com 下载成功"
150
+ else
151
+ dl_fail_msg
152
+ exit 1
153
+ fi
154
+ fi
155
+ echo -e " ${GREEN}OK${NC}"
156
+
157
+ # ── 安装脚本 & 配置 OpenClaw ──
158
+ echo -n "[5/5] 部署脚本 & 配置 OpenClaw... "
159
+ mkdir -p "$INSTALL_DIR"
160
+ cp "$WR_DIR/transcribe.py" "$INSTALL_DIR/"
161
+ cp "$WR_DIR/transcribe_server.py" "$INSTALL_DIR/"
162
+ cp "$WR_DIR/voicebox-transcribe.sh" "$INSTALL_DIR/"
163
+ chmod +x "$INSTALL_DIR/voicebox-transcribe.sh" "$INSTALL_DIR/transcribe.py" "$INSTALL_DIR/transcribe_server.py"
164
+
165
+ # 2026-08-12:启动常驻转写服务(模型常驻内存,免每次重新加载)。
166
+ # 已在跑则跳过;未跑则后台 detached 启动,日志写入 ~/.openclaw/logs/wr-transcribe-server.log。
167
+ WR_SERVER_PORT="${WR_WHISPER_PORT:-17890}"
168
+ if curl -s --connect-timeout 2 "http://127.0.0.1:${WR_SERVER_PORT}/health" >/dev/null 2>&1; then
169
+ echo -n " (常驻服务已运行) "
170
+ else
171
+ mkdir -p "$(dirname "$HOME/.openclaw/logs/wr-transcribe-server.log" 2>/dev/null)" 2>/dev/null || true
172
+ nohup python3 "$INSTALL_DIR/transcribe_server.py" "$WR_SERVER_PORT" \
173
+ >>"$HOME/.openclaw/logs/wr-transcribe-server.log" 2>&1 &
174
+ echo -n " (常驻服务已启动) "
175
+ fi
176
+
177
+ # 写入 OpenClaw 配置
178
+ if command -v openclaw &>/dev/null; then
179
+ openclaw config set audio.transcription.command \
180
+ "[\"$INSTALL_DIR/voicebox-transcribe.sh\", \"{{MediaPath}}\"]" 2>/dev/null || {
181
+ echo -e "${YELLOW}⚠ OpenClaw 配置写入失败,请手动执行:${NC}"
182
+ echo " openclaw config set audio.transcription.command '[\"$INSTALL_DIR/voicebox-transcribe.sh\", \"{{MediaPath}}\"]'"
183
+ }
184
+
185
+ # 2026-08-12:入站语音转写切到本地 faster-whisper(tools.media.audio → cli 模式)。
186
+ # 默认 OpenClaw 入站音频转写走 tools.media.audio 的云端 provider(如商汤 SenseAudio),
187
+ # 安装本扩展后应切到本地命令,避免联网/费用;用户无需手动改配置。
188
+ openclaw config set tools.media.audio.models \
189
+ '[{"type":"cli","command":"'"$INSTALL_DIR"'/voicebox-transcribe.sh","args":["{{MediaPath}}"]}]' 2>/dev/null || {
190
+ echo -e "${YELLOW}⚠ tools.media.audio 切本地失败,请手动配置${NC}"
191
+ }
192
+ openclaw gateway restart 2>/dev/null || true
193
+ else
194
+ echo -e "${YELLOW}⚠ 未检测到 openclaw CLI,跳过自动配置${NC}"
195
+ fi
196
+
197
+ echo -e "${GREEN}OK${NC}"
198
+ echo ""
199
+
200
+ # ── 验证 ──
201
+ echo "============================================"
202
+ echo " 安装验证"
203
+ echo "============================================"
204
+
205
+ echo -n " faster-whisper: "
206
+ python3 -c "import faster_whisper; print(faster_whisper.__version__)" 2>/dev/null \
207
+ && echo "" \
208
+ || echo -e "${RED}未安装${NC}"
209
+
210
+ echo -n " 模型: "
211
+ if [ -d "$MODEL_CACHE" ] && [ "$(du -sm "$MODEL_CACHE" | cut -f1)" -gt 500 ]; then
212
+ echo -e "${GREEN}$(du -sh "$MODEL_CACHE" | cut -f1)${NC}"
213
+ else
214
+ echo -e "${RED}未下载或异常${NC}"
215
+ fi
216
+
217
+ echo -n " 转录脚本: "
218
+ if [ -f "$INSTALL_DIR/transcribe.py" ] && [ -f "$INSTALL_DIR/voicebox-transcribe.sh" ]; then
219
+ echo -e "${GREEN}已部署${NC} ($INSTALL_DIR)"
220
+ else
221
+ echo -e "${RED}缺失${NC}"
222
+ fi
223
+
224
+ echo ""
225
+ echo -e "${GREEN}✅ 安装完成!${NC}"
226
+ echo " 语音转录将在下次 OpenClaw 重启后生效。"
227
+ echo ""
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "workrelay-transcribe",
3
+ "displayName": "语音转录(STT)",
4
+ "description": "语音消息转文字:本地 faster-whisper 转录,无需联网",
5
+ "requirements": [
6
+ {
7
+ "id": "os",
8
+ "label": "操作系统",
9
+ "expect": "Linux / macOS",
10
+ "check": "uname -s | grep -qiE 'linux|darwin'",
11
+ "hint": "仅支持 Linux/macOS(Windows 请用 WSL2)"
12
+ },
13
+ {
14
+ "id": "python",
15
+ "label": "Python 3.8+",
16
+ "expect": "已安装 python3",
17
+ "check": "command -v python3 >/dev/null 2>&1 && python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)'",
18
+ "hint": "未找到 Python 3.8+,请先安装 Python"
19
+ },
20
+ {
21
+ "id": "pip",
22
+ "label": "pip 可用",
23
+ "expect": "pip3 或 python3 -m pip",
24
+ "check": "command -v pip3 >/dev/null 2>&1 || python3 -m pip --version >/dev/null 2>&1",
25
+ "hint": "未找到 pip 时,安装脚本会自动用 python3 -m ensurepip --user 用户级自举,无需手动安装"
26
+ },
27
+ {
28
+ "id": "disk",
29
+ "label": "磁盘 ≥ 2.5GB",
30
+ "expect": "≥ 2.5GB 可用",
31
+ "check": "[ $(df -BM \"$HOME\" | tail -1 | awk '{print $4}' | sed 's/M//') -ge 2560 ]",
32
+ "hint": "可用空间不足,模型约需 460MB,建议预留 1GB"
33
+ },
34
+ {
35
+ "id": "network",
36
+ "label": "外网可达(HuggingFace 或镜像)",
37
+ "expect": "能访问 huggingface.co 或 hf-mirror.com",
38
+ "check": "curl -sI --max-time 5 https://huggingface.co >/dev/null 2>&1 || curl -sI --max-time 5 https://hf-mirror.com >/dev/null 2>&1",
39
+ "hint": "无法访问 HuggingFace 及国内镜像,将无法下载 Whisper 模型"
40
+ }
41
+ ]
42
+ }