"""REST endpoints for AI media generation — TTS, avatar videos.""" import base64 import json import logging from odoo import http from odoo.http import request, Response _logger = logging.getLogger(__name__) def _json_response(data, status=200): return Response(json.dumps(data, default=str), status=status, content_type="application/json") def _get_json(): try: return json.loads(request.httprequest.data or "{}") except Exception: return {} class MediaController(http.Controller): """Handles /api/exam/*/media and avatar endpoints from media.service.ts.""" def _get_tts_provider(self): return request.env["ir.config_parameter"].sudo().get_param("encoach_ai.tts_provider", "polly") def _get_tts(self): """Get the configured TTS provider.""" provider = self._get_tts_provider() if provider == "elevenlabs": from odoo.addons.encoach_ai.services.elevenlabs_service import ElevenLabsService return ElevenLabsService(request.env) from odoo.addons.encoach_ai.services.polly_service import PollyService return PollyService(request.env) def _synthesize(self, text, body): """Dispatch TTS call with correct kwargs for each provider.""" tts = self._get_tts() provider = self._get_tts_provider() if provider == "elevenlabs": gender = body.get("gender", "female") language = body.get("language", "en-GB") voice_key = f"{gender}_{'british' if 'GB' in language else 'american'}" return tts.synthesize(text, voice_id=body.get("voice_id"), voice_key=voice_key) return tts.synthesize( text, voice=body.get("voice"), language=body.get("language", "en-GB"), gender=body.get("gender", "female"), ) # ── POST /api/exam/listening/media — generate listening audio ── @http.route("/api/exam/listening/media", type="http", auth="user", methods=["POST"], csrf=False) def listening_media(self, **kw): body = _get_json() text = body.get("text", "") if not text: return _json_response({"error": "No text provided"}, 400) try: result = self._synthesize(text, body) audio_b64 = base64.b64encode(result["audio"]).decode() return _json_response({ "audio_base64": audio_b64, "content_type": result["content_type"], "voice": result.get("voice") or result.get("voice_id"), "characters": result["characters"], }) except Exception as e: _logger.exception("Listening media generation failed") return _json_response({"error": str(e)}, 500) # ── POST /api/exam/speaking/media — generate speaking prompt audio ── @http.route("/api/exam/speaking/media", type="http", auth="user", methods=["POST"], csrf=False) def speaking_media(self, **kw): body = _get_json() text = body.get("text", "") if not text: return _json_response({"error": "No text provided"}, 400) try: result = self._synthesize(text, body) audio_b64 = base64.b64encode(result["audio"]).decode() return _json_response({ "audio_base64": audio_b64, "content_type": result["content_type"], }) except Exception as e: return _json_response({"error": str(e)}, 500) # ── GET /api/exam/avatars — list ELAI avatars ── @http.route("/api/exam/avatars", type="http", auth="user", methods=["GET"], csrf=False) def list_avatars(self, **kw): try: from odoo.addons.encoach_ai.services.elai_service import ElaiService elai = ElaiService(request.env) avatars = elai.list_avatars() return _json_response({"avatars": avatars}) except Exception as e: return _json_response({"avatars": [], "note": str(e)}) # ── POST /api/exam/avatar/video — create avatar video ── @http.route("/api/exam/avatar/video", type="http", auth="user", methods=["POST"], csrf=False) def create_avatar_video(self, **kw): body = _get_json() try: from odoo.addons.encoach_ai.services.elai_service import ElaiService elai = ElaiService(request.env) result = elai.create_video( body.get("script", ""), avatar_id=body.get("avatar_id"), title=body.get("title", "EnCoach Video"), ) return _json_response(result) except Exception as e: return _json_response({"error": str(e)}, 500) # ── GET /api/exam/avatar/video/:id — check video status ── @http.route("/api/exam/avatar/video/", type="http", auth="user", methods=["GET"], csrf=False) def video_status(self, video_id, **kw): try: from odoo.addons.encoach_ai.services.elai_service import ElaiService elai = ElaiService(request.env) return _json_response(elai.get_video_status(video_id)) except Exception as e: return _json_response({"video_id": video_id, "status": "error", "error": str(e)}) # ── POST /api/courses/ai-generate — AiCreationAssistant.tsx ── @http.route("/api/courses/ai-generate", type="http", auth="user", methods=["POST"], csrf=False) def ai_generate_course(self, **kw): body = _get_json() try: from odoo.addons.encoach_ai.services.openai_service import OpenAIService ai = OpenAIService(request.env) messages = [ {"role": "system", "content": ( "Generate a complete course structure. Return JSON: " "{\"title\": string, \"description\": string, \"modules\": " "[{\"title\": string, \"skill\": string, \"estimated_hours\": number, " "\"topics\": [string], \"resources\": [{\"title\": string, \"type\": string}]}], " "\"duration_weeks\": number}" )}, {"role": "user", "content": json.dumps(body)}, ] result = ai.chat_json(messages, action="generate_course", max_tokens=4096) return _json_response(result) except Exception as e: return _json_response({"error": str(e)}, 500)