@staff0rd/assist 0.87.0 → 0.88.0
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.
|
@@ -252,7 +252,9 @@ dependencies = [
|
|
|
252
252
|
|
|
253
253
|
[package.optional-dependencies]
|
|
254
254
|
dev = [
|
|
255
|
+
{ name = "radon" },
|
|
255
256
|
{ name = "ruff" },
|
|
257
|
+
{ name = "xenon" },
|
|
256
258
|
]
|
|
257
259
|
|
|
258
260
|
[package.metadata]
|
|
@@ -260,10 +262,12 @@ requires-dist = [
|
|
|
260
262
|
{ name = "nemo-toolkit", extras = ["asr"], specifier = ">=1.22" },
|
|
261
263
|
{ name = "numpy", specifier = ">=1.24" },
|
|
262
264
|
{ name = "onnxruntime", specifier = ">=1.17" },
|
|
265
|
+
{ name = "radon", marker = "extra == 'dev'", specifier = ">=6.0" },
|
|
263
266
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8" },
|
|
264
267
|
{ name = "silero-vad", specifier = ">=5.1" },
|
|
265
268
|
{ name = "sounddevice", specifier = ">=0.4" },
|
|
266
269
|
{ name = "torch", specifier = ">=2.0", index = "https://download.pytorch.org/whl/cu124" },
|
|
270
|
+
{ name = "xenon", marker = "extra == 'dev'", specifier = ">=0.9" },
|
|
267
271
|
]
|
|
268
272
|
provides-extras = ["dev"]
|
|
269
273
|
|
|
@@ -2146,6 +2150,18 @@ wheels = [
|
|
|
2146
2150
|
{ url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" },
|
|
2147
2151
|
]
|
|
2148
2152
|
|
|
2153
|
+
[[package]]
|
|
2154
|
+
name = "mando"
|
|
2155
|
+
version = "0.7.1"
|
|
2156
|
+
source = { registry = "https://pypi.org/simple" }
|
|
2157
|
+
dependencies = [
|
|
2158
|
+
{ name = "six" },
|
|
2159
|
+
]
|
|
2160
|
+
sdist = { url = "https://files.pythonhosted.org/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" }
|
|
2161
|
+
wheels = [
|
|
2162
|
+
{ url = "https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" },
|
|
2163
|
+
]
|
|
2164
|
+
|
|
2149
2165
|
[[package]]
|
|
2150
2166
|
name = "markdown"
|
|
2151
2167
|
version = "3.10.2"
|
|
@@ -3120,7 +3136,7 @@ name = "nvidia-cudnn-cu12"
|
|
|
3120
3136
|
version = "9.1.0.70"
|
|
3121
3137
|
source = { registry = "https://pypi.org/simple" }
|
|
3122
3138
|
dependencies = [
|
|
3123
|
-
{ name = "nvidia-cublas-cu12" },
|
|
3139
|
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
|
|
3124
3140
|
]
|
|
3125
3141
|
wheels = [
|
|
3126
3142
|
{ url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741, upload-time = "2024-04-22T15:24:15.253Z" },
|
|
@@ -3131,7 +3147,7 @@ name = "nvidia-cufft-cu12"
|
|
|
3131
3147
|
version = "11.2.1.3"
|
|
3132
3148
|
source = { registry = "https://pypi.org/simple" }
|
|
3133
3149
|
dependencies = [
|
|
3134
|
-
{ name = "nvidia-nvjitlink-cu12" },
|
|
3150
|
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
|
|
3135
3151
|
]
|
|
3136
3152
|
wheels = [
|
|
3137
3153
|
{ url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117, upload-time = "2024-04-03T20:57:40.402Z" },
|
|
@@ -3150,9 +3166,9 @@ name = "nvidia-cusolver-cu12"
|
|
|
3150
3166
|
version = "11.6.1.9"
|
|
3151
3167
|
source = { registry = "https://pypi.org/simple" }
|
|
3152
3168
|
dependencies = [
|
|
3153
|
-
{ name = "nvidia-cublas-cu12" },
|
|
3154
|
-
{ name = "nvidia-cusparse-cu12" },
|
|
3155
|
-
{ name = "nvidia-nvjitlink-cu12" },
|
|
3169
|
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
|
|
3170
|
+
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" },
|
|
3171
|
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
|
|
3156
3172
|
]
|
|
3157
3173
|
wheels = [
|
|
3158
3174
|
{ url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057, upload-time = "2024-04-03T20:58:28.735Z" },
|
|
@@ -3163,7 +3179,7 @@ name = "nvidia-cusparse-cu12"
|
|
|
3163
3179
|
version = "12.3.1.170"
|
|
3164
3180
|
source = { registry = "https://pypi.org/simple" }
|
|
3165
3181
|
dependencies = [
|
|
3166
|
-
{ name = "nvidia-nvjitlink-cu12" },
|
|
3182
|
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
|
|
3167
3183
|
]
|
|
3168
3184
|
wheels = [
|
|
3169
3185
|
{ url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763, upload-time = "2024-04-03T20:58:59.995Z" },
|
|
@@ -4241,6 +4257,19 @@ wheels = [
|
|
|
4241
4257
|
{ url = "https://files.pythonhosted.org/packages/c0/28/26534bed77109632a956977f60d8519049f545abc39215d086e33a61f1f2/pyyaml_ft-8.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:de04cfe9439565e32f178106c51dd6ca61afaa2907d143835d501d84703d3793", size = 171579, upload-time = "2025-06-10T15:32:14.34Z" },
|
|
4242
4258
|
]
|
|
4243
4259
|
|
|
4260
|
+
[[package]]
|
|
4261
|
+
name = "radon"
|
|
4262
|
+
version = "6.0.1"
|
|
4263
|
+
source = { registry = "https://pypi.org/simple" }
|
|
4264
|
+
dependencies = [
|
|
4265
|
+
{ name = "colorama" },
|
|
4266
|
+
{ name = "mando" },
|
|
4267
|
+
]
|
|
4268
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" }
|
|
4269
|
+
wheels = [
|
|
4270
|
+
{ url = "https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" },
|
|
4271
|
+
]
|
|
4272
|
+
|
|
4244
4273
|
[[package]]
|
|
4245
4274
|
name = "rapidfuzz"
|
|
4246
4275
|
version = "3.14.3"
|
|
@@ -5652,6 +5681,20 @@ wheels = [
|
|
|
5652
5681
|
{ url = "https://files.pythonhosted.org/packages/c4/da/5a086bf4c22a41995312db104ec2ffeee2cf6accca9faaee5315c790377d/wrapt-2.1.1-py3-none-any.whl", hash = "sha256:3b0f4629eb954394a3d7c7a1c8cca25f0b07cefe6aa8545e862e9778152de5b7", size = 43886, upload-time = "2026-02-03T02:11:45.048Z" },
|
|
5653
5682
|
]
|
|
5654
5683
|
|
|
5684
|
+
[[package]]
|
|
5685
|
+
name = "xenon"
|
|
5686
|
+
version = "0.9.3"
|
|
5687
|
+
source = { registry = "https://pypi.org/simple" }
|
|
5688
|
+
dependencies = [
|
|
5689
|
+
{ name = "pyyaml" },
|
|
5690
|
+
{ name = "radon" },
|
|
5691
|
+
{ name = "requests" },
|
|
5692
|
+
]
|
|
5693
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c4/7c/2b341eaeec69d514b635ea18481885a956d196a74322a4b0942ef0c31691/xenon-0.9.3.tar.gz", hash = "sha256:4a7538d8ba08aa5d79055fb3e0b2393c0bd6d7d16a4ab0fcdef02ef1f10a43fa", size = 9883, upload-time = "2024-10-21T10:27:53.722Z" }
|
|
5694
|
+
wheels = [
|
|
5695
|
+
{ url = "https://files.pythonhosted.org/packages/6f/5d/29ff8665b129cafd147d90b86e92babee32e116e3c84447107da3e77f8fb/xenon-0.9.3-py2.py3-none-any.whl", hash = "sha256:6e2c2c251cc5e9d01fe984e623499b13b2140fcbf74d6c03a613fa43a9347097", size = 8966, upload-time = "2024-10-21T10:27:51.121Z" },
|
|
5696
|
+
]
|
|
5697
|
+
|
|
5655
5698
|
[[package]]
|
|
5656
5699
|
name = "xxhash"
|
|
5657
5700
|
version = "3.6.0"
|
|
@@ -335,70 +335,80 @@ class VoiceDaemon:
|
|
|
335
335
|
text = self._stt.transcribe(audio)
|
|
336
336
|
|
|
337
337
|
if self._state == ACTIVATED:
|
|
338
|
-
|
|
339
|
-
command = text.strip()
|
|
340
|
-
if command:
|
|
341
|
-
if command != self._typed_text:
|
|
342
|
-
if self._typed_text:
|
|
343
|
-
self._update_typed_text(command)
|
|
344
|
-
else:
|
|
345
|
-
keyboard.type_text(command)
|
|
346
|
-
self._dispatch_result(command)
|
|
347
|
-
else:
|
|
348
|
-
if self._typed_text:
|
|
349
|
-
keyboard.backspace(len(self._typed_text))
|
|
350
|
-
log("dispatch_cancelled", "Empty command in activated mode")
|
|
338
|
+
self._finalize_activated_command(text)
|
|
351
339
|
self._reset_listening()
|
|
352
340
|
return
|
|
353
341
|
|
|
354
342
|
if self._wake_detected:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if command != self._typed_text:
|
|
359
|
-
self._update_typed_text(command)
|
|
360
|
-
self._dispatch_result(command)
|
|
361
|
-
elif found:
|
|
362
|
-
# Wake word found but no command text after it
|
|
363
|
-
if self._typed_text:
|
|
364
|
-
keyboard.backspace(len(self._typed_text))
|
|
365
|
-
log("dispatch_cancelled", "No command after wake word")
|
|
366
|
-
elif self._typed_text:
|
|
367
|
-
# Final transcription lost the wake word (e.g. audio clipping
|
|
368
|
-
# turned "computer" into "uter"); fall back to the command
|
|
369
|
-
# captured during streaming
|
|
370
|
-
self._dispatch_result(self._typed_text)
|
|
371
|
-
else:
|
|
372
|
-
# Check final transcription for wake word
|
|
373
|
-
found, command = check_wake_word(text)
|
|
374
|
-
if found and command:
|
|
375
|
-
log("wake_word_detected", command)
|
|
376
|
-
if DEBUG:
|
|
377
|
-
print(f" Wake word! Final: {command}", file=sys.stderr)
|
|
378
|
-
keyboard.type_text(command)
|
|
379
|
-
self._dispatch_result(command)
|
|
380
|
-
if found and not command:
|
|
381
|
-
# Wake word only — enter ACTIVATED state for next utterance
|
|
382
|
-
log("wake_word_only", "Listening for command...")
|
|
383
|
-
if DEBUG:
|
|
384
|
-
print(
|
|
385
|
-
" Wake word heard — listening for command...", file=sys.stderr
|
|
386
|
-
)
|
|
387
|
-
self._audio_buffer.clear()
|
|
388
|
-
self._vad.reset()
|
|
389
|
-
self._wake_detected = False
|
|
390
|
-
self._typed_text = ""
|
|
391
|
-
self._last_partial_at = 0
|
|
392
|
-
self._activated_at = time.monotonic()
|
|
393
|
-
self._state = ACTIVATED
|
|
394
|
-
return # don't reset to IDLE
|
|
395
|
-
elif not found:
|
|
396
|
-
log("no_wake_word", text)
|
|
397
|
-
if DEBUG:
|
|
398
|
-
print(f" No wake word: {text}", file=sys.stderr)
|
|
343
|
+
self._finalize_streamed_wake_word(text)
|
|
344
|
+
elif not self._finalize_check_final_text(text):
|
|
345
|
+
return # entered ACTIVATED state, don't reset to IDLE
|
|
399
346
|
|
|
400
347
|
self._reset_listening()
|
|
401
348
|
|
|
349
|
+
def _finalize_activated_command(self, text: str) -> None:
|
|
350
|
+
"""Finalize utterance in ACTIVATED state (full text is the command)."""
|
|
351
|
+
command = text.strip()
|
|
352
|
+
if command:
|
|
353
|
+
if command != self._typed_text:
|
|
354
|
+
if self._typed_text:
|
|
355
|
+
self._update_typed_text(command)
|
|
356
|
+
else:
|
|
357
|
+
keyboard.type_text(command)
|
|
358
|
+
self._dispatch_result(command)
|
|
359
|
+
else:
|
|
360
|
+
if self._typed_text:
|
|
361
|
+
keyboard.backspace(len(self._typed_text))
|
|
362
|
+
log("dispatch_cancelled", "Empty command in activated mode")
|
|
363
|
+
|
|
364
|
+
def _finalize_streamed_wake_word(self, text: str) -> None:
|
|
365
|
+
"""Finalize when wake word was detected during streaming."""
|
|
366
|
+
found, command = check_wake_word(text)
|
|
367
|
+
if found and command:
|
|
368
|
+
if command != self._typed_text:
|
|
369
|
+
self._update_typed_text(command)
|
|
370
|
+
self._dispatch_result(command)
|
|
371
|
+
elif found:
|
|
372
|
+
if self._typed_text:
|
|
373
|
+
keyboard.backspace(len(self._typed_text))
|
|
374
|
+
log("dispatch_cancelled", "No command after wake word")
|
|
375
|
+
elif self._typed_text:
|
|
376
|
+
# Final transcription lost the wake word (e.g. audio clipping
|
|
377
|
+
# turned "computer" into "uter"); fall back to the command
|
|
378
|
+
# captured during streaming
|
|
379
|
+
self._dispatch_result(self._typed_text)
|
|
380
|
+
|
|
381
|
+
def _finalize_check_final_text(self, text: str) -> bool:
|
|
382
|
+
"""Check final transcription for wake word.
|
|
383
|
+
|
|
384
|
+
Returns True if caller should reset to IDLE, False if entering ACTIVATED.
|
|
385
|
+
"""
|
|
386
|
+
found, command = check_wake_word(text)
|
|
387
|
+
if found and command:
|
|
388
|
+
log("wake_word_detected", command)
|
|
389
|
+
if DEBUG:
|
|
390
|
+
print(f" Wake word! Final: {command}", file=sys.stderr)
|
|
391
|
+
keyboard.type_text(command)
|
|
392
|
+
self._dispatch_result(command)
|
|
393
|
+
if found and not command:
|
|
394
|
+
# Wake word only — enter ACTIVATED state for next utterance
|
|
395
|
+
log("wake_word_only", "Listening for command...")
|
|
396
|
+
if DEBUG:
|
|
397
|
+
print(" Wake word heard — listening for command...", file=sys.stderr)
|
|
398
|
+
self._audio_buffer.clear()
|
|
399
|
+
self._vad.reset()
|
|
400
|
+
self._wake_detected = False
|
|
401
|
+
self._typed_text = ""
|
|
402
|
+
self._last_partial_at = 0
|
|
403
|
+
self._activated_at = time.monotonic()
|
|
404
|
+
self._state = ACTIVATED
|
|
405
|
+
return False
|
|
406
|
+
elif not found:
|
|
407
|
+
log("no_wake_word", text)
|
|
408
|
+
if DEBUG:
|
|
409
|
+
print(f" No wake word: {text}", file=sys.stderr)
|
|
410
|
+
return True
|
|
411
|
+
|
|
402
412
|
def _reset_listening(self) -> None:
|
|
403
413
|
self._audio_buffer.clear()
|
|
404
414
|
self._vad.reset()
|
|
@@ -408,6 +418,21 @@ class VoiceDaemon:
|
|
|
408
418
|
self._activated_at = 0.0
|
|
409
419
|
self._state = IDLE
|
|
410
420
|
|
|
421
|
+
def _check_activated_timeout(self) -> bool:
|
|
422
|
+
"""If in ACTIVATED state with no audio buffered, check for timeout.
|
|
423
|
+
|
|
424
|
+
Returns True if timed out and state was reset.
|
|
425
|
+
"""
|
|
426
|
+
if self._state != ACTIVATED or self._audio_buffer:
|
|
427
|
+
return False
|
|
428
|
+
if time.monotonic() - self._activated_at <= ACTIVATED_TIMEOUT:
|
|
429
|
+
return False
|
|
430
|
+
log("activated_timeout", "No command received")
|
|
431
|
+
if DEBUG:
|
|
432
|
+
print("\n Activation timed out", file=sys.stderr)
|
|
433
|
+
self._reset_listening()
|
|
434
|
+
return True
|
|
435
|
+
|
|
411
436
|
def run(self) -> None:
|
|
412
437
|
signal.signal(signal.SIGTERM, self._handle_signal)
|
|
413
438
|
signal.signal(signal.SIGINT, self._handle_signal)
|
|
@@ -425,12 +450,7 @@ class VoiceDaemon:
|
|
|
425
450
|
while self._running:
|
|
426
451
|
chunk = self._mic.read(timeout=0.5)
|
|
427
452
|
if chunk is None:
|
|
428
|
-
|
|
429
|
-
if time.monotonic() - self._activated_at > ACTIVATED_TIMEOUT:
|
|
430
|
-
log("activated_timeout", "No command received")
|
|
431
|
-
if DEBUG:
|
|
432
|
-
print("\n Activation timed out", file=sys.stderr)
|
|
433
|
-
self._reset_listening()
|
|
453
|
+
self._check_activated_timeout()
|
|
434
454
|
continue
|
|
435
455
|
|
|
436
456
|
prob = self._vad.process(chunk)
|
|
@@ -448,14 +468,8 @@ class VoiceDaemon:
|
|
|
448
468
|
self._last_partial_at = 0
|
|
449
469
|
|
|
450
470
|
elif self._state == ACTIVATED:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if time.monotonic() - self._activated_at > ACTIVATED_TIMEOUT:
|
|
454
|
-
log("activated_timeout", "No command received")
|
|
455
|
-
if DEBUG:
|
|
456
|
-
print("\n Activation timed out", file=sys.stderr)
|
|
457
|
-
self._reset_listening()
|
|
458
|
-
continue
|
|
471
|
+
if self._check_activated_timeout():
|
|
472
|
+
continue
|
|
459
473
|
|
|
460
474
|
if prob > self._vad.threshold and not self._audio_buffer:
|
|
461
475
|
log("speech_start", "command after activation")
|
package/dist/index.js
CHANGED