@goodandready/dsh-voice 0.9.27 → 0.9.28

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
@@ -219,7 +219,7 @@ Registers `transcribe_audio(file_path, language?)` in `ctx.tools`, allowing agen
219
219
  ### Internal HTTP Endpoints
220
220
  * `POST /dsh-voice/transcribe` — `{ dataBase64, mimeType, mode }` → `{ ok, text, provider, tookMs }`
221
221
  * `POST /dsh-voice/polish` — `{ text }` → `{ ok, text }`
222
- * `GET /dsh-voice/status` — Returns daemon status, active chains, SenseVoice.
222
+ * `GET /dsh-voice/status` — Returns daemon status, active chains, SenseVoice and `effectiveSensevoiceProvider`.
223
223
  * `GET /dsh-voice/config` — Returns live plugin configuration snapshot.
224
224
  * `PUT /dsh-voice/config` — Updates and persists plugin configuration across network.
225
225
  * `GET/POST /dsh-voice/sensevoice-installer` — SenseVoice 1-click model installation status and trigger (protected by `isTrustedCaller`, model paths redacted).
package/README.ru.md CHANGED
@@ -219,7 +219,7 @@ dsh plugin --profile web add @goodandready/dsh-voice
219
219
  ### Внутренние HTTP эндпоинты
220
220
  * `POST /dsh-voice/transcribe` — `{ dataBase64, mimeType, mode }` → `{ ok, text, provider, tookMs }`
221
221
  * `POST /dsh-voice/polish` — `{ text }` → `{ ok, text }`
222
- * `GET /dsh-voice/status` — состояние демонов, цепочки, конфигурация SenseVoice и реалтайма.
222
+ * `GET /dsh-voice/status` — состояние демонов, цепочки, конфигурация SenseVoice, реалтайма и effectiveSensevoiceProvider.
223
223
  * `GET/PUT /dsh-voice/config` — получение и сохранение конфигурации плагина.
224
224
  * `GET/POST /dsh-voice/sensevoice-installer` — статус и установка модели SenseVoice в 1 клик.
225
225
  * `POST /api/dsh-voice/update` — запуск обновления плагина до актуальной версии из npm (с проверкой прав и блокировки профиля).
package/README.zh.md CHANGED
@@ -138,7 +138,7 @@ v0.8.18 质量审查后的修复批次(Gitea #79–#87,PR #88):
138
138
 
139
139
  * `POST /dsh-voice/transcribe` — 音频转写:`{ dataBase64, mimeType, mode }` → `{ ok, text, provider, tookMs }`
140
140
  * `POST /dsh-voice/polish` — 文本模型润色:`{ text }` → `{ ok, text }`
141
- * `GET /dsh-voice/status` — 查询后端状态、服务商健康度与 SenseVoice
141
+ * `GET /dsh-voice/status` — 查询后端状态、服务商健康度、SenseVoice 及 effectiveSensevoiceProvider
142
142
  * `GET /dsh-voice/config` — 获取插件运行时配置快照
143
143
  * `PUT /dsh-voice/config` — 跨网络安全持久化更新插件配置
144
144
  * `GET/POST /dsh-voice/sensevoice-installer` — SenseVoice 一键模型安装状态与触发接口(受 `isTrustedCaller` 保护,绝对路径脱敏)
package/lib/client.js CHANGED
@@ -3997,6 +3997,10 @@ voice._chainsPromise = null
3997
3997
  stopCurrent,
3998
3998
  submitPending,
3999
3999
  loadWebGpuWhisper,
4000
+ waitStop,
4001
+ currentLevel,
4002
+ accentColor,
4003
+ softColor,
4000
4004
  }
4001
4005
  return module.exports
4002
4006
  },
@@ -1,5 +1,6 @@
1
1
  // lib/host-routes.js — Host HTTP routes for dsh-voice status, polish, and transcribe
2
2
  import { writeJson, readBody, isTrustedCaller } from './http-util.js'
3
+ import { resolveSensevoiceProvider } from './sensevoice-installer.js'
3
4
 
