Spaces:
Running
Running
| 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)) | |