| import io |
| import json |
|
|
| from fastapi import APIRouter, Depends, HTTPException |
| from fastapi.responses import StreamingResponse |
| from google import genai |
| from pydantic import BaseModel |
|
|
| from ..auth.dependencies import get_current_user |
| from ..config.model_ssot import SYSTEM_MODELS |
| from ..services.prompt_service import prompt_service |
| from ..services.text_to_speech_service import text_to_speech_service |
|
|
| router = APIRouter(prefix="/api/audio", tags=["audio"]) |
|
|
|
|
| class TTSRequest(BaseModel): |
| text: str = "" |
| scene: str = "marketing_pitch" |
| voice: str | None = None |
| agent_data: dict | None = None |
|
|
|
|
| @router.post("/generate") |
| async def generate_audio_stream(request: TTSRequest, current_user: dict = Depends(get_current_user)): |
| """ |
| Generates TTS audio and returns it as a streaming WAV response. |
| If agent_data is provided, it uses an LLM to dynamically generate the script |
| based on the scene's prompt template and the data. |
| """ |
| prompt_name = f"tts_{request.scene}" |
|
|
| |
| template = prompt_service.get_prompt(prompt_name) |
| |
| if not template or template == "You are a helpful AI assistant.": |
| template = "{text}" |
|
|
| final_text = request.text |
|
|
| |
| if request.agent_data: |
| from ..services.credential_service import credential_service |
|
|
| api_key = await credential_service.get_credential("GEMINI_API_KEY") or await credential_service.get_credential( |
| "GOOGLE_API_KEY" |
| ) |
| if not api_key: |
| raise HTTPException(status_code=500, detail="No API key for semantic translation") |
|
|
| client = genai.Client(api_key=api_key) |
| model_name = SYSTEM_MODELS.get("DEFAULT_TEXT", "models/gemini-3.1-flash-lite").split("/")[-1] |
|
|
| default_instruction = ( |
| "You are a Senior Chief of Staff translating dashboard JSON data into a fluent, " |
| "natural spoken briefing script. Read the provided prompt template to understand " |
| "the voice style and instructions." |
| ) |
| system_instruction = prompt_service.get_prompt("CHIEF_OF_STAFF_AUDIO_BRIEFING", default=default_instruction) |
|
|
| prompt = ( |
| f"=== Prompt Template ===\n{template}\n\n" |
| f"=== Dashboard Data ===\n{json.dumps(request.agent_data, ensure_ascii=False)}\n\n" |
| "Task: Generate the final spoken script based ONLY on the dashboard data provided, " |
| "adhering strictly to the style requested in the template. Output the raw text ready for TTS." |
| ) |
|
|
| try: |
| response = await client.aio.models.generate_content( |
| model=model_name, contents=prompt, config={"system_instruction": system_instruction} |
| ) |
| final_text = response.text if response.text else "Failed to generate briefing." |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=f"LLM translation failed: {str(e)}") from e |
|
|
| else: |
| |
| try: |
| final_text = template.format(text=request.text) |
| except KeyError: |
| |
| final_text = template + "\n" + request.text |
|
|
| |
| voice = request.voice |
| if not voice: |
| |
| voice = "Charon" if request.scene == "commander_briefing" else "Puck" |
|
|
| |
| success, result = await text_to_speech_service.generate_audio(final_text, voice_name=voice) |
|
|
| if not success: |
| raise HTTPException(status_code=500, detail=str(result)) |
|
|
| |
| return StreamingResponse( |
| io.BytesIO(result), media_type="audio/wav", headers={"Content-Disposition": 'inline; filename="speech.wav"'} |
| ) |
|
|