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:
Yamen Ahmad
2026-04-11 14:27:03 +04:00
parent 5b2ccbfeec
commit 74c2c9f2d2
30 changed files with 378 additions and 187 deletions

View File

@@ -6,13 +6,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell } from "recharts";
import AiReportNarrative from "@/components/ai/AiReportNarrative";
const tabNarratives: Record<string, string> = {
overview: "Writing scores (61%) are significantly lower than other modules. Consider allocating more teaching resources to writing workshops. Reading leads at 72%, suggesting current materials are effective.",
trends: "Scores have shown a consistent upward trend of +14 points over 6 months. The plateau in April correlates with mid-term exam stress. June's 72% is the highest recorded average this year.",
distribution: "B1 is the largest cohort at 30%, indicating most students are at intermediate level. Only 3% reach C2 — consider creating more advanced pathways to support progression from C1.",
comparison: "Attendance dropped 8% in the second week of March, correlating with the mid-term assignment deadline. Consider spacing deadlines more evenly across the term.",
};
const thresholds = ["0%", "50%", "70%", "90%"];
const barData = [
@@ -69,7 +62,7 @@ export default function StatsCorporatePage() {
</TabsList>
<TabsContent value="overview" className="mt-4">
<AiReportNarrative narrative={tabNarratives.overview} />
<AiReportNarrative report_type="corporate-overview" data={{ modules: barData }} />
<Card className="border-0 shadow-sm">
<CardHeader><CardTitle className="text-base">Average Score by Module</CardTitle></CardHeader>
<CardContent>
@@ -87,7 +80,7 @@ export default function StatsCorporatePage() {
</TabsContent>
<TabsContent value="trends" className="mt-4">
<AiReportNarrative narrative={tabNarratives.trends} />
<AiReportNarrative report_type="corporate-trends" data={{ trends: trendData }} />
<Card className="border-0 shadow-sm">
<CardHeader><CardTitle className="text-base">Score Trend Over Time</CardTitle></CardHeader>
<CardContent>
@@ -105,7 +98,7 @@ export default function StatsCorporatePage() {
</TabsContent>
<TabsContent value="distribution" className="mt-4">
<AiReportNarrative narrative={tabNarratives.distribution} />
<AiReportNarrative report_type="corporate-distribution" data={{ distribution: distData }} />
<Card className="border-0 shadow-sm">
<CardHeader><CardTitle className="text-base">Level Distribution</CardTitle></CardHeader>
<CardContent className="flex justify-center">
@@ -122,7 +115,7 @@ export default function StatsCorporatePage() {
</TabsContent>
<TabsContent value="comparison" className="mt-4">
<AiReportNarrative narrative={tabNarratives.comparison} />
<AiReportNarrative report_type="corporate-comparison" data={{ threshold }} />
<Card className="border-0 shadow-sm">
<CardContent className="p-8 text-center text-muted-foreground">Entity comparison charts will appear here based on selected filters.</CardContent>
</Card>