@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 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
- // Linguistic filter: functional stop-word dictionary for vocabulary keyword extraction.
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
- // Functional speech edit commands (#37): spoken line breaks and punctuation -> symbols.
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(text)
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(text)
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(text)
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(text, span)
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
- // ------------------------------------------------- browser recognition
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
- if (rec.hadSpeech && raw > 0 && raw < 0.08) {
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,
@@ -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.43",
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",