EnCoach Odoo 19 custom modules
Full backend implementation with custom Odoo modules: - encoach_api: Core API, user management, JWT auth - encoach_exam: Exam generation (reading, writing, listening, speaking) - encoach_evaluate: AI-powered evaluation (writing, speaking) - encoach_training: Training tips and walkthrough - encoach_storage: File storage management - encoach_payment: Stripe, PayPal, Paymob integration - encoach_mail: Email notifications Made-with: Cursor
This commit is contained in:
107
encoach_ai_media/services/elai_service.py
Normal file
107
encoach_ai_media/services/elai_service.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
ELAI_BASE_URL = "https://apis.elai.io/api/v1/videos"
|
||||
|
||||
|
||||
class EncoachElaiService:
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
self.token = (
|
||||
env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("encoach.elai_token", "")
|
||||
)
|
||||
|
||||
def _headers(self):
|
||||
return {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def create_video(self, text, avatar_code):
|
||||
"""Create and render an ELAI video.
|
||||
|
||||
Returns the video_id for status polling, or None on failure.
|
||||
"""
|
||||
avatar = (
|
||||
self.env["encoach.elai.avatar"]
|
||||
.sudo()
|
||||
.search([("avatar_code", "=", avatar_code)], limit=1)
|
||||
)
|
||||
if not avatar:
|
||||
_logger.error("Avatar not found: %s", avatar_code)
|
||||
return None
|
||||
|
||||
payload = {
|
||||
"name": f"EnCoach Speaking - {avatar.name}",
|
||||
"slides": [
|
||||
{
|
||||
"avatar": {
|
||||
"code": avatar.avatar_code,
|
||||
"canvas": avatar.canvas or "default",
|
||||
"voiceId": avatar.voice_id or "",
|
||||
"voiceProvider": avatar.voice_provider or "",
|
||||
},
|
||||
"speech": text,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
try:
|
||||
resp = httpx.post(
|
||||
ELAI_BASE_URL,
|
||||
headers=self._headers(),
|
||||
json=payload,
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
video_id = data.get("_id") or data.get("id")
|
||||
|
||||
self._render_video(video_id)
|
||||
return video_id
|
||||
except Exception:
|
||||
_logger.exception("ELAI video creation failed")
|
||||
return None
|
||||
|
||||
def _render_video(self, video_id):
|
||||
try:
|
||||
httpx.post(
|
||||
f"{ELAI_BASE_URL}/render/{video_id}",
|
||||
headers=self._headers(),
|
||||
timeout=30,
|
||||
)
|
||||
except Exception:
|
||||
_logger.exception("ELAI render request failed for %s", video_id)
|
||||
|
||||
def poll_status(self, video_id):
|
||||
"""Poll ELAI for video rendering status.
|
||||
|
||||
Returns dict with 'status' and optionally 'url' keys.
|
||||
"""
|
||||
try:
|
||||
resp = httpx.get(
|
||||
f"{ELAI_BASE_URL}/{video_id}",
|
||||
headers=self._headers(),
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
status = data.get("status", "unknown")
|
||||
result = {"status": status}
|
||||
if status == "ready":
|
||||
result["url"] = data.get("url", "")
|
||||
return result
|
||||
except Exception:
|
||||
_logger.exception("ELAI status poll failed for %s", video_id)
|
||||
return {"status": "error"}
|
||||
|
||||
def get_avatars(self):
|
||||
"""List all configured ELAI avatars."""
|
||||
avatars = self.env["encoach.elai.avatar"].sudo().search([])
|
||||
return [a.to_encoach_dict() for a in avatars]
|
||||
Reference in New Issue
Block a user