4
5
  export function registerHostRoutes(ctx, {
5
6
  live,
@@ -63,6 +64,7 @@ export function registerHostRoutes(ctx, {
63
64
  sensevoiceUrl: cfg.sensevoiceUrl,
64
65
  sensevoiceAutostart: !!cfg.sensevoiceAutostart,
65
66
  sensevoiceProvider: cfg.sensevoiceProvider || 'cpu',
67
+ effectiveSensevoiceProvider: resolveSensevoiceProvider(cfg),
66
68
  sensevoiceThreads: cfg.sensevoiceThreads || 4,
67
69
  webgpuModel: cfg.webgpuModel || 'onnx-community/whisper-tiny',
68
70
  deepgramBaseUrl: cfg.deepgramBaseUrl || 'https://api.deepgram.com',
package/lib/index.js CHANGED
@@ -47,6 +47,23 @@ export const Config = typeof z.number().volatile === 'function'
47
47
  ? makeVolatile(BaseConfig)
48
48
  : BaseConfig
49
49
 
50
+ export function isVolatileRef(value) {
51
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && typeof value.get === 'function'
52
+ }
53
+
54
+ export function plainConfig(value) {
55
+ while (typeof value === 'function') {
56
+ value = value()
57
+ }
58
+ if (isVolatileRef(value)) return plainConfig(value.get())
59
+ if (value === null || typeof value !== 'object') return value
60
+ if (value instanceof Date || value instanceof RegExp || (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))) {
61
+ return value
62
+ }
63
+ if (Array.isArray(value)) return value.map(plainConfig)
64
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, plainConfig(v)]))
65
+ }
66
+
50
67
  export function apply(ctx, baseConfig) {
51
68
  let settingsService = null
52
69
  let scope = null
@@ -55,7 +72,25 @@ export function apply(ctx, baseConfig) {
55
72
  const readEntryConfig = createConfigReader(baseConfig, ctx)
56
73
  getConfig = readEntryConfig
57
74
 
58
- const live = () => BaseConfig(structuredClone(getConfig() ?? {})) ?? readEntryConfig()
75
+ const describeRow = () => {
76
+ if (typeof settingsService?.describe !== 'function') return null
77
+ try {
78
+ return settingsService.describe().find((r) => r && r.ns === NS) || null
79
+ } catch {
80
+ return null
81
+ }
82
+ }
83
+
84
+ const live = () => {
85
+ try {
86
+ const raw = getConfig() ?? {}
87
+ const unwrapped = plainConfig(raw)
88
+ const validated = BaseConfig(unwrapped)
89
+ return plainConfig(validated) ?? plainConfig(readEntryConfig())
90
+ } catch {
91
+ return plainConfig(readEntryConfig())
92
+ }
93
+ }
59
94
 
60
95
  const resolveKey = async (ref) => {
61
96
  if (!ref || !ref.type) return ''
@@ -81,14 +116,19 @@ export function apply(ctx, baseConfig) {
81
116
  try {
82
117
  scope = sctx.settings.register(NS, Config, { base: baseConfig })
83
118
  if (scope && typeof scope.get === 'function') {
84
- getConfig = () => scope.get() ?? readEntryConfig()
119
+ getConfig = () => scope.get() ?? describeRow()?.config ?? readEntryConfig()
120
+ } else {
121
+ getConfig = () => describeRow()?.config ?? readEntryConfig()
85
122
  }
86
123
  if (scope && typeof scope.watch === 'function') {
87
124
  scope.watch(() => {
88
125
  triggerAutostart()
89
126
  })
90
127
  }
91
- } catch { /* modern DSH handles settings via SettingsForms */ }
128
+ } catch {
129
+ // Modern DSH 0.2 handles settings via SettingsForms and describe/update/replace
130
+ getConfig = () => describeRow()?.config ?? readEntryConfig()
131
+ }
92
132
  sctx.effect(() => () => { getConfig = readEntryConfig; scope = null; settingsService = null })
93
133
  triggerAutostart()
94
134
  })
@@ -231,6 +231,31 @@ export function registerSensevoiceInstaller(ctx, { liveConfig, isAlive, updateCo
231
231
  })
232
232
  }
233
233
 
234
+ export function resolveSensevoiceProvider(cfg) {
235
+ let modelPath = String(cfg?.sensevoiceModel || '')
236
+ const rawProv = String(cfg?.sensevoiceProvider || '').trim().toLowerCase()
237
+ let normProv = rawProv === 'rknpu' ? 'rknn' : rawProv
238
+
239
+ if (normProv === 'rknn') {
240
+ const ext = path.extname(modelPath).toLowerCase()
241
+ if (ext === '.onnx') {
242
+ const modelDir = modelPath ? path.dirname(modelPath) : getDefaultModelDir()
243
+ const rknnCandidate = path.join(modelDir, path.basename(modelPath, ext) + '.rknn')
244
+ const defaultRknn = path.join(modelDir, 'model.rknn')
245
+ if (existsSync(rknnCandidate) || existsSync(defaultRknn)) {
246
+ return 'rknn'
247
+ }
248
+ // Guard: ordinary ONNX model cannot be loaded by sherpa-onnx RKNN runtime (#221)
249
+ return 'cpu'
250
+ }
251
+ if (ext === '.rknn') {
252
+ return 'rknn'
253
+ }
254
+ }
255
+
256
+ return normProv || 'cpu'
257
+ }
258
+
234
259
  export function buildSensevoiceArgs(cfg, port) {
235
260
  let modelPath = String(cfg?.sensevoiceModel || '')
236
261
  const modelDir = modelPath ? path.dirname(modelPath) : getDefaultModelDir()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "Voice input for DeepSeek Harness: dictation chunked by pauses and voice messages, each with its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp, plus any OpenAI-compatible API of your own).",
5
5
  "license": "MIT",
6
6
  "type": "module",