jarvis-cloud / backend /voice /activation.py
Jarvis2345's picture
Squash history — remove all prior commits (secret hygiene, S4)
a31f556
Raw
History Blame
5.5 kB
import asyncio
import logging
import os
import json
from datetime import datetime
from backend.voice.daily_recap import build_daily_recap_summary, generate_greeting_via_llm, get_boot_state_path
from backend.system.app_launcher import launch_application, open_url
from backend.services.pc_mic_service import pc_mic_service
async def _play_tts_bytes(audio_bytes: bytes):
"""Play synthesized WAV bytes natively using pygame mixer."""
import tempfile
import pygame
import asyncio
# Lazy init
if not pygame.mixer.get_init():
# TTSPipeline outputs 24000Hz mono
pygame.mixer.init(frequency=24000, size=-16, channels=1, buffer=4096)
# Write to temp file
fd, path = tempfile.mkstemp(suffix=".wav")
with os.fdopen(fd, 'wb') as f:
f.write(audio_bytes)
try:
# Play asynchronously without blocking the event loop
def _play():
pygame.mixer.music.load(path)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
await asyncio.to_thread(_play)
except Exception as e:
logging.error(f"Playback failed: {e}")
finally:
pc_mic_service.unmute()
try:
if os.path.exists(path):
os.remove(path)
except Exception:
pass
async def run_boot_greeting_sequence(persona: str, is_test: bool = False):
"""
Coordinates the Daily Recap generation, Riot/YouTube launch, and TTS.
"""
logging.info(f"Starting boot greeting sequence for persona: {persona}")
# 1. Start fetching the daily recap in the background
summary_task = asyncio.create_task(build_daily_recap_summary())
# 2. Fire and forget the background app launches ONLY ONCE PER DAY
today_str = datetime.now().strftime("%Y-%m-%d")
state_path = get_boot_state_path()
last_app_launch_date = ""
try:
if os.path.exists(state_path):
with open(state_path, "r") as f:
data = json.load(f)
last_app_launch_date = data.get("last_app_launch_date", "")
except Exception:
pass
if last_app_launch_date != today_str:
if not is_test:
asyncio.create_task(launch_application("RiotClientServices.exe"))
youtube_url = "https://www.youtube.com/@jarvistheai"
asyncio.create_task(open_url(youtube_url))
else:
logging.info("[TEST MODE] Skipping actual app launches.")
# Update the state
try:
# We must preserve last_boot_time if it exists
data = {}
if os.path.exists(state_path):
with open(state_path, "r") as f:
data = json.load(f)
data["last_app_launch_date"] = today_str
with open(state_path, "w") as f:
json.dump(data, f)
except Exception as e:
logging.error(f"Failed to update app launch date: {e}")
else:
logging.info("Skipping Riot/YouTube launch: Already launched today.")
# 3. Wait for the database recap to finish building
summary = await summary_task
# 4. Generate the personalized speech text via LLM
text_to_speak = await generate_greeting_via_llm(persona, summary)
logging.info(f"Generated boot greeting text: {text_to_speak}")
# 5. Play the initial greeting
from backend.voice.tts import synthesize_bytes
try:
initial_audio_bytes = await synthesize_bytes(text_to_speak, persona)
if initial_audio_bytes:
pc_mic_service.mute()
await _play_tts_bytes(initial_audio_bytes)
# 6. --- JARVIS 10X INTELLIGENT DAY REVIEW GREETING ---
# Executes AFTER existing greeting completes
from backend.voice.greeting_intelligence import generate_intelligent_follow_up
follow_up_text = await generate_intelligent_follow_up(persona, summary)
logging.info(f"Generated intelligent follow-up text: {follow_up_text}")
# Prefix the transition phrase explicitly required by the user
final_text = "AFTER THE EXSITING GREETING SYSTEM COMPLETES THEN TRHIS NEW UPDATE " + follow_up_text
follow_up_audio_bytes = await synthesize_bytes(final_text, persona)
if follow_up_audio_bytes:
await _play_tts_bytes(follow_up_audio_bytes)
# Fix LLM Amnesia: Record the greeting into memory
try:
from modules.memory import record_interaction
record_interaction(text_to_speak + " " + final_text, role="assistant")
except Exception as memory_e:
logging.error(f"Failed to record boot greeting to memory: {memory_e}")
except Exception as e:
logging.error(f"TTS Synthesis or Playback failed: {e}")
async def on_jarvis_friday_activated(persona: str, trigger_source: str):
"""
The main hook triggered by the frontend or external events.
"""
if trigger_source != "exe_launch":
logging.info(f"Activation ignored: trigger_source is {trigger_source}, expected 'exe_launch'")
return
logging.info(f"Valid exe_launch activation detected for persona {persona}. Kicking off boot sequence.")
# Fire and forget the boot sequence so we don't block the caller
asyncio.create_task(run_boot_greeting_sequence(persona))