@goodandready/dsh-voice 0.9.1 → 0.9.2

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/lib/client.js CHANGED
@@ -547,15 +547,15 @@ window.__ModuleLoader__.load({
547
547
  const trimmed = String(text || '').trim().toLowerCase()
548
548
  const { buttons, input } = promptInfo
549
549
  if (buttons && buttons.length > 0) {
550
- if (/^(1|первый|first)$/i.test(trimmed) && buttons[0]) { buttons[0].click(); return true }
551
- if (/^(2|второй|second)$/i.test(trimmed) && buttons[1]) { buttons[1].click(); return true }
552
- if (/^(3|третий|third)$/i.test(trimmed) && buttons[2]) { buttons[2].click(); return true }
553
- if (/^(да|yes|ok|confirm|согласен)$/i.test(trimmed)) {
554
- const yesBtn = buttons.find((b) => /да|yes|ok|confirm/i.test(b.textContent || ''))
550
+ if (/^(1|первый|first|第一)$/i.test(trimmed) && buttons[0]) { buttons[0].click(); return true }
551
+ if (/^(2|второй|second|第二)$/i.test(trimmed) && buttons[1]) { buttons[1].click(); return true }
552
+ if (/^(3|третий|third|第三)$/i.test(trimmed) && buttons[2]) { buttons[2].click(); return true }
553
+ if (/^(да|yes|ok|confirm|согласен|是|好|确认|同意)$/i.test(trimmed)) {
554
+ const yesBtn = buttons.find((b) => /да|yes|ok|confirm|是|好|确认|同意/i.test(b.textContent || ''))
555
555
  if (yesBtn) { yesBtn.click(); return true }
556
556
  }
557
- if (/^(нет|no|отмена|cancel)$/i.test(trimmed)) {
558
- const noBtn = buttons.find((b) => /нет|no|cancel|отмена/i.test(b.textContent || ''))
557
+ if (/^(нет|no|отмена|cancel|不|否|取消)$/i.test(trimmed)) {
558
+ const noBtn = buttons.find((b) => /нет|no|cancel|отмена|不|否|取消/i.test(b.textContent || ''))
559
559
  if (noBtn) { noBtn.click(); return true }
560
560
  }
561
561
  }
@@ -697,8 +697,8 @@ window.__ModuleLoader__.load({
697
697
  if (!res.ok || !parsed || !parsed.ok) {
698
698
  throw new Error((parsed && parsed.error && parsed.error.message) || `HTTP ${res.status}`)
699
699
  }
700
- if (parsed.command) return { command: parsed.command, text: '' }
701
- return { text: String(parsed.text || '').trim(), command: null }
700
+ if (parsed.command) return { command: parsed.command, text: '', action: null }
701
+ return { text: String(parsed.text || '').trim(), command: null, action: parsed.action || null }
702
702
  }
703
703
 
704
704
  function tidyPhrase(text) {
@@ -893,7 +893,10 @@ window.__ModuleLoader__.load({
893
893
  chainsPromise = fetch('/dsh-voice/status', { cache: 'no-store' })
894
894
  .then((res) => res.json())
895
895
  .then((data) => (data && data.modes) || {})
896
- .catch(() => ({}))
896
+ .catch(() => {
897
+ chainsPromise = null
898
+ return {}
899
+ })
897
900
  }
898
901
  return chainsPromise.then((modes) => {
899
902
  const row = modes[mode] || {}
@@ -2187,6 +2190,7 @@ window.__ModuleLoader__.load({
2187
2190
  const [err, setErr] = React.useState('')
2188
2191
  const [serverConfig, setServerConfig] = React.useState(null)
2189
2192
  const [configLoading, setConfigLoading] = React.useState(true)
2193
+ const [jargonInput, setJargonInput] = React.useState(null)
2190
2194
 
2191
2195
  React.useEffect(() => {
2192
2196
  let alive = true
@@ -2505,6 +2509,21 @@ window.__ModuleLoader__.load({
2505
2509
  const save = async () => {
2506
2510
  setErr(''); setSaved(false)
2507
2511
  if (!draft) return
2512
+ if (jargonInput !== null) {
2513
+ const lines = jargonInput.split('\n')
2514
+ const parsed = lines
2515
+ .map((line) => {
2516
+ const parts = line.split(/->|=|:/)
2517
+ if (parts.length >= 2) {
2518
+ const from = parts[0].trim()
2519
+ const to = parts.slice(1).join(':').trim()
2520
+ if (from && to) return { from, to }
2521
+ }
2522
+ return null
2523
+ })
2524
+ .filter(Boolean)
2525
+ draft.jargonDictionary = parsed
2526
+ }
2508
2527
 
2509
2528
  let httpOk = false
2510
2529
  let httpError = null
@@ -3028,16 +3047,24 @@ window.__ModuleLoader__.load({
3028
3047
  rows: 3,
3029
3048
  disabled: !writable,
3030
3049
  placeholder: t('jargonPlaceholder'),
3031
- value: Array.isArray(draft && draft.jargonDictionary)
3032
- ? draft.jargonDictionary.map((item) => item && item.from ? `${item.from} -> ${item.to}` : '').filter(Boolean).join('\n')
3033
- : '',
3050
+ value: jargonInput !== null
3051
+ ? jargonInput
3052
+ : (Array.isArray(draft && draft.jargonDictionary)
3053
+ ? draft.jargonDictionary.map((item) => item && item.from ? `${item.from} -> ${item.to}` : '').filter(Boolean).join('\n')
3054
+ : ''),
3034
3055
  onChange: (e) => {
3035
- const lines = e.target.value.split('\n')
3056
+ setJargonInput(e.target.value)
3057
+ },
3058
+ onBlur: () => {
3059
+ if (jargonInput === null) return
3060
+ const lines = jargonInput.split('\n')
3036
3061
  const parsed = lines
3037
3062
  .map((line) => {
3038
3063
  const parts = line.split(/->|=|:/)
3039
3064
  if (parts.length >= 2) {
3040
- return { from: parts[0].trim(), to: parts.slice(1).join(':').trim() }
3065
+ const from = parts[0].trim()
3066
+ const to = parts.slice(1).join(':').trim()
3067
+ if (from && to) return { from, to }
3041
3068
  }
3042
3069
  return null
3043
3070
  })
@@ -3110,7 +3137,7 @@ window.__ModuleLoader__.load({
3110
3137
  React.createElement('div', { className: 'cb-section-desc' }, t('versionHint')),
3111
3138
  React.createElement('div', { className: 'cb-row', style: { alignItems: 'center', gap: '12px' } },
3112
3139
  React.createElement('span', { className: 'cb-badge cb-badge-ok' },
3113
- 'v' + ((updaterStatus && updaterStatus.currentVersion) || '0.9.1')
3140
+ 'v' + ((updaterStatus && updaterStatus.currentVersion) || '0.9.2')
3114
3141
  ),
3115
3142
  updaterStatus && updaterStatus.updateAvailable
3116
3143
  ? React.createElement('span', { className: 'cb-badge cb-badge-warn' },
package/lib/index.js CHANGED
@@ -27,14 +27,11 @@ import { createPolishText } from './polish.js'
27
27
  import { createUserMessage } from '@deepseek-ai/dsh-llm'
28
28
  import { buildProviderOrder, sessionCommand } from './transcribe-core.js'
29
29
  import { registerPluginUpdater } from './updater.js'
30
- import { registerSensevoiceInstaller } from './sensevoice-installer.js'
30
+ import { registerSensevoiceInstaller, findTokensFile } from './sensevoice-installer.js'
31
31
  import { createConfigReader } from './config-compat.js'
32
32
  import { registerConfigRoutes } from './config-routes.js'
33
33
  import { getAllowedAudioRoots, validateAudioPath, validateAudioContent } from './audio-guard.js'
34
-
35
- function isAutoLang(lang) {
36
- return !lang || lang === 'auto' || String(lang).includes(',')
37
- }
34
+ import { isAutoLang } from './provider-http.js'
38
35
 
39
36
  export const name = '@goodandready/dsh-voice'
40
37
  export const NS = 'dsh-voice'
@@ -324,9 +321,12 @@ export function apply(ctx, baseConfig) {
324
321
  try {
325
322
  let port = '6006'
326
323
  try { port = new URL(cfg.sensevoiceUrl).port || '6006' } catch { /* invalid URL */ }
324
+ const tokensPath = findTokensFile(path.dirname(cfg.sensevoiceModel))
325
+ const tokensArg = tokensPath ? ` --tokens=${JSON.stringify(tokensPath)}` : ''
327
326
  const spec = ctx.shell.resolve({
328
327
  command: `${JSON.stringify(cfg.sensevoiceBin)}`
329
328
  + ` --sense-voice-model=${JSON.stringify(cfg.sensevoiceModel)}`
329
+ + tokensArg
330
330
  + ` --port=${port} --num-threads=4`,
331
331
  timeoutMs: 0,
332
332
  stdoutMaxBytes: 4 * 1024 * 1024,
@@ -595,7 +595,7 @@ export function apply(ctx, baseConfig) {
595
595
  },
596
596
  }), 'dsh-voice: /transcribe route')
597
597
 
598
- ctx.tools.register(
598
+ ctx.effect(() => ctx.tools.register(
599
599
  defineTool({
600
600
  name: 'transcribe_audio',
601
601
  description:
@@ -649,7 +649,7 @@ export function apply(ctx, baseConfig) {
649
649
  return out
650
650
  },
651
651
  }),
652
- )
652
+ ), 'dsh-voice: transcribe_audio tool')
653
653
 
654
654
  ctx.effect(() => () => {
655
655
  if (child) { try { child.kill && child.kill() } catch { /* already dead */ } }
package/lib/normalize.js CHANGED
@@ -128,28 +128,28 @@ export function extractVoiceActions(text) {
128
128
  if (!raw) return { text: '', action: null }
129
129
 
130
130
  const lower = raw.toLowerCase().replace(/[.!?…]+$/, '').trim()
131
- if (/^(отправь|отправить|send|send it|send message)$/i.test(lower)) {
131
+ if (/^(отправь|отправить|send|send it|send message|发送|发出)$/i.test(lower)) {
132
132
  return { text: '', action: 'send' }
133
133
  }
134
- if (/^(отмена|cancel|discard)$/i.test(lower)) {
134
+ if (/^(отмена|cancel|discard|取消|算了)$/i.test(lower)) {
135
135
  return { text: '', action: 'cancel' }
136
136
  }
137
- if (/^(очисти|очистить|clear|clear composer)$/i.test(lower)) {
137
+ if (/^(очисти|очистить|clear|clear composer|清空|清除)$/i.test(lower)) {
138
138
  return { text: '', action: 'clear' }
139
139
  }
140
- if (/^(новая строка|с новой строки|new line|newline)$/i.test(lower)) {
140
+ if (/^(новая строка|с новой строки|new line|newline|换行|新行)$/i.test(lower)) {
141
141
  return { text: '', action: 'newline' }
142
142
  }
143
143
 
144
- const trailingSend = /[\s,]+(отправь|отправить|send|send it)[.!?…]*$/i
144
+ const trailingSend = /[\s,]+(отправь|отправить|send|send it|发送|发出)[.!?…]*$/i
145
145
  if (trailingSend.test(raw)) {
146
146
  return { text: raw.replace(trailingSend, '').trim(), action: 'send' }
147
147
  }
148
- const trailingCancel = /[\s,]+(отмена|cancel)[.!?…]*$/i
148
+ const trailingCancel = /[\s,]+(отмена|cancel|取消|算了)[.!?…]*$/i
149
149
  if (trailingCancel.test(raw)) {
150
150
  return { text: raw.replace(trailingCancel, '').trim(), action: 'cancel' }
151
151
  }
152
- const trailingNewline = /[\s,]+(новая строка|с новой строки|new line)[.!?…]*$/i
152
+ const trailingNewline = /[\s,]+(новая строка|с новой строки|new line|换行|新行)[.!?…]*$/i
153
153
  if (trailingNewline.test(raw)) {
154
154
  return { text: raw.replace(trailingNewline, '').trim() + '\n', action: 'newline' }
155
155
  }
@@ -2,7 +2,7 @@
2
2
  // Downloads pre-quantized ONNX model and configures autostart on the host.
3
3
 
4
4
  import { createWriteStream, existsSync, readdirSync } from 'node:fs'
5
- import { mkdir, stat } from 'node:fs/promises'
5
+ import { mkdir, stat, unlink } from 'node:fs/promises'
6
6
  import { homedir } from 'node:os'
7
7
  import path from 'node:path'
8
8
  import { spawn } from 'node:child_process'
@@ -161,6 +161,7 @@ export async function startSensevoiceInstall(ctx, cfg, updateConfigFn, startSens
161
161
  installerState.step = 'extracting'
162
162
  installerState.progress = 95
163
163
  await extractTarBz2(archivePath, modelDir)
164
+ await unlink(archivePath).catch(() => {})
164
165
 
165
166
  const modelFile = findModelFile(modelDir)
166
167
  if (!modelFile) throw new Error('model.int8.onnx not found after extraction')
@@ -183,6 +184,7 @@ export async function startSensevoiceInstall(ctx, cfg, updateConfigFn, startSens
183
184
 
184
185
  return { ok: true, modelPath: modelFile }
185
186
  } catch (err) {
187
+ await unlink(archivePath).catch(() => {})
186
188
  installerState.installing = false
187
189
  installerState.error = String(err?.message || err)
188
190
  installerState.step = 'failed'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-voice",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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",
@@ -43,6 +43,7 @@
43
43
  "scripts": {
44
44
  "test": "node --test test/*.test.mjs",
45
45
  "build:client": "node scripts/build-client.mjs",
46
+ "build:check": "node scripts/build-client.mjs --check",
46
47
  "pretest": "node scripts/build-client.mjs",
47
48
  "lint": "node --check lib/*.js scripts/*.mjs",
48
49
  "pack:check": "node scripts/pack-check.mjs"