@oadank/dsh-input-tools 0.3.14 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,7 @@ powershell -ExecutionPolicy Bypass -File scripts\setup-profile.ps1
69
69
  # Linux/macOS:
70
70
  bash scripts/setup-profile.sh
71
71
  pnpm install
72
+ pnpm run build:lib # ⚠️ 必须!全新 clone 无编译产物,跳过会报 Failed to resolve @deepseek-ai/dsh-client-web
72
73
  pnpm run build:web
73
74
  dsh --profile web
74
75
  ```
@@ -103,7 +104,7 @@ cd deepseek-harness
103
104
  git checkout 141eb6fef8 # 官方 dsh-0.1.0-rc.8 基线
104
105
  # 打补丁(脚本自动探测/输入源码位置):
105
106
  powershell -ExecutionPolicy Bypass -File <插件目录>\patches\apply-voice-patch.ps1
106
- pnpm install && pnpm run build:web && dsh --profile web
107
+ pnpm install && pnpm run build:lib && pnpm run build:web && dsh --profile web
107
108
  ```
108
109
 
109
110
  ### 方案 B:直接用整合版 fork(推荐)
@@ -112,7 +113,7 @@ pnpm install && pnpm run build:web && dsh --profile web
112
113
  git clone https://github.com/oadank/deepseek-harness.git
113
114
  cd deepseek-harness
114
115
  powershell -ExecutionPolicy Bypass -File scripts\setup-profile.ps1
