From 4470e3d20b498c69dd2b3f98f83bec9e5c78dd07 Mon Sep 17 00:00:00 2001
From: Nurfog
Date: Thu, 2 Apr 2026 12:21:45 -0300
Subject: [PATCH] =?UTF-8?q?feat:=20A=C3=B1adir=20selecci=C3=B3n=20de=20can?=
=?UTF-8?q?tidad=20de=20preguntas=20en=20el=20formulario=20de=20plantillas?=
=?UTF-8?q?=20de=20prueba=20y=20mejorar=20la=20gesti=C3=B3n=20de=20edici?=
=?UTF-8?q?=C3=B3n?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/handlers_test_templates.rs | 9 ++++---
web/studio/src/app/test-templates/page.tsx | 23 +++++++++++++---
.../TestTemplates/TestTemplateForm.tsx | 26 ++++++++++++++++++-
.../TestTemplates/TestTemplateManager.tsx | 5 ++--
4 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/services/cms-service/src/handlers_test_templates.rs b/services/cms-service/src/handlers_test_templates.rs
index b0a6d09..9038981 100644
--- a/services/cms-service/src/handlers_test_templates.rs
+++ b/services/cms-service/src/handlers_test_templates.rs
@@ -901,6 +901,7 @@ pub async fn generate_questions_with_rag(
) -> Result>, (StatusCode, String)> {
use common::ai::{self, generate_embedding};
use serde_json::json;
+ let requested_num_questions = payload.num_questions.unwrap_or(5).clamp(1, 20);
let mut mysql_questions: Vec;
@@ -952,7 +953,7 @@ pub async fn generate_questions_with_rag(
.bind(&pgvector)
.bind(org_ctx.id)
.bind(payload.course_id)
- .bind(payload.num_questions.unwrap_or(5) * 3) // Get more for diversity
+ .bind(requested_num_questions * 3) // Get more for diversity
.fetch_all(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Semantic search failed: {}", e)))?;
@@ -997,7 +998,7 @@ pub async fn generate_questions_with_rag(
.bind(org_ctx.id)
.bind(payload.course_id)
.bind(&format!("%{}%", topic))
- .bind(payload.num_questions.unwrap_or(5) * 3)
+ .bind(requested_num_questions * 3)
.fetch_all(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Keyword fallback failed: {}", e)))?;
@@ -1081,7 +1082,7 @@ pub async fn generate_questions_with_rag(
.bind(org_ctx.id)
.bind(payload.course_id)
.bind(&format!("%{}%", topic))
- .bind(payload.num_questions.unwrap_or(5) * 3)
+ .bind(requested_num_questions * 3)
.fetch_all(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Keyword search failed: {}", e)))?;
@@ -1283,7 +1284,7 @@ pub async fn generate_questions_with_rag(
// 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);
+ let num_questions = requested_num_questions;
let requested_question_type = match payload.question_type.as_deref() {
Some("multiple-choice") => "multiple-choice".to_string(),
Some("true-false") => "true-false".to_string(),
diff --git a/web/studio/src/app/test-templates/page.tsx b/web/studio/src/app/test-templates/page.tsx
index 72b0a6d..9581ba1 100644
--- a/web/studio/src/app/test-templates/page.tsx
+++ b/web/studio/src/app/test-templates/page.tsx
@@ -6,18 +6,33 @@ import TestTemplateManager from '@/components/TestTemplates/TestTemplateManager'
import TestTemplateForm from '@/components/TestTemplates/TestTemplateForm';
export default function TestTemplatesPage() {
- const [view, setView] = useState<'list' | 'create'>('list');
+ const [view, setView] = useState<'list' | 'create' | 'edit'>('list');
+ const [editingTemplateId, setEditingTemplateId] = useState(null);
return (
{view === 'list' ? (
setView('create')}
+ onCreateTemplate={() => {
+ setEditingTemplateId(null);
+ setView('create');
+ }}
+ onEditTemplate={(template) => {
+ setEditingTemplateId(template.id);
+ setView('edit');
+ }}
/>
) : (
setView('list')}
- onCancel={() => setView('list')}
+ templateId={view === 'edit' ? editingTemplateId || undefined : undefined}
+ onSuccess={() => {
+ setEditingTemplateId(null);
+ setView('list');
+ }}
+ onCancel={() => {
+ setEditingTemplateId(null);
+ setView('list');
+ }}
/>
)}
diff --git a/web/studio/src/components/TestTemplates/TestTemplateForm.tsx b/web/studio/src/components/TestTemplates/TestTemplateForm.tsx
index a4d4f24..2d472c4 100644
--- a/web/studio/src/components/TestTemplates/TestTemplateForm.tsx
+++ b/web/studio/src/components/TestTemplates/TestTemplateForm.tsx
@@ -53,6 +53,7 @@ export default function TestTemplateForm({ onSuccess, onCancel }: TestTemplateFo
const [expandedQuestion, setExpandedQuestion] = useState(null);
const [aiContext, setAiContext] = useState('');
const [aiQuestionType, setAiQuestionType] = useState('multiple-choice');
+ const [aiQuestionCount, setAiQuestionCount] = useState(5);
// MySQL course selection state
const [mysqlPlans, setMysqlPlans] = useState([]);
@@ -249,7 +250,7 @@ export default function TestTemplateForm({ onSuccess, onCancel }: TestTemplateFo
},
body: JSON.stringify({
topic: aiContext,
- num_questions: 5,
+ num_questions: aiQuestionCount,
question_type: aiQuestionType,
}),
});
@@ -577,6 +578,26 @@ export default function TestTemplateForm({ onSuccess, onCancel }: TestTemplateFo
+
+
+ Puedes elegir entre 1 y 20 preguntas por generacion.
+
{/* Sections */}
diff --git a/web/studio/src/components/TestTemplates/TestTemplateManager.tsx b/web/studio/src/components/TestTemplates/TestTemplateManager.tsx
index 7c42e87..28c0863 100644
--- a/web/studio/src/components/TestTemplates/TestTemplateManager.tsx
+++ b/web/studio/src/components/TestTemplates/TestTemplateManager.tsx
@@ -16,9 +16,10 @@ import { Plus, Search, Filter, Edit2, Trash2, Eye, Copy, BookOpen, Clock, Target
interface TestTemplateManagerProps {
onSelectTemplate?: (template: TestTemplate) => void;
onCreateTemplate?: () => void;
+ onEditTemplate?: (template: TestTemplate) => void;
}
-export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate }: TestTemplateManagerProps) {
+export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate, onEditTemplate }: TestTemplateManagerProps) {
const [templates, setTemplates] = useState([]);
const [loading, setLoading] = useState(true);
@@ -346,7 +347,7 @@ export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate