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:
@@ -1,7 +1,10 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { queryKeys } from "./keys";
|
||||
import { aiCourseService } from "@/services/ai-course.service";
|
||||
import type { ExaminerReview } from "@/types";
|
||||
import {
|
||||
aiCourseService,
|
||||
type AiCourseCreateEnglishRequest,
|
||||
type AiCourseCreateIeltsRequest,
|
||||
} from "@/services/ai-course.service";
|
||||
|
||||
export function useAiCourse(courseId: number | undefined) {
|
||||
return useQuery({
|
||||
@@ -22,7 +25,7 @@ export function useAiCourseTracks(courseId: number | undefined) {
|
||||
export function useCreateEnglishCourse() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: { current_level: string; target_level: string; learning_style: string[] }) =>
|
||||
mutationFn: (data: AiCourseCreateEnglishRequest) =>
|
||||
aiCourseService.createEnglish(data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["ai-course"] });
|
||||
@@ -33,7 +36,7 @@ export function useCreateEnglishCourse() {
|
||||
export function useCreateIeltsCourse() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: { exam_type: string; target_band: number; skills: string[] }) =>
|
||||
mutationFn: (data: AiCourseCreateIeltsRequest) =>
|
||||
aiCourseService.createIelts(data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["ai-course"] });
|
||||
@@ -63,8 +66,8 @@ export function useApproveQuality() {
|
||||
export function useRejectQuality() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ courseId, notes }: { courseId: number; notes: string }) =>
|
||||
aiCourseService.rejectQuality(courseId, notes),
|
||||
mutationFn: ({ courseId, reason }: { courseId: number; reason: string }) =>
|
||||
aiCourseService.rejectQuality(courseId, reason),
|
||||
onSuccess: (_d, { courseId }) => {
|
||||
qc.invalidateQueries({ queryKey: queryKeys.aiCourse.quality(courseId) });
|
||||
},
|
||||
@@ -89,7 +92,8 @@ export function useIeltsValidation(courseId: number | undefined) {
|
||||
export function useSubmitExaminerReview() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: ExaminerReview) => aiCourseService.submitExaminerReview(data),
|
||||
mutationFn: (data: { logId: number; action: string; examiner_notes?: string }) =>
|
||||
aiCourseService.submitExaminerReview(data.logId, { action: data.action, examiner_notes: data.examiner_notes }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["ai-course"] });
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ export function useExamAutoSave() {
|
||||
|
||||
export function useExamSubmit() {
|
||||
return useMutation({
|
||||
mutationFn: (examId: number) => examSessionService.submit(examId),
|
||||
mutationFn: (data: { examId: number; attempt_id: number; answers: { question_id: number; answer: unknown }[] }) =>
|
||||
examSessionService.submit(data.examId, { attempt_id: data.attempt_id, answers: data.answers }),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user