@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 +43 -16
- package/lib/index.js +7 -7
- package/lib/normalize.js +7 -7
- package/lib/sensevoice-installer.js +3 -1
- package/package.json +2 -1
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
|
|
554
|
-
const yesBtn = buttons.find((b) => /да|yes|ok|confirm
|
|
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
|
|
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:
|
|
3032
|
-
?
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|