@goodandready/dsh-voice 0.9.43 → 0.9.44
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 +14 -18
- package/lib/host-routes.js +1 -0
- package/lib/schema.js +2 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -852,8 +852,7 @@ window.__ModuleLoader__.load({
|
|
|
852
852
|
const text = chunks.join(' ')
|
|
853
853
|
if (!text || text.length < 3) return []
|
|
854
854
|
const matches = text.match(/[A-Za-zА-Яа-яЁё_][A-Za-zА-Яа-яЁё0-9_]{2,29}/g) || []
|
|
855
|
-
//
|
|
856
|
-
// Language processing data for context analysis, distinct from user-facing UI text.
|
|
855
|
+
// Stop-word dictionary for vocabulary keyword extraction
|
|
857
856
|
const stop = new Set([
|
|
858
857
|
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'any', 'can', 'her', 'was',
|
|
859
858
|
'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'man', 'new', 'now',
|
|
@@ -983,9 +982,7 @@ window.__ModuleLoader__.load({
|
|
|
983
982
|
return lead + s + trail
|
|
984
983
|
}
|
|
985
984
|
|
|
986
|
-
//
|
|
987
|
-
// Linguistic data for audio transcript post-processing; not user-facing UI text.
|
|
988
|
-
// Russian phrases match RU STT output; English covers EN dictation.
|
|
985
|
+
// Speech edit commands (#37): spoken line breaks and punctuation -> symbols
|
|
989
986
|
const VOICE_COMMANDS = [
|
|
990
987
|
[/(^|[\s,.!?])с новой строки([\s,.!?]|$)/gi, '$1\n$2'],
|
|
991
988
|
[/(^|[\s,.!?])новая строка([\s,.!?]|$)/gi, '$1\n$2'],
|
|
@@ -1033,22 +1030,26 @@ window.__ModuleLoader__.load({
|
|
|
1033
1030
|
function insertDraftText(text, target) {
|
|
1034
1031
|
if (voice.disposed) return false
|
|
1035
1032
|
const span = target && target.span
|
|
1033
|
+
const sIn = (target && target.input) || voice.input
|
|
1034
|
+
const sDraft = (sIn && typeof sIn.draft === 'string') ? sIn.draft : ''
|
|
1035
|
+
const spaced = (sDraft && !/\s$/.test(sDraft) && !/^\s/.test(text)
|
|
1036
|
+
&& !/^[.,;:!?…»)\]}]/.test(text) && !/[\s(«"'\[]$/.test(sDraft)) ? ' ' + text : text
|
|
1036
1037
|
if (span) {
|
|
1037
1038
|
if (typeof span.insert === 'function') {
|
|
1038
1039
|
try {
|
|
1039
|
-
const r = span.insert(
|
|
1040
|
+
const r = span.insert(spaced)
|
|
1040
1041
|
if (r !== false) return true
|
|
1041
1042
|
return false
|
|
1042
1043
|
} catch (_) {}
|
|
1043
1044
|
} else if (typeof span.insertText === 'function') {
|
|
1044
1045
|
try {
|
|
1045
|
-
const r = span.insertText(
|
|
1046
|
+
const r = span.insertText(spaced)
|
|
1046
1047
|
if (r !== false) return true
|
|
1047
1048
|
return false
|
|
1048
1049
|
} catch (_) {}
|
|
1049
1050
|
} else if (typeof span === 'function') {
|
|
1050
1051
|
try {
|
|
1051
|
-
const r = span(
|
|
1052
|
+
const r = span(spaced)
|
|
1052
1053
|
if (r !== false) return true
|
|
1053
1054
|
return false
|
|
1054
1055
|
} catch (_) {}
|
|
@@ -1062,7 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
1062
1063
|
}
|
|
1063
1064
|
if (typeof actions.insertText === 'function') {
|
|
1064
1065
|
try {
|
|
1065
|
-
const res = actions.insertText(
|
|
1066
|
+
const res = actions.insertText(spaced, span)
|
|
1066
1067
|
if (res !== false) {
|
|
1067
1068
|
if (target && typeof actions.captureInsertion === 'function') {
|
|
1068
1069
|
try { target.span = actions.captureInsertion() } catch (_) {}
|
|
@@ -1124,14 +1125,7 @@ window.__ModuleLoader__.load({
|
|
|
1124
1125
|
} catch (noAudio) { /* no audio — not critical */ }
|
|
1125
1126
|
}
|
|
1126
1127
|
|
|
1127
|
-
//
|
|
1128
|
-
//
|
|
1129
|
-
// A separate leg unlike the others: the browser recognizes speech itself,
|
|
1130
|
-
// nothing is uploaded to the host, no keys are needed, and text appears
|
|
1131
|
-
// word by word while you speak.
|
|
1132
|
-
//
|
|
1133
|
-
// Cost: in Chrome audio goes to Google servers. This provider is never
|
|
1134
|
-
// enabled automatically — only when placed explicitly in a chain.
|
|
1128
|
+
// browser recognition: in-browser, no upload, only enabled when in chain
|
|
1135
1129
|
function speechRecognitionCtor() {
|
|
1136
1130
|
if (typeof window === 'undefined') return null
|
|
1137
1131
|
return window.SpeechRecognition || window.webkitSpeechRecognition || null
|
|
@@ -1658,7 +1652,8 @@ voice._chainsPromise = null
|
|
|
1658
1652
|
} else {
|
|
1659
1653
|
rec.sileroSpeech = null
|
|
1660
1654
|
}
|
|
1661
|
-
|
|
1655
|
+
const lowT = Number(voice.settings?.lowVolumeThreshold ?? 0.08)
|
|
1656
|
+
if (rec.hadSpeech && raw > 0 && raw < lowT) {
|
|
1662
1657
|
rec.lowVolumeUntil = now + 800
|
|
1663
1658
|
rec.hasLowVolume = true
|
|
1664
1659
|
}
|
|
@@ -3156,6 +3151,7 @@ function cycleDictationMode() {
|
|
|
3156
3151
|
sessionCommands: !!(data && data.modes && data.modes.message && data.modes.message.sessionCommands),
|
|
3157
3152
|
noiseSuppression: data && data.noiseSuppression !== false,
|
|
3158
3153
|
noiseGateDb: Number(data && data.noiseGateDb !== undefined ? data.noiseGateDb : -45),
|
|
3154
|
+
lowVolumeThreshold: Number(data && data.lowVolumeThreshold !== undefined ? data.lowVolumeThreshold : 0.08),
|
|
3159
3155
|
contextGlossary: data && data.contextGlossary !== false,
|
|
3160
3156
|
visualizerStyle: (data && data.visualizerStyle) || 'liquid-wave',
|
|
3161
3157
|
gatedTurnTaking: data && data.gatedTurnTaking !== false,
|
package/lib/host-routes.js
CHANGED
|
@@ -74,6 +74,7 @@ export function registerHostRoutes(ctx, deps) {
|
|
|
74
74
|
localOnly: cfg.localOnly,
|
|
75
75
|
micDeviceId: cfg.micDeviceId,
|
|
76
76
|
noiseGateDb: cfg.noiseGateDb !== undefined ? cfg.noiseGateDb : -45,
|
|
77
|
+
lowVolumeThreshold: typeof cfg.lowVolumeThreshold === 'number' ? cfg.lowVolumeThreshold : 0.08,
|
|
77
78
|
historyLimit: cfg.historyLimit,
|
|
78
79
|
voiceCommands: cfg.voiceCommands,
|
|
79
80
|
wakeWord: String(cfg.wakeWord || ''),
|
package/lib/schema.js
CHANGED
|
@@ -106,6 +106,8 @@ export const BaseConfig = z.object({
|
|
|
106
106
|
.description('Microphone device id for recording. Empty means the system default.'),
|
|
107
107
|
noiseGateDb: z.number().default(-45)
|
|
108
108
|
.description('Audio noise gate threshold in dB for client-side recording (-50 to -25 dB, or <= -90 to disable). Silence below this threshold cuts ambient background noise and prevents false VAD triggers.'),
|
|
109
|
+
lowVolumeThreshold: z.number().min(0).max(1).default(0.08)
|
|
110
|
+
.description('Recording level (0..1) below which the "Low signal / speak louder" hint appears while dictating. Lower it for quiet microphones; 0 disables the hint.'),
|
|
109
111
|
vadEngine: z.union(['silero', 'amplitude']).default('silero')
|
|
110
112
|
.description('Voice activity detection engine: neural Silero VAD (WASM/ONNX) or classic amplitude threshold.'),
|
|
111
113
|
streamingWs: z.boolean().default(true)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.44",
|
|
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",
|