voice-mode 2.27.0__py3-none-any.whl → 2.28.1__py3-none-any.whl
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.
- voice_mode/__version__.py +1 -1
- voice_mode/cli.py +152 -37
- voice_mode/cli_commands/exchanges.py +6 -0
- voice_mode/frontend/.next/BUILD_ID +1 -1
- voice_mode/frontend/.next/app-build-manifest.json +5 -5
- voice_mode/frontend/.next/build-manifest.json +3 -3
- voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -1
- voice_mode/frontend/.next/next-server.js.nft.json +1 -1
- voice_mode/frontend/.next/prerender-manifest.json +1 -1
- voice_mode/frontend/.next/required-server-files.json +1 -1
- voice_mode/frontend/.next/server/app/_not-found/page.js +1 -1
- voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/server/app/_not-found.html +1 -1
- voice_mode/frontend/.next/server/app/_not-found.rsc +1 -1
- voice_mode/frontend/.next/server/app/api/connection-details/route.js +2 -2
- voice_mode/frontend/.next/server/app/favicon.ico/route.js +2 -2
- voice_mode/frontend/.next/server/app/index.html +1 -1
- voice_mode/frontend/.next/server/app/index.rsc +2 -2
- voice_mode/frontend/.next/server/app/page.js +2 -2
- voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/server/chunks/994.js +1 -1
- voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -1
- voice_mode/frontend/.next/server/next-font-manifest.js +1 -1
- voice_mode/frontend/.next/server/next-font-manifest.json +1 -1
- voice_mode/frontend/.next/server/pages/404.html +1 -1
- voice_mode/frontend/.next/server/pages/500.html +1 -1
- voice_mode/frontend/.next/server/server-reference-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -1
- voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +5 -5
- voice_mode/frontend/.next/standalone/.next/build-manifest.json +3 -3
- voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/page.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/server.js +1 -1
- voice_mode/frontend/.next/static/chunks/app/{layout-08be62ed6e344292.js → layout-2a1721553cbe58e4.js} +1 -1
- voice_mode/frontend/.next/static/chunks/app/page-fe35d9da20297c85.js +1 -0
- voice_mode/frontend/.next/static/chunks/{main-app-413f77c1f2c53e3f.js → main-app-c17195caa4e269d6.js} +1 -1
- voice_mode/frontend/.next/trace +43 -43
- voice_mode/frontend/.next/types/app/api/connection-details/route.ts +1 -1
- voice_mode/frontend/.next/types/app/layout.ts +1 -1
- voice_mode/frontend/.next/types/app/page.ts +1 -1
- voice_mode/frontend/package-lock.json +6 -6
- voice_mode/tools/converse.py +44 -24
- voice_mode/tools/service.py +30 -3
- voice_mode/tools/services/kokoro/install.py +1 -1
- voice_mode/tools/services/whisper/__init__.py +15 -5
- voice_mode/tools/services/whisper/install.py +41 -9
- voice_mode/tools/services/whisper/list_models.py +14 -14
- voice_mode/tools/services/whisper/model_active.py +54 -0
- voice_mode/tools/services/whisper/model_benchmark.py +159 -0
- voice_mode/tools/services/whisper/{download_model.py → model_install.py} +72 -11
- voice_mode/tools/services/whisper/model_remove.py +36 -0
- voice_mode/tools/services/whisper/models.py +225 -26
- voice_mode/utils/services/whisper_helpers.py +206 -19
- voice_mode/utils/services/whisper_version.py +138 -0
- {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/METADATA +5 -1
- {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/RECORD +77 -74
- voice_mode/frontend/.next/static/chunks/app/page-80fc72669f25298f.js +0 -1
- voice_mode/tools/services/whisper/list_models_tool.py +0 -65
- /voice_mode/frontend/.next/static/{wQ5pxzPmwjlzdUfJwSjMg → LhJalgfazyY_l3L_v0_Kw}/_buildManifest.js +0 -0
- /voice_mode/frontend/.next/static/{wQ5pxzPmwjlzdUfJwSjMg → LhJalgfazyY_l3L_v0_Kw}/_ssgManifest.js +0 -0
- {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/WHEEL +0 -0
- {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/entry_points.txt +0 -0
voice_mode/__version__.py
CHANGED
voice_mode/cli.py
CHANGED
@@ -58,18 +58,21 @@ def voice_mode() -> None:
|
|
58
58
|
|
59
59
|
# Service group commands
|
60
60
|
@voice_mode_main_cli.group()
|
61
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
61
62
|
def kokoro():
|
62
63
|
"""Manage Kokoro TTS service."""
|
63
64
|
pass
|
64
65
|
|
65
66
|
|
66
67
|
@voice_mode_main_cli.group()
|
68
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
67
69
|
def whisper():
|
68
70
|
"""Manage Whisper STT service."""
|
69
71
|
pass
|
70
72
|
|
71
73
|
|
72
74
|
@voice_mode_main_cli.group()
|
75
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
73
76
|
def livekit():
|
74
77
|
"""Manage LiveKit RTC service."""
|
75
78
|
pass
|
@@ -128,6 +131,7 @@ def disable():
|
|
128
131
|
|
129
132
|
|
130
133
|
@kokoro.command()
|
134
|
+
@click.help_option('-h', '--help')
|
131
135
|
@click.option('--lines', '-n', default=50, help='Number of log lines to show')
|
132
136
|
def logs(lines):
|
133
137
|
"""View Kokoro service logs."""
|
@@ -172,6 +176,7 @@ def health():
|
|
172
176
|
|
173
177
|
|
174
178
|
@kokoro.command()
|
179
|
+
@click.help_option('-h', '--help')
|
175
180
|
@click.option('--install-dir', help='Directory to install kokoro-fastapi')
|
176
181
|
@click.option('--port', default=8880, help='Port to configure for the service')
|
177
182
|
@click.option('--force', '-f', is_flag=True, help='Force reinstall even if already installed')
|
@@ -209,6 +214,7 @@ def install(install_dir, port, force, version, auto_enable):
|
|
209
214
|
|
210
215
|
|
211
216
|
@kokoro.command()
|
217
|
+
@click.help_option('-h', '--help')
|
212
218
|
@click.option('--remove-models', is_flag=True, help='Also remove downloaded Kokoro models')
|
213
219
|
@click.option('--remove-all-data', is_flag=True, help='Remove all Kokoro data including logs and cache')
|
214
220
|
@click.confirmation_option(prompt='Are you sure you want to uninstall Kokoro?')
|
@@ -294,6 +300,7 @@ def disable():
|
|
294
300
|
|
295
301
|
|
296
302
|
@whisper.command()
|
303
|
+
@click.help_option('-h', '--help')
|
297
304
|
@click.option('--lines', '-n', default=50, help='Number of log lines to show')
|
298
305
|
def logs(lines):
|
299
306
|
"""View Whisper service logs."""
|
@@ -316,7 +323,7 @@ def health():
|
|
316
323
|
import subprocess
|
317
324
|
try:
|
318
325
|
result = subprocess.run(
|
319
|
-
["curl", "-s", "http://127.0.0.1:
|
326
|
+
["curl", "-s", "http://127.0.0.1:2022/health"],
|
320
327
|
capture_output=True, text=True, timeout=5
|
321
328
|
)
|
322
329
|
if result.returncode == 0:
|
@@ -330,7 +337,7 @@ def health():
|
|
330
337
|
except json.JSONDecodeError:
|
331
338
|
click.echo("✅ Whisper is responding (non-JSON response)")
|
332
339
|
else:
|
333
|
-
click.echo("❌ Whisper not responding on port
|
340
|
+
click.echo("❌ Whisper not responding on port 2022")
|
334
341
|
except subprocess.TimeoutExpired:
|
335
342
|
click.echo("❌ Whisper health check timed out")
|
336
343
|
except Exception as e:
|
@@ -338,6 +345,7 @@ def health():
|
|
338
345
|
|
339
346
|
|
340
347
|
@whisper.command()
|
348
|
+
@click.help_option('-h', '--help')
|
341
349
|
@click.option('--install-dir', help='Directory to install whisper.cpp')
|
342
350
|
@click.option('--model', default='large-v2', help='Whisper model to download (default: large-v2)')
|
343
351
|
@click.option('--use-gpu/--no-gpu', default=None, help='Enable GPU support if available')
|
@@ -386,6 +394,7 @@ def install(install_dir, model, use_gpu, force, version, auto_enable):
|
|
386
394
|
|
387
395
|
|
388
396
|
@whisper.command()
|
397
|
+
@click.help_option('-h', '--help')
|
389
398
|
@click.option('--remove-models', is_flag=True, help='Also remove downloaded Whisper models')
|
390
399
|
@click.option('--remove-all-data', is_flag=True, help='Remove all Whisper data including logs and transcriptions')
|
391
400
|
@click.confirmation_option(prompt='Are you sure you want to uninstall Whisper?')
|
@@ -422,6 +431,7 @@ def uninstall(remove_models, remove_all_data):
|
|
422
431
|
|
423
432
|
|
424
433
|
@whisper.group("model")
|
434
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
425
435
|
def whisper_model():
|
426
436
|
"""Manage Whisper models.
|
427
437
|
|
@@ -434,6 +444,7 @@ def whisper_model():
|
|
434
444
|
|
435
445
|
|
436
446
|
@whisper_model.command("active")
|
447
|
+
@click.help_option('-h', '--help')
|
437
448
|
@click.argument('model_name', required=False)
|
438
449
|
def whisper_model_active(model_name):
|
439
450
|
"""Show or set the active Whisper model.
|
@@ -442,34 +453,34 @@ def whisper_model_active(model_name):
|
|
442
453
|
With MODEL_NAME: Sets the active model (updates VOICEMODE_WHISPER_MODEL)
|
443
454
|
"""
|
444
455
|
from voice_mode.tools.services.whisper.models import (
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
456
|
+
get_active_model,
|
457
|
+
WHISPER_MODEL_REGISTRY,
|
458
|
+
is_whisper_model_installed,
|
459
|
+
set_active_model
|
449
460
|
)
|
450
461
|
import os
|
451
462
|
import subprocess
|
452
463
|
|
453
464
|
if model_name:
|
454
465
|
# Set model mode
|
455
|
-
if model_name not in
|
466
|
+
if model_name not in WHISPER_MODEL_REGISTRY:
|
456
467
|
click.echo(f"Error: '{model_name}' is not a valid model.", err=True)
|
457
468
|
click.echo("\nAvailable models:", err=True)
|
458
|
-
for name in
|
469
|
+
for name in WHISPER_MODEL_REGISTRY.keys():
|
459
470
|
click.echo(f" - {name}", err=True)
|
460
471
|
return
|
461
472
|
|
462
473
|
# Check if model is installed
|
463
|
-
if not
|
474
|
+
if not is_whisper_model_installed(model_name):
|
464
475
|
click.echo(f"Error: Model '{model_name}' is not installed.", err=True)
|
465
476
|
click.echo(f"Install it with: voice-mode whisper model install {model_name}", err=True)
|
466
477
|
raise click.Abort()
|
467
478
|
|
468
479
|
# Get previous model
|
469
|
-
previous_model =
|
480
|
+
previous_model = get_active_model()
|
470
481
|
|
471
482
|
# Update the configuration file
|
472
|
-
|
483
|
+
set_active_model(model_name)
|
473
484
|
|
474
485
|
click.echo(f"✓ Active model set to: {model_name}")
|
475
486
|
if previous_model != model_name:
|
@@ -492,14 +503,14 @@ def whisper_model_active(model_name):
|
|
492
503
|
|
493
504
|
else:
|
494
505
|
# Show current model
|
495
|
-
current =
|
506
|
+
current = get_active_model()
|
496
507
|
|
497
508
|
# Check if current model is installed
|
498
|
-
installed =
|
509
|
+
installed = is_whisper_model_installed(current)
|
499
510
|
status = click.style("[✓ Installed]", fg="green") if installed else click.style("[Not installed]", fg="red")
|
500
511
|
|
501
512
|
# Get model info
|
502
|
-
model_info =
|
513
|
+
model_info = WHISPER_MODEL_REGISTRY.get(current, {})
|
503
514
|
|
504
515
|
click.echo(f"\nActive Whisper model: {click.style(current, fg='yellow', bold=True)} {status}")
|
505
516
|
if model_info:
|
@@ -524,24 +535,25 @@ def whisper_model_active(model_name):
|
|
524
535
|
def whisper_models():
|
525
536
|
"""List available Whisper models and their installation status."""
|
526
537
|
from voice_mode.tools.services.whisper.models import (
|
527
|
-
|
538
|
+
WHISPER_MODEL_REGISTRY,
|
528
539
|
get_model_directory,
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
format_size
|
540
|
+
get_active_model,
|
541
|
+
is_whisper_model_installed,
|
542
|
+
get_installed_whisper_models,
|
543
|
+
format_size,
|
544
|
+
has_whisper_coreml_model
|
533
545
|
)
|
534
546
|
|
535
547
|
model_dir = get_model_directory()
|
536
|
-
current_model =
|
537
|
-
installed_models =
|
548
|
+
current_model = get_active_model()
|
549
|
+
installed_models = get_installed_whisper_models()
|
538
550
|
|
539
551
|
# Calculate totals
|
540
552
|
total_installed_size = sum(
|
541
|
-
|
553
|
+
WHISPER_MODEL_REGISTRY[m]["size_mb"] for m in installed_models
|
542
554
|
)
|
543
555
|
total_available_size = sum(
|
544
|
-
m["size_mb"] for m in
|
556
|
+
m["size_mb"] for m in WHISPER_MODEL_REGISTRY.values()
|
545
557
|
)
|
546
558
|
|
547
559
|
# Print header
|
@@ -549,9 +561,9 @@ def whisper_models():
|
|
549
561
|
click.echo("")
|
550
562
|
|
551
563
|
# Print models table
|
552
|
-
for model_name, info in
|
564
|
+
for model_name, info in WHISPER_MODEL_REGISTRY.items():
|
553
565
|
# Check status
|
554
|
-
is_installed =
|
566
|
+
is_installed = is_whisper_model_installed(model_name)
|
555
567
|
is_current = model_name == current_model
|
556
568
|
|
557
569
|
# Format status
|
@@ -564,7 +576,11 @@ def whisper_models():
|
|
564
576
|
|
565
577
|
# Format installation status
|
566
578
|
if is_installed:
|
567
|
-
|
579
|
+
# Check for Core ML model
|
580
|
+
if has_whisper_coreml_model(model_name):
|
581
|
+
install_status = click.style("[✓ Installed+ML]", fg="green")
|
582
|
+
else:
|
583
|
+
install_status = click.style("[✓ Installed]", fg="green")
|
568
584
|
else:
|
569
585
|
install_status = click.style("[ Download ]", fg="bright_black")
|
570
586
|
|
@@ -581,7 +597,7 @@ def whisper_models():
|
|
581
597
|
desc = click.style(desc, fg="yellow")
|
582
598
|
|
583
599
|
# Print row
|
584
|
-
click.echo(f"{status} {model_display} {install_status:
|
600
|
+
click.echo(f"{status} {model_display} {install_status:18} {size_str} {lang_str} {desc}")
|
585
601
|
|
586
602
|
# Print footer
|
587
603
|
click.echo("")
|
@@ -593,6 +609,7 @@ def whisper_models():
|
|
593
609
|
|
594
610
|
|
595
611
|
@whisper_model.command("install")
|
612
|
+
@click.help_option('-h', '--help')
|
596
613
|
@click.argument('model', default='large-v2')
|
597
614
|
@click.option('--force', '-f', is_flag=True, help='Re-download even if model exists')
|
598
615
|
@click.option('--skip-core-ml', is_flag=True, help='Skip Core ML conversion on Apple Silicon')
|
@@ -606,8 +623,11 @@ def whisper_model_install(model, force, skip_core_ml):
|
|
606
623
|
medium, medium.en, large-v1, large-v2, large-v3, large-v3-turbo
|
607
624
|
"""
|
608
625
|
import json
|
609
|
-
|
610
|
-
|
626
|
+
import voice_mode.tools.services.whisper.model_install as install_module
|
627
|
+
# Get the actual function from the MCP tool wrapper
|
628
|
+
tool = install_module.whisper_model_install
|
629
|
+
install_func = tool.fn if hasattr(tool, 'fn') else tool
|
630
|
+
result = asyncio.run(install_func(
|
611
631
|
model=model,
|
612
632
|
force_download=force,
|
613
633
|
skip_core_ml=skip_core_ml
|
@@ -645,6 +665,7 @@ def whisper_model_install(model, force, skip_core_ml):
|
|
645
665
|
|
646
666
|
|
647
667
|
@whisper_model.command("remove")
|
668
|
+
@click.help_option('-h', '--help')
|
648
669
|
@click.argument('model')
|
649
670
|
@click.option('--force', '-f', is_flag=True, help='Remove without confirmation')
|
650
671
|
def whisper_model_remove(model, force):
|
@@ -653,28 +674,28 @@ def whisper_model_remove(model, force):
|
|
653
674
|
MODEL is the name of the model to remove (e.g., 'large-v2').
|
654
675
|
"""
|
655
676
|
from voice_mode.tools.services.whisper.models import (
|
656
|
-
|
657
|
-
|
677
|
+
WHISPER_MODEL_REGISTRY,
|
678
|
+
is_whisper_model_installed,
|
658
679
|
get_model_directory,
|
659
|
-
|
680
|
+
get_active_model
|
660
681
|
)
|
661
682
|
import os
|
662
683
|
|
663
684
|
# Validate model name
|
664
|
-
if model not in
|
685
|
+
if model not in WHISPER_MODEL_REGISTRY:
|
665
686
|
click.echo(f"Error: '{model}' is not a valid model.", err=True)
|
666
687
|
click.echo("\nAvailable models:", err=True)
|
667
|
-
for name in
|
688
|
+
for name in WHISPER_MODEL_REGISTRY.keys():
|
668
689
|
click.echo(f" - {name}", err=True)
|
669
690
|
ctx.exit(1)
|
670
691
|
|
671
692
|
# Check if model is installed
|
672
|
-
if not
|
693
|
+
if not is_whisper_model_installed(model):
|
673
694
|
click.echo(f"Model '{model}' is not installed.")
|
674
695
|
return
|
675
696
|
|
676
697
|
# Check if it's the current model
|
677
|
-
current =
|
698
|
+
current = get_active_model()
|
678
699
|
if model == current:
|
679
700
|
click.echo(f"Warning: '{model}' is the currently selected model.", err=True)
|
680
701
|
if not force:
|
@@ -683,7 +704,7 @@ def whisper_model_remove(model, force):
|
|
683
704
|
|
684
705
|
# Get model path
|
685
706
|
model_dir = get_model_directory()
|
686
|
-
model_info =
|
707
|
+
model_info = WHISPER_MODEL_REGISTRY[model]
|
687
708
|
model_path = model_dir / model_info["filename"]
|
688
709
|
|
689
710
|
# Also check for Core ML models
|
@@ -712,6 +733,83 @@ def whisper_model_remove(model, force):
|
|
712
733
|
click.echo(f"Error removing model: {e}", err=True)
|
713
734
|
|
714
735
|
|
736
|
+
@whisper_model.command("benchmark")
|
737
|
+
@click.help_option('-h', '--help')
|
738
|
+
@click.option('--models', default='installed', help='Models to benchmark: installed, all, or comma-separated list')
|
739
|
+
@click.option('--sample', help='Audio file to use for benchmarking')
|
740
|
+
@click.option('--runs', default=1, help='Number of benchmark runs per model')
|
741
|
+
def whisper_model_benchmark_cmd(models, sample, runs):
|
742
|
+
"""Benchmark Whisper model performance.
|
743
|
+
|
744
|
+
Runs performance tests on specified models to help choose the optimal model
|
745
|
+
for your use case based on speed vs accuracy trade-offs.
|
746
|
+
"""
|
747
|
+
from voice_mode.tools.services.whisper.model_benchmark import whisper_model_benchmark
|
748
|
+
|
749
|
+
# Parse models parameter
|
750
|
+
if ',' in models:
|
751
|
+
model_list = [m.strip() for m in models.split(',')]
|
752
|
+
else:
|
753
|
+
model_list = models
|
754
|
+
|
755
|
+
# Run benchmark
|
756
|
+
result = asyncio.run(whisper_model_benchmark(
|
757
|
+
models=model_list,
|
758
|
+
sample_file=sample,
|
759
|
+
runs=runs
|
760
|
+
))
|
761
|
+
|
762
|
+
if not result.get('success'):
|
763
|
+
click.echo(f"❌ Benchmark failed: {result.get('error', 'Unknown error')}", err=True)
|
764
|
+
return
|
765
|
+
|
766
|
+
# Display results
|
767
|
+
click.echo("\n" + "="*60)
|
768
|
+
click.echo("Whisper Model Benchmark Results")
|
769
|
+
click.echo("="*60)
|
770
|
+
|
771
|
+
if result.get('sample_file'):
|
772
|
+
click.echo(f"Sample: {result['sample_file']}")
|
773
|
+
if result.get('runs_per_model') > 1:
|
774
|
+
click.echo(f"Runs per model: {result['runs_per_model']} (showing best)")
|
775
|
+
click.echo("")
|
776
|
+
|
777
|
+
# Display benchmark table
|
778
|
+
click.echo(f"{'Model':<20} {'Load (ms)':<12} {'Encode (ms)':<12} {'Total (ms)':<12} {'Speed':<10}")
|
779
|
+
click.echo("-"*70)
|
780
|
+
|
781
|
+
for bench in result.get('benchmarks', []):
|
782
|
+
if bench.get('success'):
|
783
|
+
model = bench['model']
|
784
|
+
load_time = f"{bench.get('load_time_ms', 0):.1f}"
|
785
|
+
encode_time = f"{bench.get('encode_time_ms', 0):.1f}"
|
786
|
+
total_time = f"{bench.get('total_time_ms', 0):.1f}"
|
787
|
+
rtf = f"{bench.get('real_time_factor', 0):.1f}x"
|
788
|
+
|
789
|
+
# Highlight fastest model
|
790
|
+
if bench['model'] == result.get('fastest_model'):
|
791
|
+
model = click.style(model, fg='green', bold=True)
|
792
|
+
rtf = click.style(rtf, fg='green', bold=True)
|
793
|
+
|
794
|
+
click.echo(f"{model:<20} {load_time:<12} {encode_time:<12} {total_time:<12} {rtf:<10}")
|
795
|
+
else:
|
796
|
+
click.echo(f"{bench['model']:<20} {'Failed':<12} {bench.get('error', 'Unknown error')}")
|
797
|
+
|
798
|
+
# Display recommendations
|
799
|
+
if result.get('recommendations'):
|
800
|
+
click.echo("\nRecommendations:")
|
801
|
+
for rec in result['recommendations']:
|
802
|
+
click.echo(f" • {rec}")
|
803
|
+
|
804
|
+
# Summary
|
805
|
+
if result.get('fastest_model'):
|
806
|
+
click.echo(f"\nFastest model: {click.style(result['fastest_model'], fg='yellow', bold=True)}")
|
807
|
+
click.echo(f"Processing time: {result.get('fastest_time_ms', 'N/A')} ms")
|
808
|
+
|
809
|
+
click.echo("\nNote: Speed values show real-time factor (higher is better)")
|
810
|
+
click.echo(" 1.0x = real-time, 10x = 10 times faster than real-time")
|
811
|
+
|
812
|
+
|
715
813
|
# LiveKit service commands
|
716
814
|
@livekit.command()
|
717
815
|
def status():
|
@@ -762,6 +860,7 @@ def disable():
|
|
762
860
|
|
763
861
|
|
764
862
|
@livekit.command()
|
863
|
+
@click.help_option('-h', '--help')
|
765
864
|
@click.option('--lines', '-n', default=50, help='Number of log lines to show')
|
766
865
|
def logs(lines):
|
767
866
|
"""View LiveKit service logs."""
|
@@ -785,6 +884,7 @@ def update():
|
|
785
884
|
|
786
885
|
|
787
886
|
@livekit.command()
|
887
|
+
@click.help_option('-h', '--help')
|
788
888
|
@click.option('--install-dir', help='Directory to install LiveKit')
|
789
889
|
@click.option('--port', default=7880, help='Port for LiveKit server (default: 7880)')
|
790
890
|
@click.option('--force', '-f', is_flag=True, help='Force reinstall even if already installed')
|
@@ -825,6 +925,7 @@ def install(install_dir, port, force, version, auto_enable):
|
|
825
925
|
|
826
926
|
|
827
927
|
@livekit.command()
|
928
|
+
@click.help_option('-h', '--help')
|
828
929
|
@click.option('--remove-config', is_flag=True, help='Also remove LiveKit configuration files')
|
829
930
|
@click.option('--remove-all-data', is_flag=True, help='Remove all LiveKit data including logs')
|
830
931
|
@click.confirmation_option(prompt='Are you sure you want to uninstall LiveKit?')
|
@@ -854,12 +955,14 @@ def uninstall(remove_config, remove_all_data):
|
|
854
955
|
|
855
956
|
# LiveKit frontend subcommands
|
856
957
|
@livekit.group()
|
958
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
857
959
|
def frontend():
|
858
960
|
"""Manage LiveKit Voice Assistant Frontend."""
|
859
961
|
pass
|
860
962
|
|
861
963
|
|
862
964
|
@frontend.command("install")
|
965
|
+
@click.help_option('-h', '--help')
|
863
966
|
@click.option('--auto-enable/--no-auto-enable', default=None, help='Enable service after installation (default: from config)')
|
864
967
|
def frontend_install(auto_enable):
|
865
968
|
"""Install and setup LiveKit Voice Assistant Frontend."""
|
@@ -887,6 +990,7 @@ def frontend_install(auto_enable):
|
|
887
990
|
|
888
991
|
|
889
992
|
@frontend.command("start")
|
993
|
+
@click.help_option('-h', '--help')
|
890
994
|
@click.option('--port', default=3000, help='Port to run frontend on (default: 3000)')
|
891
995
|
@click.option('--host', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)')
|
892
996
|
def frontend_start(port, host):
|
@@ -967,6 +1071,7 @@ def frontend_open():
|
|
967
1071
|
|
968
1072
|
|
969
1073
|
@frontend.command("logs")
|
1074
|
+
@click.help_option('-h', '--help')
|
970
1075
|
@click.option("--lines", "-n", default=50, help="Number of lines to show (default: 50)")
|
971
1076
|
@click.option("--follow", "-f", is_flag=True, help="Follow log output (tail -f)")
|
972
1077
|
def frontend_logs(lines, follow):
|
@@ -1020,6 +1125,7 @@ def frontend_disable():
|
|
1020
1125
|
|
1021
1126
|
|
1022
1127
|
@frontend.command("build")
|
1128
|
+
@click.help_option('-h', '--help')
|
1023
1129
|
@click.option('--force', '-f', is_flag=True, help='Force rebuild even if build exists')
|
1024
1130
|
def frontend_build(force):
|
1025
1131
|
"""Build frontend for production (requires Node.js)."""
|
@@ -1077,6 +1183,7 @@ def frontend_build(force):
|
|
1077
1183
|
|
1078
1184
|
# Configuration management group
|
1079
1185
|
@voice_mode_main_cli.group()
|
1186
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
1080
1187
|
def config():
|
1081
1188
|
"""Manage voice-mode configuration."""
|
1082
1189
|
pass
|
@@ -1091,6 +1198,7 @@ def config_list():
|
|
1091
1198
|
|
1092
1199
|
|
1093
1200
|
@config.command("get")
|
1201
|
+
@click.help_option('-h', '--help')
|
1094
1202
|
@click.argument('key')
|
1095
1203
|
def config_get(key):
|
1096
1204
|
"""Get a configuration value."""
|
@@ -1128,6 +1236,7 @@ def config_get(key):
|
|
1128
1236
|
|
1129
1237
|
|
1130
1238
|
@config.command("set")
|
1239
|
+
@click.help_option('-h', '--help')
|
1131
1240
|
@click.argument('key')
|
1132
1241
|
@click.argument('value')
|
1133
1242
|
def config_set(key, value):
|
@@ -1139,6 +1248,7 @@ def config_set(key, value):
|
|
1139
1248
|
|
1140
1249
|
# Shell completion group
|
1141
1250
|
@voice_mode_main_cli.group()
|
1251
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
1142
1252
|
def completion():
|
1143
1253
|
"""Generate shell completion scripts for voice-mode."""
|
1144
1254
|
pass
|
@@ -1218,6 +1328,7 @@ def completion_fish():
|
|
1218
1328
|
|
1219
1329
|
|
1220
1330
|
@completion.command("install")
|
1331
|
+
@click.help_option('-h', '--help')
|
1221
1332
|
@click.option('--shell', type=click.Choice(['bash', 'zsh', 'fish', 'auto']), default='auto', help='Shell type to install for')
|
1222
1333
|
def completion_install(shell):
|
1223
1334
|
"""Show installation instructions for shell completion.
|
@@ -1283,6 +1394,7 @@ def completion_install(shell):
|
|
1283
1394
|
|
1284
1395
|
# Diagnostics group
|
1285
1396
|
@voice_mode_main_cli.group()
|
1397
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
1286
1398
|
def diag():
|
1287
1399
|
"""Diagnostic tools for voice-mode."""
|
1288
1400
|
pass
|
@@ -1380,6 +1492,7 @@ voice_mode_main_cli.add_command(exchanges_cmd.exchanges)
|
|
1380
1492
|
|
1381
1493
|
# Converse command - direct voice conversation from CLI
|
1382
1494
|
@voice_mode_main_cli.command()
|
1495
|
+
@click.help_option('-h', '--help')
|
1383
1496
|
@click.option('--message', '-m', default="Hello! How can I help you today?", help='Initial message to speak')
|
1384
1497
|
@click.option('--wait/--no-wait', default=True, help='Wait for response after speaking')
|
1385
1498
|
@click.option('--duration', '-d', type=float, default=30.0, help='Listen duration in seconds')
|
@@ -1578,6 +1691,7 @@ def version():
|
|
1578
1691
|
|
1579
1692
|
# Update command
|
1580
1693
|
@voice_mode_main_cli.command()
|
1694
|
+
@click.help_option('-h', '--help')
|
1581
1695
|
@click.option('--force', is_flag=True, help='Force reinstall even if already up to date')
|
1582
1696
|
def update(force):
|
1583
1697
|
"""Update Voice Mode to the latest version."""
|
@@ -1659,6 +1773,7 @@ def update(force):
|
|
1659
1773
|
|
1660
1774
|
# Completions command
|
1661
1775
|
@voice_mode_main_cli.command()
|
1776
|
+
@click.help_option('-h', '--help')
|
1662
1777
|
@click.argument('shell', type=click.Choice(['bash', 'zsh', 'fish']))
|
1663
1778
|
@click.option('--install', is_flag=True, help='Install completion script to the appropriate location')
|
1664
1779
|
def completions(shell, install):
|
@@ -20,12 +20,14 @@ from voice_mode.exchanges import (
|
|
20
20
|
|
21
21
|
|
22
22
|
@click.group()
|
23
|
+
@click.help_option('-h', '--help', help='Show this message and exit')
|
23
24
|
def exchanges():
|
24
25
|
"""Manage and view conversation exchange logs."""
|
25
26
|
pass
|
26
27
|
|
27
28
|
|
28
29
|
@exchanges.command()
|
30
|
+
@click.help_option('-h', '--help')
|
29
31
|
@click.option('-f', '--format',
|
30
32
|
type=click.Choice(['simple', 'pretty', 'json', 'raw']),
|
31
33
|
default='simple',
|
@@ -86,6 +88,7 @@ def tail(format, stt, tts, full, no_color, date, transport, provider):
|
|
86
88
|
|
87
89
|
|
88
90
|
@exchanges.command()
|
91
|
+
@click.help_option('-h', '--help')
|
89
92
|
@click.option('-n', '--lines', type=int, default=20,
|
90
93
|
help='Number of exchanges to show')
|
91
94
|
@click.option('-c', '--conversation', help='Show specific conversation')
|
@@ -140,6 +143,7 @@ def view(lines, conversation, today, yesterday, date, format, reverse, no_color)
|
|
140
143
|
|
141
144
|
|
142
145
|
@exchanges.command()
|
146
|
+
@click.help_option('-h', '--help')
|
143
147
|
@click.argument('query')
|
144
148
|
@click.option('-n', '--max-results', type=int, default=50,
|
145
149
|
help='Maximum results to show')
|
@@ -221,6 +225,7 @@ def search(query, max_results, days, exchange_type, regex, ignore_case,
|
|
221
225
|
|
222
226
|
|
223
227
|
@exchanges.command()
|
228
|
+
@click.help_option('-h', '--help')
|
224
229
|
@click.option('-d', '--days', type=int, help='Stats for last N days')
|
225
230
|
@click.option('--by-hour', is_flag=True, help='Group by hour')
|
226
231
|
@click.option('--by-provider', is_flag=True, help='Group by provider')
|
@@ -333,6 +338,7 @@ def stats(days, by_hour, by_provider, by_transport, timing, conversations,
|
|
333
338
|
|
334
339
|
|
335
340
|
@exchanges.command()
|
341
|
+
@click.help_option('-h', '--help')
|
336
342
|
@click.option('-c', '--conversation', help='Export specific conversation')
|
337
343
|
@click.option('-d', '--date', type=click.DateTime(formats=['%Y-%m-%d']),
|
338
344
|
help='Export date range')
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
LhJalgfazyY_l3L_v0_Kw
|
@@ -4,25 +4,25 @@
|
|
4
4
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
5
5
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
6
6
|
"static/chunks/117-40bc79a2b97edb21.js",
|
7
|
-
"static/chunks/main-app-
|
7
|
+
"static/chunks/main-app-c17195caa4e269d6.js",
|
8
8
|
"static/chunks/app/_not-found/page-5011050e402ab9c8.js"
|
9
9
|
],
|
10
10
|
"/layout": [
|
11
11
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
12
12
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
13
13
|
"static/chunks/117-40bc79a2b97edb21.js",
|
14
|
-
"static/chunks/main-app-
|
14
|
+
"static/chunks/main-app-c17195caa4e269d6.js",
|
15
15
|
"static/css/a2f49a47752b5010.css",
|
16
|
-
"static/chunks/app/layout-
|
16
|
+
"static/chunks/app/layout-2a1721553cbe58e4.js"
|
17
17
|
],
|
18
18
|
"/page": [
|
19
19
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
20
20
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
21
21
|
"static/chunks/117-40bc79a2b97edb21.js",
|
22
|
-
"static/chunks/main-app-
|
22
|
+
"static/chunks/main-app-c17195caa4e269d6.js",
|
23
23
|
"static/chunks/144d3bae-2d5f122b82426d88.js",
|
24
24
|
"static/chunks/471-bd4b96a33883dfa2.js",
|
25
|
-
"static/chunks/app/page-
|
25
|
+
"static/chunks/app/page-fe35d9da20297c85.js"
|
26
26
|
]
|
27
27
|
}
|
28
28
|
}
|
@@ -5,14 +5,14 @@
|
|
5
5
|
"devFiles": [],
|
6
6
|
"ampDevFiles": [],
|
7
7
|
"lowPriorityFiles": [
|
8
|
-
"static/
|
9
|
-
"static/
|
8
|
+
"static/LhJalgfazyY_l3L_v0_Kw/_buildManifest.js",
|
9
|
+
"static/LhJalgfazyY_l3L_v0_Kw/_ssgManifest.js"
|
10
10
|
],
|
11
11
|
"rootMainFiles": [
|
12
12
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
13
13
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
14
14
|
"static/chunks/117-40bc79a2b97edb21.js",
|
15
|
-
"static/chunks/main-app-
|
15
|
+
"static/chunks/main-app-c17195caa4e269d6.js"
|
16
16
|
],
|
17
17
|
"pages": {
|
18
18
|
"/_app": [
|