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
102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { queryKeys } from "./keys";
|
|
import {
|
|
aiCourseService,
|
|
type AiCourseCreateEnglishRequest,
|
|
type AiCourseCreateIeltsRequest,
|
|
} from "@/services/ai-course.service";
|
|
|
|
export function useAiCourse(courseId: number | undefined) {
|
|
return useQuery({
|
|
queryKey: queryKeys.aiCourse.course(courseId ?? 0),
|
|
queryFn: () => aiCourseService.getCourse(courseId!),
|
|
enabled: !!courseId && courseId > 0,
|
|
});
|
|
}
|
|
|
|
export function useAiCourseTracks(courseId: number | undefined) {
|
|
return useQuery({
|
|
queryKey: queryKeys.aiCourse.tracks(courseId ?? 0),
|
|
queryFn: () => aiCourseService.getTracks(courseId!),
|
|
enabled: !!courseId && courseId > 0,
|
|
});
|
|
}
|
|
|
|
export function useCreateEnglishCourse() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: AiCourseCreateEnglishRequest) =>
|
|
aiCourseService.createEnglish(data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["ai-course"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useCreateIeltsCourse() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: AiCourseCreateIeltsRequest) =>
|
|
aiCourseService.createIelts(data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["ai-course"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useQualityGate(courseId: number | undefined) {
|
|
return useQuery({
|
|
queryKey: queryKeys.aiCourse.quality(courseId ?? 0),
|
|
queryFn: () => aiCourseService.getQualityGate(courseId!),
|
|
enabled: !!courseId && courseId > 0,
|
|
});
|
|
}
|
|
|
|
export function useApproveQuality() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (courseId: number) => aiCourseService.approveQuality(courseId),
|
|
onSuccess: (_d, courseId) => {
|
|
qc.invalidateQueries({ queryKey: queryKeys.aiCourse.quality(courseId) });
|
|
qc.invalidateQueries({ queryKey: queryKeys.aiCourse.course(courseId) });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useRejectQuality() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ courseId, reason }: { courseId: number; reason: string }) =>
|
|
aiCourseService.rejectQuality(courseId, reason),
|
|
onSuccess: (_d, { courseId }) => {
|
|
qc.invalidateQueries({ queryKey: queryKeys.aiCourse.quality(courseId) });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useEnglishTaxonomy() {
|
|
return useQuery({
|
|
queryKey: queryKeys.aiCourse.englishTaxonomy,
|
|
queryFn: () => aiCourseService.getEnglishTaxonomy(),
|
|
});
|
|
}
|
|
|
|
export function useIeltsValidation(courseId: number | undefined) {
|
|
return useQuery({
|
|
queryKey: queryKeys.aiCourse.ieltsValidation(courseId ?? 0),
|
|
queryFn: () => aiCourseService.getIeltsValidation(courseId!),
|
|
enabled: !!courseId && courseId > 0,
|
|
});
|
|
}
|
|
|
|
export function useSubmitExaminerReview() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
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"] });
|
|
},
|
|
});
|
|
}
|