Spaces:
Running
Running
| from fastapi import HTTPException, APIRouter, Response | |
| from pydantic import BaseModel | |
| router = APIRouter() | |
| class SpeakPayload(BaseModel): | |
| text: str | |
| agent: str = "friday" | |
| format: str = "wav" # always wav | |
| class DevicePayload(BaseModel): | |
| id: str | |
| class SpaceAckPayload(BaseModel): | |
| space: str # "dark_space" | "family_friendly" | |
| persona: str = "friday" | |
| async def speak(p: SpeakPayload): | |
| """Synthesise TTS via XTTS-v2 and return raw WAV bytes.""" | |
| try: | |
| from backend.voice.tts import TTSPipeline | |
| tts = TTSPipeline() | |
| audio_bytes: bytes = await tts.synthesize(p.text, personality=p.agent) | |
| return Response(content=audio_bytes, media_type="audio/wav") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def space_ack(p: SpaceAckPayload): | |
| """ | |
| Β§2.1b β Space Context Voice Injection entry point. | |
| Returns the persona-aware acknowledgement text. Android will take this text | |
| and silently inject it into the local WAV recording without playing out loud. | |
| """ | |
| from backend.agent.personas import get_space_ack_prompt | |
| prompt_text = get_space_ack_prompt(p.persona, p.space) | |
| return {"status": "ok", "space": p.space, "persona": p.persona, "text": prompt_text} | |
| async def get_devices(): | |
| return [] | |
| async def set_device(p: DevicePayload): | |
| return {"status": "ok"} | |
| async def wake_status(): | |
| return {"status": "listening"} | |
| from fastapi import UploadFile, File, BackgroundTasks | |
| import os | |
| import shutil | |
| def process_offline_log_stt(file_path: str): | |
| import asyncio | |
| import logging | |
| try: | |
| from backend.voice.stt import STTPipeline | |
| stt = STTPipeline() | |
| with open(file_path, "rb") as f: | |
| audio_bytes = f.read() | |
| # Enqueue STT (wait for it as it's async) | |
| text = asyncio.run(stt.transcribe(audio_bytes)) | |
| if text.strip(): | |
| logging.info(f"Offline Audio Parsed: {text}") | |
| from backend.agent.react_agent import run_agent_pipeline | |
| asyncio.run(run_agent_pipeline(text)) | |
| except Exception as e: | |
| logging.error(f"Failed to process offline log STT: {e}") | |
| async def upload_log(file: UploadFile = File(...), background_tasks: BackgroundTasks = BackgroundTasks()): | |
| try: | |
| sync_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "voice", "audio_logs", "phone_sync") | |
| os.makedirs(sync_dir, exist_ok=True) | |
| file_path = os.path.join(sync_dir, file.filename) | |
| with open(file_path, "wb") as buffer: | |
| shutil.copyfileobj(file.file, buffer) | |
| # Enqueue STT parsing for the offline log | |
| background_tasks.add_task(process_offline_log_stt, file_path) | |
| return {"status": "ok", "filename": file.filename, "message": "Successfully uploaded"} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # βββ Β§0.4 JARVIS Voice Profile Preview & Test ββββββββββββββββββββββββββββββββ | |
| async def get_jarvis_voice_sample(): | |
| """Returns current tuned JARVIS voice sample WAV for UI playback.""" | |
| import os | |
| from fastapi.responses import FileResponse | |
| app_data = os.environ.get("JARVIS_APP_DATA_DIR", ".") | |
| sample_path = os.path.join(app_data, "voices", "jarvis.wav") | |
| if os.path.exists(sample_path): | |
| return FileResponse(sample_path, media_type="audio/wav") | |
| raise HTTPException(status_code=404, detail="JARVIS voice sample not found. Place a jarvis.wav file in the voices directory.") | |
| async def test_jarvis_voice(): | |
| """Synthesises a short test line through the full tuned Β§0.4 JARVIS pipeline.""" | |
| try: | |
| from backend.voice.tts import TTSPipeline | |
| tts = TTSPipeline() | |
| # context="conversation" β routes through XTTS with full tuned profile | |
| audio_bytes = await tts.synthesize( | |
| "Good evening, sir. All systems are fully operational.", | |
| personality="jarvis", | |
| language="en", | |
| context="conversation" | |
| ) | |
| from fastapi.responses import Response | |
| return Response(content=audio_bytes, media_type="audio/wav") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |