feat: Generation Page AI workflows + AI/Vector modules + exam session fixes
Generation Page (complete rebuild): - Full production-parity exam generation wizard with 4 IELTS modules - Reading: AI passage gen, 5 exercise types (MCQ, Fill, Write, T/F, Match) - Listening: 4 section types, AI context gen, TTS audio gen (ElevenLabs) - Writing: Task 1/2, AI instruction gen, word limits, marks - Speaking: 3 parts, AI script gen, avatar video gen (7 avatars) - Per-module config: timer, CEFR difficulty, access, approval, rubrics - Exam submission workflow (draft/published) Exam Structures: - New encoach.exam.structure model + CRUD controller - ExamStructuresPage wired to real API AI Module (encoach_ai): - OpenAI service, ElevenLabs TTS, AWS Polly, ELAI avatars - AI settings model with Odoo config parameters - 7 generation endpoints (passage, exercises, instructions, scripts, context) Vector Module (encoach_vector): - pgvector integration for RAG-based content search - Embedding service with sentence-transformers Exam Session Fixes: - Fixed ExamSession.tsx field mapping (question_type→type, exam_title→title) - Fixed submit payload to include attempt_id and answers - Fixed normalizeType to handle null/undefined Tested: 12/12 API tests passed, browser-verified with real OpenAI calls Made-with: Cursor
This commit is contained in:
87
custom_addons/encoach_ai/services/gptzero_service.py
Normal file
87
custom_addons/encoach_ai/services/gptzero_service.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""GPTZero AI content detection service."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import requests as _requests
|
||||
except ImportError:
|
||||
_requests = None
|
||||
|
||||
GPTZERO_BASE = "https://api.gptzero.me/v2"
|
||||
|
||||
|
||||
class GPTZeroService:
|
||||
"""Detect AI-generated content in student submissions."""
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
self._get_param = env["ir.config_parameter"].sudo().get_param
|
||||
|
||||
def _get_key(self):
|
||||
key = self._get_param("encoach_ai.gptzero_api_key", "")
|
||||
if not key:
|
||||
import os
|
||||
key = os.environ.get("GPT_ZERO_API_KEY", "")
|
||||
if not key:
|
||||
raise RuntimeError("GPTZero API key not configured — set in AI Settings")
|
||||
return key
|
||||
|
||||
def _log(self, action, latency, status="success", error=None):
|
||||
try:
|
||||
self.env["encoach.ai.log"].sudo().create({
|
||||
"service": "gptzero",
|
||||
"action": action,
|
||||
"latency_ms": latency,
|
||||
"status": status,
|
||||
"error_message": error,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def detect(self, text):
|
||||
"""Check if text is AI-generated.
|
||||
|
||||
Returns:
|
||||
dict with 'is_ai_generated' (bool), 'ai_probability' (float 0-1),
|
||||
'human_probability' (float), 'sentences' (list of per-sentence scores)
|
||||
"""
|
||||
if not _requests:
|
||||
raise RuntimeError("requests package not installed")
|
||||
key = self._get_key()
|
||||
t0 = time.time()
|
||||
try:
|
||||
resp = _requests.post(
|
||||
f"{GPTZERO_BASE}/predict/text",
|
||||
json={"document": text},
|
||||
headers={"x-api-key": key, "Content-Type": "application/json"},
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
doc = data.get("documents", [{}])[0] if data.get("documents") else {}
|
||||
result = {
|
||||
"is_ai_generated": doc.get("completely_generated_prob", 0) > 0.5,
|
||||
"ai_probability": doc.get("completely_generated_prob", 0),
|
||||
"human_probability": 1 - doc.get("completely_generated_prob", 0),
|
||||
"mixed_probability": doc.get("average_generated_prob", 0),
|
||||
"sentences": [
|
||||
{
|
||||
"text": s.get("sentence", ""),
|
||||
"ai_probability": s.get("generated_prob", 0),
|
||||
"is_ai": s.get("generated_prob", 0) > 0.5,
|
||||
}
|
||||
for s in doc.get("sentences", [])
|
||||
],
|
||||
}
|
||||
self._log("detect", int((time.time() - t0) * 1000))
|
||||
return result
|
||||
except Exception as exc:
|
||||
self._log("detect", int((time.time() - t0) * 1000), "error", str(exc))
|
||||
raise
|
||||
|
||||
def detect_batch(self, texts):
|
||||
"""Check multiple texts for AI generation."""
|
||||
return [self.detect(t) for t in texts]
|
||||
Reference in New Issue
Block a user