feat(generation): rebuild Generation Page with full AI workflows
- Rebuild GenerationPage.tsx from static placeholder to production-parity exam generation wizard with all 4 IELTS modules (Reading, Listening, Writing, Speaking) plus Level and Industry - Add per-module config: timer, CEFR difficulty tags, access type, entities, approval workflow, rubric, grading system, shuffling - Reading: AI passage generation, 5 exercise types (MCQ, Fill Blanks, Write Blanks, True/False, Paragraph Match), categories/types - Listening: 4 section types, AI context generation, TTS audio generation - Writing: Task 1/2, AI instruction generation, word limits, marks - Speaking: 3 parts, AI script generation, avatar video generation with 7 avatar options - Wire ExamStructuresPage to real CRUD API (list/create/delete) - Add backend exam_structure model and controller (/api/exam-structures) - Enhance ai_controller with 5 specialized generation handlers (passage, exercises, writing instructions, speaking script, listening context) - Add POST /api/exam/generation/submit for exam creation workflow - Fix media.service avatar video endpoint alignment - All 12 API tests passed, browser-verified with real OpenAI calls Made-with: Cursor
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from . import templates
|
||||
from . import ielts_exam
|
||||
from . import custom_exam
|
||||
from . import exam_structures
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _json_body():
|
||||
try:
|
||||
return json.loads(request.httprequest.data or '{}')
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _json_response(data, status=200):
|
||||
return request.make_json_response(data, status=status)
|
||||
|
||||
|
||||
class ExamStructureController(http.Controller):
|
||||
|
||||
@http.route('/api/exam-structures', type='http', auth='user', methods=['GET'], csrf=False)
|
||||
def list_structures(self, **kw):
|
||||
domain = [('active', '=', True)]
|
||||
entity_id = kw.get('entity_id')
|
||||
if entity_id:
|
||||
domain.append(('entity_id', '=', int(entity_id)))
|
||||
|
||||
limit = int(kw.get('limit', 50))
|
||||
offset = int(kw.get('offset', 0))
|
||||
records = request.env['encoach.exam.structure'].search(domain, limit=limit, offset=offset, order='create_date desc')
|
||||
total = request.env['encoach.exam.structure'].search_count(domain)
|
||||
|
||||
items = []
|
||||
for r in records:
|
||||
modules = []
|
||||
if r.modules:
|
||||
try:
|
||||
modules = json.loads(r.modules)
|
||||
except Exception:
|
||||
modules = []
|
||||
items.append({
|
||||
'id': r.id,
|
||||
'name': r.name,
|
||||
'entity_id': r.entity_id.id if r.entity_id else None,
|
||||
'entity_name': r.entity_id.name if r.entity_id else None,
|
||||
'industry': r.industry or '',
|
||||
'modules': modules,
|
||||
'config': json.loads(r.config) if r.config else {},
|
||||
})
|
||||
|
||||
return _json_response({'items': items, 'total': total})
|
||||
|
||||
@http.route('/api/exam-structures', type='http', auth='user', methods=['POST'], csrf=False)
|
||||
def create_structure(self, **kw):
|
||||
body = _json_body()
|
||||
name = body.get('name')
|
||||
if not name:
|
||||
return _json_response({'error': 'name is required'}, status=400)
|
||||
|
||||
vals = {
|
||||
'name': name,
|
||||
'industry': body.get('industry', ''),
|
||||
'modules': json.dumps(body.get('modules', [])),
|
||||
'config': json.dumps(body.get('config', {})),
|
||||
}
|
||||
entity_id = body.get('entity_id')
|
||||
if entity_id:
|
||||
vals['entity_id'] = int(entity_id)
|
||||
|
||||
record = request.env['encoach.exam.structure'].create(vals)
|
||||
return _json_response({
|
||||
'id': record.id,
|
||||
'name': record.name,
|
||||
'entity_id': record.entity_id.id if record.entity_id else None,
|
||||
'industry': record.industry or '',
|
||||
'modules': json.loads(record.modules) if record.modules else [],
|
||||
})
|
||||
|
||||
@http.route('/api/exam-structures/<int:structure_id>', type='http', auth='user', methods=['DELETE'], csrf=False)
|
||||
def delete_structure(self, structure_id, **kw):
|
||||
record = request.env['encoach.exam.structure'].browse(structure_id)
|
||||
if not record.exists():
|
||||
return _json_response({'error': 'Structure not found'}, status=404)
|
||||
record.unlink()
|
||||
return _json_response({'success': True})
|
||||
Reference in New Issue
Block a user