@lumiastream/wakeword 1.1.5-alpha.2 → 1.1.5-alpha.4

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.
Files changed (2) hide show
  1. package/lib/voice.js +49 -14
  2. package/package.json +1 -1
package/lib/voice.js CHANGED
@@ -5,6 +5,14 @@ import { fileURLToPath } from "node:url";
5
5
  import { existsSync, chmodSync } from "node:fs";
6
6
  import readline from "node:readline";
7
7
 
8
+ /* ------------------------------------------------------------------ */
9
+ /* 0. Helpers */
10
+ /* ------------------------------------------------------------------ */
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+
13
+ const ASAR_TOKEN = "app.asar";
14
+ const ASAR_UNPACKED_TOKEN = "app.asar.unpacked";
15
+
8
16
  // Ensure native libs can load from app.asar.unpacked when packaged
9
17
  const maybeUnpackedPath = (libPath) => {
10
18
  if (typeof libPath !== "string") return libPath;
@@ -33,14 +41,6 @@ koffi.load = (libPath, ...rest) => {
33
41
 
34
42
  const { Model, Recognizer, setLogLevel } = await import("vosk-koffi");
35
43
 
36
- /* ------------------------------------------------------------------ */
37
- /* 0. Helpers */
38
- /* ------------------------------------------------------------------ */
39
- const here = dirname(fileURLToPath(import.meta.url));
40
-
41
- const ASAR_TOKEN = "app.asar";
42
- const ASAR_UNPACKED_TOKEN = "app.asar.unpacked";
43
-
44
44
  function unpacked(p) {
45
45
  if (!p || !p.includes(ASAR_TOKEN)) return p;
46
46
  if (p.includes(ASAR_UNPACKED_TOKEN)) return p;
@@ -156,6 +156,9 @@ const LOG_PARTIAL =
156
156
  ["1", "true", "yes"].includes(
157
157
  (process.env.WAKEWORD_LOG_PARTIAL || "").toLowerCase()
158
158
  );
159
+ let LOG_FINAL = ["1", "true", "yes"].includes(
160
+ (process.env.WAKEWORD_LOG_FINAL || "").toLowerCase()
161
+ );
159
162
  let lastLevelLog = 0;
160
163
 
161
164
  function logAudioLevel(buf) {
@@ -222,25 +225,49 @@ mic.on("data", (buf) => {
222
225
  function handle(processedWord, averageConfidence, originalText) {
223
226
  if (!processedWord && !originalText) return;
224
227
 
228
+ const finalSentence =
229
+ typeof originalText === "string" && originalText.trim()
230
+ ? originalText.trim()
231
+ : (processedWord ?? "").toString().trim();
232
+ if (LOG_FINAL && finalSentence) {
233
+ process.stdout?.write(`final|${finalSentence}\n`);
234
+ }
235
+
225
236
  const normalizedProcessed = normalizePhrase(processedWord);
226
237
  const normalizedOriginal = normalizePhrase(originalText);
227
238
  const matches = new Set();
239
+ const confidentCommands = new Set();
228
240
 
229
- const findMatches = (text) => {
241
+ const findMatches = (text, allowedCommands = COMMANDS) => {
230
242
  if (!text || text.includes(UNKNOWN_TOKEN)) return;
231
243
  const hits = MATCH_SENTENCE
232
- ? COMMANDS.filter((command) => text.includes(command))
233
- : COMMANDS.filter((command) => text === command);
244
+ ? allowedCommands.filter((command) => text.includes(command))
245
+ : allowedCommands.filter((command) => text === command);
234
246
  hits.forEach((hit) => matches.add(hit));
235
247
  };
236
248
 
237
- // Try the filtered text first, then fall back to the raw sentence for sentence matching
249
+ // Only allow sentence matches for commands that were confidently recognized.
250
+ if (normalizedProcessed) {
251
+ COMMANDS.forEach((command) => {
252
+ const isMatch = MATCH_SENTENCE
253
+ ? normalizedProcessed.includes(command)
254
+ : normalizedProcessed === command;
255
+ if (isMatch) {
256
+ confidentCommands.add(command);
257
+ }
258
+ });
259
+ }
260
+
261
+ // Try the filtered text first, then fall back to the raw sentence only for confident commands.
238
262
  findMatches(normalizedProcessed);
239
- findMatches(normalizedOriginal);
263
+ findMatches(normalizedOriginal, [...confidentCommands]);
240
264
 
241
265
  if (!matches.size) return;
242
266
 
243
267
  matches.forEach((match) => {
268
+ if (finalSentence) {
269
+ process.stdout?.write(`sentence|${finalSentence}\n`);
270
+ }
244
271
  process.stdout?.write(`voice|${match}\n`);
245
272
  process.stdout?.write(`confidence|${averageConfidence}\n`);
246
273
  });
@@ -252,13 +279,21 @@ const rl = readline.createInterface({ input: process.stdin, terminal: false });
252
279
 
253
280
  rl.on("line", (line) => {
254
281
  const trimmed = line.trim();
255
- if (!trimmed.startsWith("update,") && !trimmed.startsWith("confidence,"))
282
+ if (
283
+ !trimmed.startsWith("update,") &&
284
+ !trimmed.startsWith("confidence,") &&
285
+ !trimmed.startsWith("debug,")
286
+ )
256
287
  return;
257
288
 
258
289
  if (trimmed.startsWith("confidence,")) {
259
290
  WORD_CONFIDENCE_THRESHOLD = Number(trimmed.split(",")[1]);
260
291
  return;
261
292
  }
293
+ if (trimmed.startsWith("debug,")) {
294
+ LOG_FINAL = toBool(trimmed.split(",")[1]);
295
+ return;
296
+ }
262
297
 
263
298
  const phrases = trimmed
264
299
  .split(",")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/wakeword",
3
- "version": "1.1.5-alpha.2",
3
+ "version": "1.1.5-alpha.4",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "files": [