Spaces:
Running
Running
| """Entry point for cascade mode - keeps cascade logic isolated from main.py.""" | |
| from __future__ import annotations | |
| import os | |
| import time | |
| import signal | |
| import logging | |
| from typing import TYPE_CHECKING, Any | |
| if TYPE_CHECKING: | |
| from argparse import Namespace | |
| from reachy_mini import ReachyMini | |
| from lyon_chatbox.tools.core_tools import ToolDependencies | |
| def run_cascade_mode( | |
| deps: ToolDependencies, | |
| robot: ReachyMini, | |
| args: Namespace, | |
| logger: logging.Logger, | |
| ) -> None: | |
| """Run the application in cascade mode (ASR→LLM→TTS pipeline). | |
| This function handles all cascade-specific initialization, UI creation, | |
| and lifecycle management, keeping main.py clean. | |
| Args: | |
| deps: Tool dependencies for robot control | |
| robot: ReachyMini instance | |
| args: Parsed command line arguments | |
| logger: Logger instance | |
| """ | |
| # Set env vars from CLI overrides BEFORE any config/handler import | |
| if getattr(args, "asr_provider", None): | |
| os.environ["CASCADE_ASR_PROVIDER"] = args.asr_provider | |
| if getattr(args, "llm_provider", None): | |
| os.environ["CASCADE_LLM_PROVIDER"] = args.llm_provider | |
| if getattr(args, "tts_provider", None): | |
| os.environ["CASCADE_TTS_PROVIDER"] = args.tts_provider | |
| from lyon_chatbox.cascade.handler import CascadeHandler | |
| logger.info("Using cascade pipeline mode (ASR→LLM→TTS)") | |
| stream_manager: Any = None | |
| if args.autotest: | |
| # Autotest mode — feed text utterances through the pipeline | |
| from pathlib import Path | |
| from lyon_chatbox.cascade.autotest_stream import CascadeTestStream | |
| if args.autotest == "__default__": | |
| test_file = str(Path(__file__).parent / "autotest.txt") | |
| else: | |
| test_file = args.autotest | |
| logger.info(f"Using autotest mode: {test_file}") | |
| handler = CascadeHandler(deps) | |
| handler.ensure_initialized() | |
| stream_manager = CascadeTestStream(handler, robot, test_file) | |
| elif args.gradio: | |
| # Gradio UI mode | |
| from lyon_chatbox.cascade.ui import CascadeGradioUI | |
| logger.info("Using Gradio UI for cascade mode") | |
| handler = CascadeHandler(deps) | |
| cascade_ui = CascadeGradioUI(handler, robot) | |
| stream_manager = cascade_ui.create_interface() | |
| else: | |
| # Console mode with VAD | |
| from lyon_chatbox.cascade.console import CascadeLocalStream | |
| logger.info("Using console mode for cascade (VAD-based speech detection)") | |
| handler = CascadeHandler(deps) | |
| handler.ensure_initialized() | |
| stream_manager = CascadeLocalStream(handler, robot) | |
| # Start services | |
| if deps.movement_manager: | |
| deps.movement_manager.start() | |
| if deps.head_wobbler: | |
| deps.head_wobbler.start() | |
| # For console mode: CascadeLocalStream.launch() handles media start | |
| # For Gradio mode: audio recording is handled by ContinuousVADRecorder, | |
| # and camera frames come directly from CameraWorker (no start_recording needed). | |
| if deps.camera_worker: | |
| deps.camera_worker.start() | |
| logger.info("Camera worker started in cascade mode") | |
| else: | |
| logger.warning("No camera worker available (deps.camera_worker is None)") | |
| if deps.vision_manager: | |
| deps.vision_manager.start() | |
| # Start cascade handler (only for Gradio mode - console mode runs synchronously) | |
| if args.gradio: | |
| handler.start() | |
| try: | |
| stream_manager.launch() | |
| except KeyboardInterrupt: | |
| logger.info("Keyboard interruption in main thread... closing server.") | |
| finally: | |
| # Ignore further Ctrl+C during cleanup so we always reach os._exit. | |
| signal.signal(signal.SIGINT, signal.SIG_IGN) | |
| # Stop the stream manager | |
| stream_manager.close() | |
| # Stop cascade handler (only for Gradio mode) | |
| if args.gradio: | |
| handler.stop() | |
| # Stop other services | |
| if deps.movement_manager: | |
| deps.movement_manager.stop() | |
| if deps.head_wobbler: | |
| deps.head_wobbler.stop() | |
| if deps.camera_worker: | |
| deps.camera_worker.stop() | |
| if deps.vision_manager: | |
| deps.vision_manager.stop() | |
| # Ensure media is explicitly closed before disconnecting | |
| try: | |
| robot.media.close() | |
| except Exception as e: | |
| logger.debug(f"Error closing media during shutdown: {e}") | |
| # Prevent connection to keep alive some threads | |
| robot.client.disconnect() | |
| time.sleep(1) | |
| logger.info("Cascade mode shutdown complete.") | |
| # Hard exit to avoid segfault from PortAudio/sounddevice native | |
| # cleanup racing with daemon thread teardown during interpreter shutdown. | |
| os._exit(0) | |