diff --git a/services/cms-service/src/handlers_test_templates.rs b/services/cms-service/src/handlers_test_templates.rs index 22cb04d..8a32b90 100644 --- a/services/cms-service/src/handlers_test_templates.rs +++ b/services/cms-service/src/handlers_test_templates.rs @@ -615,9 +615,10 @@ pub struct ApplyTemplatePayload { // ==================== RAG Question Generation ==================== -/// POST /api/test-templates/generate-with-rag - Generate questions using RAG from MySQL question bank +/// POST /test-templates/generate-with-rag - Generate questions using RAG from MySQL question bank pub async fn generate_questions_with_rag( Org(org_ctx): Org, + claims: Claims, State(pool): State, Json(payload): Json, ) -> Result>, (StatusCode, String)> { @@ -701,6 +702,10 @@ pub async fn generate_questions_with_rag( tracing::info!("Calling Ollama at {} with model {}", url, model); + // Save topic for later use + let topic = payload.topic.clone().unwrap_or_else(|| "English grammar".to_string()); + let num_questions = payload.num_questions.unwrap_or(5); + // Simplified system prompt for better performance on slower machines let system_prompt = format!( r#"You are an English Teacher creating quiz questions. @@ -725,8 +730,8 @@ pub async fn generate_questions_with_rag( Skills: reading, listening, speaking, writing. Distribute across all 4."#, rag_context, - payload.num_questions.unwrap_or(5), - payload.topic.unwrap_or_else(|| "English grammar".to_string()) + num_questions, + topic ); tracing::debug!("System prompt length: {} chars", system_prompt.len()); @@ -811,9 +816,59 @@ pub async fn generate_questions_with_rag( if generated_questions.is_empty() { return Err((StatusCode::INTERNAL_SERVER_ERROR, "AI failed to generate questions".to_string())); } - - tracing::info!("Generated {} questions using RAG from MySQL bank", generated_questions.len()); - + + // Save generated questions to question bank + let mut saved_count = 0; + for question in &generated_questions { + let question_type = match question.question_type.as_str() { + "true-false" => common::models::QuestionBankType::TrueFalse, + "short-answer" => common::models::QuestionBankType::ShortAnswer, + "essay" => common::models::QuestionBankType::Essay, + "matching" => common::models::QuestionBankType::Matching, + "ordering" => common::models::QuestionBankType::Ordering, + _ => common::models::QuestionBankType::MultipleChoice, + }; + + let result = sqlx::query( + r#" + INSERT INTO question_bank ( + organization_id, created_by, question_text, question_type, + options, correct_answer, explanation, points, difficulty, + source, source_metadata, is_active + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, true) + "# + ) + .bind(org_ctx.id) + .bind(claims.sub) + .bind(&question.question_text) + .bind(&question_type) + .bind(&question.options) + .bind(&question.correct_answer) + .bind(&question.explanation) + .bind(question.points) + .bind("medium") + .bind("rag-ai") + .bind(&json!({ + "generated_by": "rag-ai", + "source": "mysql-bank", + "topic": topic, + "generated_at": chrono::Utc::now().to_rfc3339(), + })) + .execute(&pool) + .await; + + if result.is_ok() { + saved_count += 1; + } + } + + tracing::info!( + "Generated {} questions using RAG from MySQL bank, saved {} to question bank", + generated_questions.len(), + saved_count + ); + Ok(Json(generated_questions)) } diff --git a/web/studio/src/components/TestTemplates/TestTemplateForm.tsx b/web/studio/src/components/TestTemplates/TestTemplateForm.tsx index cd843c6..02b3536 100644 --- a/web/studio/src/components/TestTemplates/TestTemplateForm.tsx +++ b/web/studio/src/components/TestTemplates/TestTemplateForm.tsx @@ -328,14 +328,14 @@ export default function TestTemplateForm({ onSuccess, onCancel }: TestTemplateFo className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" > - - + + - - + + - - + + diff --git a/web/studio/src/components/TestTemplates/TestTemplateManager.tsx b/web/studio/src/components/TestTemplates/TestTemplateManager.tsx index 5b10865..75450df 100644 --- a/web/studio/src/components/TestTemplates/TestTemplateManager.tsx +++ b/web/studio/src/components/TestTemplates/TestTemplateManager.tsx @@ -90,14 +90,14 @@ export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate const getLevelLabel = (level: CourseLevel) => { const labels: Record = { beginner: 'Beginner', - beginner_1: 'Beginner 1', - beginner_2: 'Beginner 2', + beginner1: 'Beginner 1', + beginner2: 'Beginner 2', intermediate: 'Intermediate', - intermediate_1: 'Intermediate 1', - intermediate_2: 'Intermediate 2', + intermediate1: 'Intermediate 1', + intermediate2: 'Intermediate 2', advanced: 'Advanced', - advanced_1: 'Advanced 1', - advanced_2: 'Advanced 2', + advanced1: 'Advanced 1', + advanced2: 'Advanced 2', }; return labels[level] || level; }; @@ -185,14 +185,14 @@ export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate > - - + + - - + + - - + +
diff --git a/web/studio/src/lib/api.ts b/web/studio/src/lib/api.ts index aa9bcf0..ec88f9b 100644 --- a/web/studio/src/lib/api.ts +++ b/web/studio/src/lib/api.ts @@ -1135,7 +1135,7 @@ export interface BackgroundTask { // ==================== Test Templates ==================== -export type CourseLevel = 'beginner' | 'beginner_1' | 'beginner_2' | 'intermediate' | 'intermediate_1' | 'intermediate_2' | 'advanced' | 'advanced_1' | 'advanced_2'; +export type CourseLevel = 'beginner' | 'beginner1' | 'beginner2' | 'intermediate' | 'intermediate1' | 'intermediate2' | 'advanced' | 'advanced1' | 'advanced2'; export type CourseType = 'intensive' | 'regular'; export type TestType = 'CA' | 'MWT' | 'MOT' | 'FOT' | 'FWT'; export type QuestionType = 'multiple-choice' | 'true-false' | 'short-answer' | 'essay' | 'matching' | 'ordering';