Update 2026-04-22 11:43:20

This commit is contained in:
kassam 2026-04-22 11:43:21 +04:00
parent 2e3cc1ba5b
commit 3122a52966
3 changed files with 29 additions and 17 deletions

View File

@ -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")

View File

@ -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, <command>\".")
print(" Shortcuts: help | example | yolo | patrol | auto on/off | q")
print("=" * 54)
print()
try:

View File

@ -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