diff --git a/API/audio_api.py b/API/audio_api.py index 774c844..89169df 100644 --- a/API/audio_api.py +++ b/API/audio_api.py @@ -47,10 +47,10 @@ from Core.config_loader import load_config LOG_DIR = os.path.join(PROJECT_ROOT, "logs") os.makedirs(LOG_DIR, exist_ok=True) -# logging.basicConfig is idempotent per process: if marcus_voice configured -# the root logger first, this call is a no-op and both modules share the same -# RotatingFileHandler (stdlib FileHandlers hold an internal lock, so concurrent -# writes to voice.log are safe). Rotation caps voice.log at 5 MB × 3 backups. +# All voice-subsystem logs go ONLY to logs/voice.log, not stdout — the +# terminal REPL needs a clean `Command:` prompt. Anything the operator +# needs to see is print()-ed explicitly from the callback sites. +# basicConfig is idempotent (no-op if marcus_voice installed handlers first). logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", @@ -59,7 +59,6 @@ logging.basicConfig( os.path.join(LOG_DIR, "voice.log"), maxBytes=5_000_000, backupCount=3, encoding="utf-8", ), - logging.StreamHandler(), ], ) log = logging.getLogger("audio_api") diff --git a/Brain/marcus_brain.py b/Brain/marcus_brain.py index c5a6465..edb966f 100644 --- a/Brain/marcus_brain.py +++ b/Brain/marcus_brain.py @@ -172,7 +172,10 @@ def _init_voice(): text = (text or "").strip() if not text: return - print(f" [Voice] {text}") + # One clean, distinctive line so the operator can see exactly + # what Whisper transcribed before the brain reacts. Everything + # else from the voice subsystem is file-only. + print(f' [Sanad] heard: "{text}"') try: result = process_command(text) except Exception as e: @@ -182,10 +185,12 @@ def _init_voice(): sp = (result.get("speak") or "").strip() if sp and _audio_api: _audio_api.speak(sp) + # Redraw the Command: prompt that our print clobbered + print("Command: ", end="", flush=True) _voice_module = VoiceModule(_audio_api, on_command=_on_command) _voice_module.start() - print(" [Voice] Always listening (Whisper + G1 mic + TtsMaker)") + print(" [Voice] listening in background — say \"Sanad\" + your command") except Exception as e: print(f" [Voice] Init failed: {e} — continuing without voice") _audio_api = None @@ -477,13 +482,15 @@ def run_terminal(): status = get_brain_status() print() - print("=" * 48) - print(" SANAD AI BRAIN — READY") - print("=" * 48) + print("=" * 54) + print(" SANAD AI BRAIN — READY") + print("=" * 54) for k, v in status.items(): - print(f" {k:<10}: {v}") - print("=" * 48) - print(" help | example | yolo | patrol | auto on/off | q") + print(f" {k:<12}: {v}") + print("-" * 54) + print(" Type a command, or say \"Sanad, \".") + print(" Shortcuts: help | example | yolo | patrol | auto on/off | q") + print("=" * 54) print() try: diff --git a/Voice/marcus_voice.py b/Voice/marcus_voice.py index a8a7d50..f712a37 100644 --- a/Voice/marcus_voice.py +++ b/Voice/marcus_voice.py @@ -40,9 +40,11 @@ from Core.config_loader import load_config LOG_DIR = os.path.join(PROJECT_ROOT, "logs") os.makedirs(LOG_DIR, exist_ok=True) -# basicConfig is idempotent. Whichever of audio_api / marcus_voice imports -# first installs the rotating handler; the other no-ops. Both loggers then -# share the same file handle with stdlib's per-handler thread lock. +# Voice runs as a background subsystem — its INFO/DEBUG logs go ONLY to +# logs/voice.log so they don't drown out the interactive `Command:` prompt. +# Anything the user needs to see (wake-word fired, command heard) is +# print()-ed explicitly from the callbacks below. +# basicConfig is idempotent; audio_api may have already called it. logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", @@ -51,7 +53,6 @@ logging.basicConfig( os.path.join(LOG_DIR, "voice.log"), maxBytes=5_000_000, backupCount=3, encoding="utf-8", ), - logging.StreamHandler(), ], ) log = logging.getLogger("marcus_voice") @@ -248,6 +249,11 @@ class VoiceModule: if self._check_wake_word(text): log.info("Wake word detected!") + # One clean line to the terminal so the operator knows voice + # actually heard them, even though all other voice logs are + # file-only. \n leads because we may be painting over a + # half-drawn `Command:` prompt. + print("\n [Sanad] wake heard — recording command…") self._state = State.WAKE_HEARD # Acknowledge