115
- pnpm install && pnpm run build:web && dsh --profile web
116
+ pnpm install && pnpm run build:lib && pnpm run build:web && dsh --profile web
116
117
  ```
117
118
 
118
119
  > 补丁基线官方 rc.8(141eb6fef8),官方后续更新可能不兼容,请先 checkout 该基线再打。回滚:`git apply -R`。
package/lib/client.js CHANGED
@@ -586,6 +586,12 @@ window.__ModuleLoader__.load({
586
586
  const [vdSamples, setVdSamples] = useState([]); // VoiceDesign 官方示例音频(预生成)
587
587
  // [2026-08-21] 试听失败的错误提示(之前失败静默无反馈)
588
588
  const [previewErr, setPreviewErr] = useState(null);
589
+ // [2026-08-22] 试听失败浮动 Toast(fixed 顶部居中,醒目弹窗式,6 秒自动消失)
590
+ useEffect(() => {
591
+ if (previewErr === null) return;
592
+ const t = window.setTimeout(() => setPreviewErr(null), 6000);
593
+ return () => window.clearTimeout(t);
594
+ }, [previewErr]);
589
595
  // [2026-08-21] API Key 明文/密文切换(眼睛图标)
590
596
  const [showKeys, setShowKeys] = useState({});
591
597
  // [2026-08-21] 本地 TTS 一键安装命令
@@ -727,8 +733,18 @@ window.__ModuleLoader__.load({
727
733
  const audio = new Audio("data:" + d.mediaType + ";base64," + d.data);
728
734
  previewRef.current = audio;
729
735
  audio.onended = () => { if (previewTagRef.current === curTag) setPreviewing(null); };
730
- audio.onerror = () => { if (previewTagRef.current === curTag) setPreviewing(null); };
731
- audio.play().catch(() => { if (previewTagRef.current === curTag) setPreviewing(null); });
736
+ audio.onerror = () => {
737
+ if (previewTagRef.current === curTag) {
738
+ setPreviewing(null);
739
+ setPreviewErr("音频加载/播放失败(服务可能返回了无效音频)");
740
+ }
741
+ };
742
+ audio.play().catch(() => {
743
+ if (previewTagRef.current === curTag) {
744
+ setPreviewing(null);
745
+ setPreviewErr("音频加载/播放失败(服务可能返回了无效音频)");
746
+ }
747
+ });
732
748
  })
733
749
  .catch((e) => { if (previewTagRef.current === curTag) setPreviewing(null); setPreviewErr(String(e?.message ?? e)); });
734
750
  };
@@ -893,9 +909,17 @@ window.__ModuleLoader__.load({
893
909
  // 分区标题(语音图标已移到各服务商卡片前)
894
910
  h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "15px", fontWeight: 700, color: "var(--dsw-alias-label-primary,#e6e9ef)" } },
895
911
  "语音服务"),
896
- // [2026-08-21] 试听失败错误横幅(原来静默无提示)
897
- previewErr !== null ? h("div", { style: { fontSize: "12.5px", lineHeight: "1.6", color: "#e5484d", border: "1px solid rgba(229,72,77,.4)", borderRadius: "8px", padding: "8px 10px", background: "rgba(229,72,77,.08)", whiteSpace: "pre-wrap" } },
898
- "试听失败:" + previewErr) : null,
912
+ // [2026-08-21] 试听失败错误提示;[2026-08-22] fixed 顶部弹窗 Toast + 限高滚动(错误堆栈超长不撑爆)
913
+ previewErr !== null ? h("div", {
914
+ style: {
915
+ position: "fixed", top: "24px", left: "50%", transform: "translateX(-50%)", zIndex: 9999,
916
+ background: "rgba(229,72,77,.95)", color: "#fff", borderRadius: "10px",
917
+ padding: "10px 18px", fontSize: "13px", lineHeight: "1.5",
918
+ boxShadow: "0 6px 24px rgba(0,0,0,.45)",
919
+ maxWidth: "520px", maxHeight: "45vh", overflowY: "auto",
920
+ whiteSpace: "pre-wrap", wordBreak: "break-word", pointerEvents: "none",
921
+ },
922
+ }, "试听失败:" + previewErr) : null,
899
923
  // [2026-08-21] 语音能力状态面板(安装即用 vs dsh 原生契约支持)
900
924
  caps !== null ? h("div", { style: { border: "1px solid var(--dsw-alias-border-l1,#333a45)", borderRadius: "10px", padding: "10px 12px", display: "flex", flexDirection: "column", gap: "6px", background: "rgba(128,128,128,.05)", fontSize: "12.5px", lineHeight: "1.5" } },
901
925
  h("div", { style: { fontSize: "12px", fontWeight: 600, opacity: .8 } }, "语音能力"),
package/lib/index.js CHANGED
@@ -426,6 +426,28 @@ async function synthesizeXiaomiVoiceClone(text, cfg, globalCfg, voiceDesc) {
426
426
  return toMp3(new Uint8Array(Buffer.from(data, 'base64')), 'audio/wav')
427
427
  }
428
428
 
429
+ // [2026-08-22] 解析 Windows 命令行参数(正确处理双引号:引号内空格不拆、剥掉引号)。
430
+ // 之前用 command.split(/\s+/) 拆参数,用户填的带引号路径会被整段含引号传给
431
+ // execFileSync → node 把 "C:\...\local-tts.mjs" 当成相对路径拼上 cwd →
432
+ // Cannot find module 'D:\opt\...\"C:\Users\...'。本函数根治该问题。
433
+ function splitCommandLine(cmd) {
434
+ const args = []
435
+ let cur = ''
436
+ let inQuote = false
437
+ for (let i = 0; i < cmd.length; i++) {
438
+ const ch = cmd[i]
439
+ if (ch === '"') {
440
+ inQuote = !inQuote
441
+ } else if (ch === ' ' || ch === '\t') {
442
+ if (inQuote) { cur += ch } else if (cur !== '') { args.push(cur); cur = '' }
443
+ } else {
444
+ cur += ch
445
+ }
446
+ }
447
+ if (cur !== '') args.push(cur)
448
+ return args
449
+ }
450
+
429
451
  // ── local 本地 MeloTTS:HTTP 常驻服务优先,CMD 兜底 ──
430
452
  async function synthesizeLocalVoice(text, cfg) {
431
453
  const url = cfg?.url?.trim() ?? ''
@@ -442,7 +464,7 @@ async function synthesizeLocalVoice(text, cfg) {
442
464
  }
443
465
  const command = cfg?.cmd?.trim() ?? ''
444
466
  if (command === '') return null
445
- const parts = command.split(/\s+/)
467
+ const parts = splitCommandLine(command)
446
468
  const bin = parts[0]
447
469
  if (bin === undefined) return null
448
470
  const rest = parts.slice(1)
@@ -523,7 +545,7 @@ async function transcribeAudio(base64Audio, cfg) {
523
545
  }
524
546
  // 2) 本地命令(sherpa-onnx-offline.exe,结果输出到 stderr,需合并双流解析)
525
547
  if (asr.mode === 'cmd' && (asr.cmd ?? '').trim() !== '') {
526
- const parts = asr.cmd.trim().split(/\s+/)
548
+ const parts = splitCommandLine(asr.cmd.trim())
527
549
  const bin = parts[0]
528
550
  if (bin === undefined) return { ok: false, error: '命令格式错误' }
529
551
  const { spawnSync } = await import('node:child_process')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oadank/dsh-input-tools",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -6,28 +6,35 @@
6
6
  # 3. ffmpeg(无则用 winget 安装)
7
7
  # 4. 注册 nssm 服务 asr(端口 18790,开机自启)
8
8
  #
9
- # 安装位置:自动放在本插件包目录下的 sherpa-onnx/(脚本位于
10
- # <插件包>/scripts/install-asr.ps1,自动推导到 <插件包>/sherpa-onnx)
11
- # ——所有人装插件后路径都统一,不会乱。
9
+ # 安装位置(2026-08-22 改):默认装到**独立目录**,不装插件包目录——
10
+ # 插件包(profile 副本或源码 internal-plugins)会随升级/重装被覆盖,
11
+ # 模型放里面会丢。默认顺序:
12
+ # 1) -InstallDir 参数显式指定(最高优先)
13
+ # 2) 检测已有 sherpa-onnx(之前装过则复用):~\.dsh\sherpa-onnx / C:\D\opt\sherpa-onnx / D:\opt\deepseek-harness\asr
14
+ # 3) 以上都没有 → ~\.dsh\sherpa-onnx(dsh 数据目录,跨升级保留)
12
15
  # 用法:以管理员身份打开 PowerShell,执行:
13
- # powershell -ExecutionPolicy Bypass -File "<插件包>\scripts\install-asr.ps1"
14
- # 可选参数:-Port 18790(自定义端口)
16
+ # powershell -ExecutionPolicy Bypass -File "<脚本路径>\install-asr.ps1"
17
+ # 可选参数:-Port 18790(自定义端口);-InstallDir "D:\opt\my-sherpa"(自定义安装目录)
15
18
  # ============================================================
16
19
  param(
17
- [int]$Port = 18790
20
+ [int]$Port = 18790,
21
+ [string]$InstallDir = ""
18
22
  )
19
23
 
20
24
  $ErrorActionPreference = "Stop"
21
25
  $Version = "v1.13.6"
22
26
 
23
- # ---- 0. 自动推导安装目录:脚本在 <插件包>/scripts/,安装到 <插件包>/sherpa-onnx/ ----
24
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
25
- $PluginRoot = Split-Path -Parent $ScriptDir
26
- $InstallDir = Join-Path $PluginRoot "sherpa-onnx"
27
+ # ---- 0. 确定安装目录(独立目录,不装插件包内)----
28
+ if ($InstallDir -eq "") {
29
+ foreach ($c in @("$env:USERPROFILE\.dsh\sherpa-onnx", "C:\D\opt\sherpa-onnx", "D:\opt\deepseek-harness\asr")) {
30
+ if (Test-Path (Join-Path $c "bin")) { $InstallDir = $c; break }
31
+ }
32
+ if ($InstallDir -eq "") { $InstallDir = Join-Path $env:USERPROFILE ".dsh\sherpa-onnx" }
33
+ }
27
34
 
28
35
  Write-Host "==== dsh ASR 一键安装 ====" -ForegroundColor Cyan
29
- Write-Host "插件包目录: $PluginRoot"
30
36
  Write-Host "安装目录: $InstallDir"
37
+ Write-Host " (独立目录,不随插件升级/重装被覆盖)" -ForegroundColor DarkGray
31
38
  Write-Host "服务端口: $Port"
32
39
 
33
40
  # ---- 0b. 幂等保护:已完整安装则直接退出(不下载、不注册、不碰任何现有配置)----
@@ -7,13 +7,22 @@
7
7
  # 4. 生成 local-tts.mjs 启动脚本(与插件"本地命令"契约一致:
8
8
  # node local-tts.mjs <文本> → stdout 输出 mp3 音频字节)
9
9
  #
10
- # 安装位置:自动放在本插件包目录下的 sherpa-onnx/(脚本位于
11
- # <插件包>/scripts/install-local-tts.ps1,自动推导到 <插件包>/sherpa-onnx)
12
- # ——与 ASR 安装共用同一目录,两个 exe 一次下载全有。
10
+ # 安装位置(2026-08-22 改):默认装到**独立目录**,不装插件包目录——
11
+ # 插件包(profile 副本或源码 internal-plugins)会随升级/重装被覆盖,
12
+ # 模型放里面会丢。默认顺序:
13
+ # 1) -InstallDir 参数显式指定(最高优先,可指定源码 internal-plugins 等任意位置)
14
+ # 2) 检测已有 sherpa-onnx(ASR 装过则复用同一份,省一次下载):
15
+ # ~\.dsh\sherpa-onnx / C:\D\opt\sherpa-onnx / D:\opt\deepseek-harness\asr
16
+ # 3) 以上都没有 → ~\.dsh\sherpa-onnx(dsh 数据目录,跨升级保留)
13
17
  # 用法:以管理员身份打开 PowerShell,执行:
14
- # powershell -ExecutionPolicy Bypass -File "<插件包>\scripts\install-local-tts.ps1"
18
+ # powershell -ExecutionPolicy Bypass -File "<脚本路径>\install-local-tts.ps1"
19
+ # 或指定安装目录:... install-local-tts.ps1 -InstallDir "D:\opt\my-sherpa"
15
20
  # 装完后到 dsh 设置 → 语音服务 → 本地 TTS,把提示的命令填进「本地命令」。
16
21
  # ============================================================
22
+ param(
23
+ [string]$InstallDir = ""
24
+ )
25
+
17
26
  $ErrorActionPreference = "Stop"
18
27
  $Version = "v1.13.6"
19
28
 
@@ -57,14 +66,18 @@ function Expand-TarBz2 {
57
66
  return $true
58
67
  }
59
68
 
60
- # ---- 0. 自动推导安装目录:脚本在 <插件包>/scripts/,安装到 <插件包>/sherpa-onnx/ ----
61
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
62
- $PluginRoot = Split-Path -Parent $ScriptDir
63
- $InstallDir = Join-Path $PluginRoot "sherpa-onnx"
69
+ # ---- 0. 确定安装目录(独立目录,不装插件包内)----
70
+ if ($InstallDir -eq "") {
71
+ # 复用已有 sherpa-onnx(ASR 装过则共用,省一次下载)
72
+ foreach ($c in @("$env:USERPROFILE\.dsh\sherpa-onnx", "C:\D\opt\sherpa-onnx", "D:\opt\deepseek-harness\asr")) {
73
+ if (Test-Path (Join-Path $c "bin")) { $InstallDir = $c; break }
74
+ }
75
+ if ($InstallDir -eq "") { $InstallDir = Join-Path $env:USERPROFILE ".dsh\sherpa-onnx" }
76
+ }
64
77
 
65
78
  Write-Host "==== dsh 本地 TTS 一键安装 ====" -ForegroundColor Cyan
66
- Write-Host "插件包目录: $PluginRoot"
67
79
  Write-Host "安装目录: $InstallDir"
80
+ Write-Host " (独立目录,不随插件升级/重装被覆盖;与 ASR 共用同一份 sherpa-onnx)" -ForegroundColor DarkGray
68
81
 
69
82
  # ---- 0b. 幂等保护:已完整安装则直接退出 ----
70
83
  $exeExists = Test-Path "$InstallDir\bin\sherpa-onnx-offline-tts.exe"
@@ -198,14 +211,16 @@ try {
198
211
  }
199
212
  '@
200
213
  $launcher = $launcherTemplate.Replace('__SHERPA__', $exePath).Replace('__BASE__', $basePath)
201
- $launcherFile = "$PluginRoot\local-tts.mjs"
214
+ $launcherFile = Join-Path $InstallDir "local-tts.mjs"
202
215
  Set-Content -Path $launcherFile -Value $launcher -Encoding UTF8
203
216
  Write-Host " local-tts.mjs: $launcherFile" -ForegroundColor Green
204
217
 
205
- # ---- 6. 完成提示 ----
218
+ # ---- 6. 完成提示(命令只在路径含空格时才加引号;插件已能正确处理引号)----
206
219
  Write-Host "`n==== 安装完成 ====" -ForegroundColor Cyan
207
220
  $nodeExe = (Get-Command node).Source
208
- $cmdLine = "$nodeExe `"$launcherFile`""
221
+ $nodePart = if ($nodeExe -match ' ') { "`"$nodeExe`"" } else { $nodeExe }
222
+ $launcherPart = if ($launcherFile -match ' ') { "`"$launcherFile`"" } else { $launcherFile }
223
+ $cmdLine = "$nodePart $launcherPart"
209
224
  Write-Host "请到 dsh 设置 → 语音服务 → 本地 TTS,把下面这行填进「本地命令」:"
210
225
  Write-Host ""
211
226
  Write-Host " $cmdLine" -ForegroundColor Green