voice-mode 2.22.2__tar.gz → 2.23.0__tar.gz
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-2.22.2 → voice_mode-2.23.0}/CHANGELOG.md +36 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/PKG-INFO +1 -1
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/__version__.py +1 -1
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/config.py +3 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/converse.py +64 -19
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/service.py +11 -5
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/kokoro/install.py +4 -4
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/whisper/install.py +27 -18
- voice_mode-2.23.0/voice_mode/utils/services/common.py +80 -0
- voice_mode-2.22.2/voice_mode/utils/services/common.py +0 -22
- {voice_mode-2.22.2 → voice_mode-2.23.0}/.gitignore +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/README.md +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/build_hooks.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/pyproject.toml +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/__main__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/cli.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/cli_commands/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/cli_commands/exchanges.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/conversation_logger.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/core.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/data/versions.json +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/conversations.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/filters.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/formatters.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/models.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/reader.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/exchanges/stats.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/README.md +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/favicon.ico +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/globals.css +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/layout.tsx +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/page.tsx +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/next-env.d.ts +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/next.config.mjs +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/package-lock.json +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/package.json +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/pnpm-lock.yaml +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/postcss.config.mjs +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/tailwind.config.ts +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/tsconfig.json +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/prompts/README.md +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/prompts/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/prompts/converse.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/prompts/release_notes.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/prompts/services.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/provider_discovery.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/providers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/audio_files.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/changelog.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/configuration.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/statistics.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/version.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/resources/whisper_models.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/server.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/shared.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/simple_failover.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/statistics.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/streaming.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/configuration_management.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/dependencies.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/devices.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/diagnostics.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/providers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/list_versions.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/frontend.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/install.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/production_server.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/uninstall.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/version_info.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/whisper/download_model.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/whisper/uninstall.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/statistics.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/voice_registry.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/__init__.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/audio_diagnostics.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/event_logger.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/ffmpeg_check.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/format_migration.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/gpu_detection.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/migration_helpers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/services/kokoro_helpers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/services/livekit_helpers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/services/whisper_helpers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/utils/version_helpers.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/version.py +0 -0
- {voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/voice_preferences.py +0 -0
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [2.23.0] - 2025-08-16
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- **`skip_tts` parameter** - Dynamic control over text-to-speech in converse tool
|
14
|
+
- Add optional `skip_tts` parameter to override global `VOICEMODE_SKIP_TTS` setting
|
15
|
+
- When `True`: Skip TTS for faster text-only responses
|
16
|
+
- When `False`: Always use TTS regardless of environment setting
|
17
|
+
- When `None` (default): Follow `VOICEMODE_SKIP_TTS` environment variable
|
18
|
+
- Enables LLM to intelligently choose between voice and text-only responses
|
19
|
+
- **`VOICEMODE_SKIP_TTS` environment variable** - Global TTS skip configuration
|
20
|
+
- Set to `true` for permanent text-only mode (faster responses)
|
21
|
+
- Can be overridden per-call with `skip_tts` parameter
|
22
|
+
- Useful for rapid development iterations or when voice isn't needed
|
23
|
+
|
24
|
+
### Fixed
|
25
|
+
- **Service status detection** - Correctly identify SSH-forwarded vs locally running services
|
26
|
+
- SSH processes listening on service ports are now recognized as port forwards
|
27
|
+
- Status command now shows 🔄 for forwarded services vs ✅ for local services
|
28
|
+
- Prevents confusion about where services are actually running
|
29
|
+
|
30
|
+
## [2.22.3] - 2025-08-16
|
31
|
+
|
32
|
+
## [2.23.0] - 2025-08-16
|
33
|
+
|
34
|
+
### Fixed
|
35
|
+
- **Service auto-enable error** - Fix 'FunctionTool' object is not callable
|
36
|
+
- Changed whisper and kokoro installers to use `enable_service` function instead of MCP tool
|
37
|
+
- Services can now be properly auto-enabled after installation
|
38
|
+
- **Whisper build errors** - Remove obsolete make server command
|
39
|
+
- whisper-server is now built as part of the main build target
|
40
|
+
- Removed unnecessary build step that was causing errors
|
41
|
+
- **Build output verbosity** - Suppress cmake/make output unless debugging
|
42
|
+
- Build output is now captured and only shown on errors
|
43
|
+
- Use VOICEMODE_DEBUG=true to see full build output
|
44
|
+
- Significantly cleaner installation experience
|
45
|
+
|
10
46
|
## [2.22.2] - 2025-08-16
|
11
47
|
|
12
48
|
### Fixed
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: voice-mode
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.23.0
|
4
4
|
Summary: VoiceMode - Voice interaction capabilities for AI assistants (formerly voice-mcp)
|
5
5
|
Project-URL: Homepage, https://github.com/mbailey/voicemode
|
6
6
|
Project-URL: Repository, https://github.com/mbailey/voicemode
|
@@ -175,6 +175,9 @@ SAVE_TRANSCRIPTIONS = SAVE_ALL or DEBUG or os.getenv("VOICEMODE_SAVE_TRANSCRIPTI
|
|
175
175
|
# Audio feedback configuration
|
176
176
|
AUDIO_FEEDBACK_ENABLED = os.getenv("VOICEMODE_AUDIO_FEEDBACK", "true").lower() in ("true", "1", "yes", "on")
|
177
177
|
|
178
|
+
# Skip TTS configuration (skip text-to-speech for faster responses)
|
179
|
+
SKIP_TTS = os.getenv("VOICEMODE_SKIP_TTS", "false").lower() in ("true", "1", "yes", "on")
|
180
|
+
|
178
181
|
# Local provider preference configuration
|
179
182
|
PREFER_LOCAL = os.getenv("VOICEMODE_PREFER_LOCAL", "true").lower() in ("true", "1", "yes", "on")
|
180
183
|
|
@@ -48,6 +48,7 @@ from voice_mode.config import (
|
|
48
48
|
VAD_AGGRESSIVENESS,
|
49
49
|
SILENCE_THRESHOLD_MS,
|
50
50
|
MIN_RECORDING_DURATION,
|
51
|
+
SKIP_TTS,
|
51
52
|
VAD_CHUNK_DURATION_MS,
|
52
53
|
INITIAL_SILENCE_GRACE_PERIOD,
|
53
54
|
DEFAULT_LISTEN_DURATION,
|
@@ -1248,7 +1249,8 @@ async def converse(
|
|
1248
1249
|
audio_format: Optional[str] = None,
|
1249
1250
|
disable_silence_detection: Union[bool, str] = False,
|
1250
1251
|
speed: Optional[float] = None,
|
1251
|
-
vad_aggressiveness: Optional[int] = None
|
1252
|
+
vad_aggressiveness: Optional[int] = None,
|
1253
|
+
skip_tts: Optional[Union[bool, str]] = None
|
1252
1254
|
) -> str:
|
1253
1255
|
"""Have a voice conversation - speak a message and optionally listen for response.
|
1254
1256
|
|
@@ -1320,6 +1322,11 @@ async def converse(
|
|
1320
1322
|
|
1321
1323
|
Use lower values (0-1) in quiet environments to catch all speech
|
1322
1324
|
Use higher values (2-3) in noisy environments to reduce false triggers
|
1325
|
+
skip_tts: Skip text-to-speech and only show text (default: None uses VOICEMODE_SKIP_TTS env var)
|
1326
|
+
When True: Skip TTS for faster response, text-only output
|
1327
|
+
When False: Always use TTS regardless of environment setting
|
1328
|
+
When None: Follow VOICEMODE_SKIP_TTS environment variable
|
1329
|
+
Useful for rapid development iterations or when voice isn't needed
|
1323
1330
|
If wait_for_response is False: Confirmation that message was spoken
|
1324
1331
|
If wait_for_response is True: The voice response received (or error/timeout message)
|
1325
1332
|
|
@@ -1360,6 +1367,12 @@ async def converse(
|
|
1360
1367
|
|
1361
1368
|
Remember: Lower values (0-1) = more permissive, may detect non-speech as speech
|
1362
1369
|
Higher values (2-3) = more strict, may miss soft speech or whispers
|
1370
|
+
|
1371
|
+
Skip TTS Examples:
|
1372
|
+
- Fast iteration mode: converse("Processing your request", skip_tts=True) # Text only, no voice
|
1373
|
+
- Important announcement: converse("Warning: System will restart", skip_tts=False) # Always use voice
|
1374
|
+
- Quick confirmation: converse("Done!", skip_tts=True, wait_for_response=False) # Fast text-only
|
1375
|
+
- Follow user preference: converse("Hello") # Uses VOICEMODE_SKIP_TTS setting
|
1363
1376
|
"""
|
1364
1377
|
# Convert string booleans to actual booleans
|
1365
1378
|
if isinstance(wait_for_response, str):
|
@@ -1368,6 +1381,16 @@ async def converse(
|
|
1368
1381
|
disable_silence_detection = disable_silence_detection.lower() in ('true', '1', 'yes', 'on')
|
1369
1382
|
if isinstance(audio_feedback, str):
|
1370
1383
|
audio_feedback = audio_feedback.lower() in ('true', '1', 'yes', 'on')
|
1384
|
+
if skip_tts is not None and isinstance(skip_tts, str):
|
1385
|
+
skip_tts = skip_tts.lower() in ('true', '1', 'yes', 'on')
|
1386
|
+
|
1387
|
+
# Determine whether to skip TTS
|
1388
|
+
if skip_tts is not None:
|
1389
|
+
# Parameter explicitly set, use it
|
1390
|
+
should_skip_tts = skip_tts
|
1391
|
+
else:
|
1392
|
+
# Use global setting
|
1393
|
+
should_skip_tts = SKIP_TTS
|
1371
1394
|
|
1372
1395
|
# Convert string speed to float
|
1373
1396
|
if speed is not None and isinstance(speed, str):
|
@@ -1457,15 +1480,26 @@ async def converse(
|
|
1457
1480
|
if not wait_for_response:
|
1458
1481
|
try:
|
1459
1482
|
async with audio_operation_lock:
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1483
|
+
if should_skip_tts:
|
1484
|
+
# Skip TTS entirely
|
1485
|
+
success = True
|
1486
|
+
tts_metrics = {
|
1487
|
+
'ttfa': 0,
|
1488
|
+
'generation': 0,
|
1489
|
+
'playback': 0,
|
1490
|
+
'total': 0
|
1491
|
+
}
|
1492
|
+
tts_config = {'provider': 'no-op', 'voice': 'none'}
|
1493
|
+
else:
|
1494
|
+
success, tts_metrics, tts_config = await text_to_speech_with_failover(
|
1495
|
+
message=message,
|
1496
|
+
voice=voice,
|
1497
|
+
model=tts_model,
|
1498
|
+
instructions=tts_instructions,
|
1499
|
+
audio_format=audio_format,
|
1500
|
+
initial_provider=tts_provider,
|
1501
|
+
speed=speed
|
1502
|
+
)
|
1469
1503
|
|
1470
1504
|
# Include timing info if available
|
1471
1505
|
timing_info = ""
|
@@ -1589,15 +1623,26 @@ async def converse(
|
|
1589
1623
|
async with audio_operation_lock:
|
1590
1624
|
# Speak the message
|
1591
1625
|
tts_start = time.perf_counter()
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1626
|
+
if should_skip_tts:
|
1627
|
+
# Skip TTS entirely for faster response
|
1628
|
+
tts_success = True
|
1629
|
+
tts_metrics = {
|
1630
|
+
'ttfa': 0,
|
1631
|
+
'generation': 0,
|
1632
|
+
'playback': 0,
|
1633
|
+
'total': 0
|
1634
|
+
}
|
1635
|
+
tts_config = {'provider': 'no-op', 'voice': 'none'}
|
1636
|
+
else:
|
1637
|
+
tts_success, tts_metrics, tts_config = await text_to_speech_with_failover(
|
1638
|
+
message=message,
|
1639
|
+
voice=voice,
|
1640
|
+
model=tts_model,
|
1641
|
+
instructions=tts_instructions,
|
1642
|
+
audio_format=audio_format,
|
1643
|
+
initial_provider=tts_provider,
|
1644
|
+
speed=speed
|
1645
|
+
)
|
1601
1646
|
|
1602
1647
|
# Add TTS sub-metrics
|
1603
1648
|
if tts_metrics:
|
@@ -14,7 +14,7 @@ import psutil
|
|
14
14
|
|
15
15
|
from voice_mode.server import mcp
|
16
16
|
from voice_mode.config import WHISPER_PORT, KOKORO_PORT, LIVEKIT_PORT, SERVICE_AUTO_ENABLE
|
17
|
-
from voice_mode.utils.services.common import find_process_by_port
|
17
|
+
from voice_mode.utils.services.common import find_process_by_port, check_service_status
|
18
18
|
from voice_mode.utils.services.whisper_helpers import find_whisper_server, find_whisper_model
|
19
19
|
from voice_mode.utils.services.kokoro_helpers import find_kokoro_fastapi, has_gpu_support
|
20
20
|
|
@@ -195,10 +195,16 @@ async def status_service(service_name: str) -> str:
|
|
195
195
|
port = LIVEKIT_PORT
|
196
196
|
else: # frontend
|
197
197
|
port = 3000
|
198
|
-
proc = find_process_by_port(port)
|
199
198
|
|
200
|
-
|
201
|
-
|
199
|
+
status, proc = check_service_status(port)
|
200
|
+
|
201
|
+
if status == "not_available":
|
202
|
+
return f"❌ {service_name.capitalize()} is not available"
|
203
|
+
elif status == "forwarded":
|
204
|
+
return f"""🔄 {service_name.capitalize()} is available via port forwarding
|
205
|
+
Port: {port} (forwarded)
|
206
|
+
Local process: Not running
|
207
|
+
Remote: Accessible"""
|
202
208
|
|
203
209
|
try:
|
204
210
|
with proc.oneshot():
|
@@ -269,7 +275,7 @@ async def status_service(service_name: str) -> str:
|
|
269
275
|
if extra_info_parts:
|
270
276
|
extra_info = "\n " + "\n ".join(extra_info_parts)
|
271
277
|
|
272
|
-
return f"""✅ {service_name.capitalize()} is running
|
278
|
+
return f"""✅ {service_name.capitalize()} is running locally
|
273
279
|
PID: {proc.pid}
|
274
280
|
Port: {port}
|
275
281
|
CPU: {cpu_percent:.1f}%
|
@@ -268,8 +268,8 @@ async def kokoro_install(
|
|
268
268
|
|
269
269
|
if auto_enable:
|
270
270
|
logger.info("Auto-enabling kokoro service...")
|
271
|
-
from voice_mode.tools.service import
|
272
|
-
enable_result = await
|
271
|
+
from voice_mode.tools.service import enable_service
|
272
|
+
enable_result = await enable_service("kokoro")
|
273
273
|
if "✅" in enable_result:
|
274
274
|
enable_message = " Service auto-enabled."
|
275
275
|
else:
|
@@ -333,8 +333,8 @@ WantedBy=default.target
|
|
333
333
|
|
334
334
|
if auto_enable:
|
335
335
|
logger.info("Auto-enabling kokoro service...")
|
336
|
-
from voice_mode.tools.service import
|
337
|
-
enable_result = await
|
336
|
+
from voice_mode.tools.service import enable_service
|
337
|
+
enable_result = await enable_service("kokoro")
|
338
338
|
if "✅" in enable_result:
|
339
339
|
enable_message = " Service auto-enabled."
|
340
340
|
else:
|
@@ -201,7 +201,8 @@ async def whisper_install(
|
|
201
201
|
# Clean any previous build (only if Makefile exists)
|
202
202
|
if os.path.exists("Makefile"):
|
203
203
|
try:
|
204
|
-
subprocess.run(["make", "clean"], check=True
|
204
|
+
subprocess.run(["make", "clean"], check=True,
|
205
|
+
capture_output=True, text=True)
|
205
206
|
except subprocess.CalledProcessError:
|
206
207
|
logger.warning("Make clean failed, continuing anyway...")
|
207
208
|
|
@@ -216,14 +217,27 @@ async def whisper_install(
|
|
216
217
|
# Get number of CPU cores for parallel build
|
217
218
|
cpu_count = os.cpu_count() or 4
|
218
219
|
|
219
|
-
|
220
|
+
# Determine if we should show build output
|
221
|
+
debug_mode = os.environ.get("VOICEMODE_DEBUG", "").lower() in ("true", "1", "yes")
|
220
222
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
223
|
+
if debug_mode:
|
224
|
+
subprocess.run(["make", f"-j{cpu_count}"], env=build_env, check=True)
|
225
|
+
else:
|
226
|
+
# Suppress output unless there's an error
|
227
|
+
logger.info("Building whisper.cpp (this may take a few minutes)...")
|
228
|
+
try:
|
229
|
+
result = subprocess.run(["make", f"-j{cpu_count}"], env=build_env,
|
230
|
+
capture_output=True, text=True, check=True)
|
231
|
+
logger.info("Build completed successfully")
|
232
|
+
except subprocess.CalledProcessError as e:
|
233
|
+
logger.error(f"Build failed: {e}")
|
234
|
+
if e.stdout:
|
235
|
+
logger.error(f"Build output:\n{e.stdout}")
|
236
|
+
if e.stderr:
|
237
|
+
logger.error(f"Build errors:\n{e.stderr}")
|
238
|
+
raise
|
239
|
+
|
240
|
+
# Note: whisper-server is now built as part of the main build target
|
227
241
|
|
228
242
|
# Download model using shared helper
|
229
243
|
logger.info(f"Downloading default model: {model}")
|
@@ -283,12 +297,7 @@ fi
|
|
283
297
|
|
284
298
|
echo "Starting whisper-server with model: $MODEL_NAME" >> "$LOG_FILE"
|
285
299
|
|
286
|
-
#
|
287
|
-
if [ ! -f "$WHISPER_DIR/build/bin/whisper-server" ] && [ ! -f "$WHISPER_DIR/server" ]; then
|
288
|
-
echo "Building whisper-server..." >> "$LOG_FILE"
|
289
|
-
cd "$WHISPER_DIR"
|
290
|
-
make server >> "$LOG_FILE" 2>&1
|
291
|
-
fi
|
300
|
+
# Note: whisper-server is now built as part of the main build target
|
292
301
|
|
293
302
|
# Determine server binary location
|
294
303
|
if [ -f "$WHISPER_DIR/build/bin/whisper-server" ]; then
|
@@ -375,8 +384,8 @@ exec "$SERVER_BIN" \\
|
|
375
384
|
|
376
385
|
if auto_enable:
|
377
386
|
logger.info("Auto-enabling whisper service...")
|
378
|
-
from voice_mode.tools.service import
|
379
|
-
enable_result = await
|
387
|
+
from voice_mode.tools.service import enable_service
|
388
|
+
enable_result = await enable_service("whisper")
|
380
389
|
if "✅" in enable_result:
|
381
390
|
enable_message = " Service auto-enabled."
|
382
391
|
else:
|
@@ -458,8 +467,8 @@ WantedBy=default.target
|
|
458
467
|
|
459
468
|
if auto_enable:
|
460
469
|
logger.info("Auto-enabling whisper service...")
|
461
|
-
from voice_mode.tools.service import
|
462
|
-
enable_result = await
|
470
|
+
from voice_mode.tools.service import enable_service
|
471
|
+
enable_result = await enable_service("whisper")
|
463
472
|
if "✅" in enable_result:
|
464
473
|
enable_message = " Service auto-enabled."
|
465
474
|
else:
|
@@ -0,0 +1,80 @@
|
|
1
|
+
"""Common utilities for service management tools."""
|
2
|
+
|
3
|
+
import psutil
|
4
|
+
import socket
|
5
|
+
from typing import Optional, Tuple
|
6
|
+
import logging
|
7
|
+
|
8
|
+
logger = logging.getLogger("voice-mode")
|
9
|
+
|
10
|
+
|
11
|
+
def find_process_by_port(port: int) -> Optional[psutil.Process]:
|
12
|
+
"""Find a process listening on the specified port.
|
13
|
+
|
14
|
+
Returns None if port is only accessible via SSH forwarding or other non-local means.
|
15
|
+
"""
|
16
|
+
try:
|
17
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
18
|
+
try:
|
19
|
+
# Skip if we can't access process info (might be another user's process)
|
20
|
+
if not proc.is_running():
|
21
|
+
continue
|
22
|
+
|
23
|
+
# Skip SSH processes - these are port forwards, not actual services
|
24
|
+
proc_name = proc.name().lower()
|
25
|
+
if proc_name in ['ssh', 'sshd']:
|
26
|
+
continue
|
27
|
+
|
28
|
+
for conn in proc.connections():
|
29
|
+
if conn.laddr.port == port and conn.status == 'LISTEN':
|
30
|
+
# Verify this is a real local process
|
31
|
+
try:
|
32
|
+
# Try to access basic process info to ensure it's real
|
33
|
+
_ = proc.pid
|
34
|
+
_ = proc.create_time()
|
35
|
+
return proc
|
36
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
37
|
+
# Process doesn't actually exist or we can't access it
|
38
|
+
continue
|
39
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
40
|
+
continue
|
41
|
+
except Exception as e:
|
42
|
+
logger.error(f"Error finding process by port: {e}")
|
43
|
+
return None
|
44
|
+
|
45
|
+
|
46
|
+
def is_port_accessible(port: int, host: str = "127.0.0.1", timeout: float = 1.0) -> bool:
|
47
|
+
"""Check if a port is accessible (can connect to it).
|
48
|
+
|
49
|
+
This will return True for both locally running services and SSH-forwarded ports.
|
50
|
+
"""
|
51
|
+
try:
|
52
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
53
|
+
sock.settimeout(timeout)
|
54
|
+
result = sock.connect_ex((host, port))
|
55
|
+
return result == 0
|
56
|
+
except Exception as e:
|
57
|
+
logger.error(f"Error checking port accessibility: {e}")
|
58
|
+
return False
|
59
|
+
|
60
|
+
|
61
|
+
def check_service_status(port: int) -> Tuple[str, Optional[psutil.Process]]:
|
62
|
+
"""Check the status of a service on a given port.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Tuple of (status, process):
|
66
|
+
- ("local", process) if running locally
|
67
|
+
- ("forwarded", None) if accessible but not local
|
68
|
+
- ("not_available", None) if not accessible at all
|
69
|
+
"""
|
70
|
+
# First check if there's a local process
|
71
|
+
proc = find_process_by_port(port)
|
72
|
+
if proc:
|
73
|
+
return ("local", proc)
|
74
|
+
|
75
|
+
# No local process, check if port is accessible (might be forwarded)
|
76
|
+
if is_port_accessible(port):
|
77
|
+
return ("forwarded", None)
|
78
|
+
|
79
|
+
# Not accessible at all
|
80
|
+
return ("not_available", None)
|
@@ -1,22 +0,0 @@
|
|
1
|
-
"""Common utilities for service management tools."""
|
2
|
-
|
3
|
-
import psutil
|
4
|
-
from typing import Optional
|
5
|
-
import logging
|
6
|
-
|
7
|
-
logger = logging.getLogger("voice-mode")
|
8
|
-
|
9
|
-
|
10
|
-
def find_process_by_port(port: int) -> Optional[psutil.Process]:
|
11
|
-
"""Find a process listening on the specified port."""
|
12
|
-
try:
|
13
|
-
for proc in psutil.process_iter(['pid', 'name']):
|
14
|
-
try:
|
15
|
-
for conn in proc.connections():
|
16
|
-
if conn.laddr.port == port and conn.status == 'LISTEN':
|
17
|
-
return proc
|
18
|
-
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
19
|
-
continue
|
20
|
-
except Exception as e:
|
21
|
-
logger.error(f"Error finding process by port: {e}")
|
22
|
-
return None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/app/api/connection-details/route.ts
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/components/NoAgentNotification.tsx
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/components/TranscriptionView.tsx
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.frontend.plist
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.kokoro.plist
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.livekit.plist
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/launchd/com.voicemode.whisper.plist
RENAMED
File without changes
|
File without changes
|
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-frontend.service
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-kokoro.service
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-livekit.service
RENAMED
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/templates/systemd/voicemode-whisper.service
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{voice_mode-2.22.2 → voice_mode-2.23.0}/voice_mode/tools/services/livekit/production_server.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|