feat: Add AI-powered memory match game generation and remove AI image generation from the lesson editor.
This commit is contained in:
+14
-3
@@ -227,7 +227,18 @@
|
|||||||
|
|
||||||
**Estado Actual**: La plataforma cuenta con un motor de IA avanzado, gestión multi-tenant completa, tutoría inteligente con memoria histórica, una **interfaz 100% responsiva**, flujos de autenticación diferenciados, **sistema de foros de discusión funcional**, **gestión de anuncios segmentados**, **monetización integrada con Mercado Pago**, **Inscripción Masiva de Usuarios**, **Exportación Avanzada de Calificaciones**, **Librerías de Contenido reutilizables**, **Sistema de Rúbricas Avanzado**, **Secuencias de Aprendizaje**, **Gestión de Equipos Docentes**, **Vista Previa de Cursos**, **Dashboard de Progreso Estudiantil**, **Sistema de Marcadores**, **Biblioteca Global de Activos**, **Interoperabilidad LTI 1.3**, **Analíticas Predictivas**, **Integración de Jitsi**, **Portafolios con Perfiles Públicos** y **Landing Pages de Cursos (Marketing) automatizadas**.
|
**Estado Actual**: La plataforma cuenta con un motor de IA avanzado, gestión multi-tenant completa, tutoría inteligente con memoria histórica, una **interfaz 100% responsiva**, flujos de autenticación diferenciados, **sistema de foros de discusión funcional**, **gestión de anuncios segmentados**, **monetización integrada con Mercado Pago**, **Inscripción Masiva de Usuarios**, **Exportación Avanzada de Calificaciones**, **Librerías de Contenido reutilizables**, **Sistema de Rúbricas Avanzado**, **Secuencias de Aprendizaje**, **Gestión de Equipos Docentes**, **Vista Previa de Cursos**, **Dashboard de Progreso Estudiantil**, **Sistema de Marcadores**, **Biblioteca Global de Activos**, **Interoperabilidad LTI 1.3**, **Analíticas Predictivas**, **Integración de Jitsi**, **Portafolios con Perfiles Públicos** y **Landing Pages de Cursos (Marketing) automatizadas**.
|
||||||
|
|
||||||
|
## Fase 20: IA Generativa Avanzada (En Diseño) 🧠
|
||||||
|
- [ ] **Generación de Juegos de Memoria Conceptuales**: Creación automática de parejas (concepto/definición) a partir de transcripciones.
|
||||||
|
- [ ] **Simulaciones de Rol y Diálogos Ramificados**: Motor de escenarios interactivos con respuestas dinámicas de la IA.
|
||||||
|
- [ ] **Auto-Hotspots Pedagógicos**: Identificación automática de puntos de interés en imágenes con descripciones técnicas.
|
||||||
|
- [ ] **Diagramas de Mermaid Dinámicos**: Visualización automática de procesos y mapas mentales a partir del contenido de la lección.
|
||||||
|
- [ ] **Laboratorios de Código con Hints de IA**: Generación de desafíos de programación con pistas contextuales basadas en errores.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Estado Actual**: La plataforma cuenta con un motor de IA avanzado, gestión multi-tenant completa, tutoría inteligente con memoria histórica, una **interfaz 100% responsiva**, flujos de autenticación diferenciados, **sistema de foros de discusión funcional**, **gestión de anuncios segmentados**, **monetización integrada con Mercado Pago**, **Inscripción Masiva de Usuarios**, **Exportación Avanzada de Calificaciones**, **Librerías de Contenido reutilizables**, **Sistema de Rúbricas Avanzado**, **Secuencias de Aprendizaje**, **Gestión de Equipos Docentes**, **Vista Previa de Cursos**, **Dashboard de Progreso Estudiantil**, **Sistema de Marcadores**, **Biblioteca Global de Activos**, **Interoperabilidad LTI 1.3**, **Analíticas Predictivas**, **Integración de Jitsi**, **Portafolios con Perfiles Públicos** y **Landing Pages de Cursos (Marketing) automatizadas**.
|
||||||
|
|
||||||
**Próximas Prioridades**:
|
**Próximas Prioridades**:
|
||||||
1. **Accesibilidad Universal**: Auditoría y ajustes de contraste para cumplimiento WCAG 2.1.
|
1. **Conceptual Memory Match (IA)**: Implementar el primer generador de actividades interactivas.
|
||||||
2. **Integraciones Empresariales**: Conectividad con HRIS y ERPs externos.
|
2. **Accesibilidad Universal**: Auditoría y ajustes de contraste para cumplimiento WCAG 2.1.
|
||||||
3. **IA Generativa Avanzada**: Generación automática de contenido interactivo adicional.
|
3. **Integraciones Empresariales**: Conectividad con HRIS y ERPs externos.
|
||||||
|
|||||||
@@ -1204,7 +1204,15 @@ pub async fn generate_quiz(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut system_prompt = "You are an expert English Teacher. Generate 3 questions based on the lesson content to test the student's understanding of English grammar, vocabulary, or comprehension. Instructions can be in Spanish or English. Return ONLY a JSON object with a field 'blocks' which is an array of content blocks. Each block in the array must follow this exact structure: { \"id\": \"string-uuid\", \"type\": \"quiz\", \"title\": \"Quiz: Concept Check\", \"quiz_data\": { \"questions\": [ { \"id\": \"q-string\", \"type\": \"multiple-choice\", \"question\": \"String\", \"options\": [\"Option 1\", \"Option 2\", \"Option 3\", \"Option 4\"], \"correct\": [0], \"explanation\": \"Explain why the answer is correct.\" } ] } }. Important: 'correct' MUST be an array of integers.".to_string();
|
let mut system_prompt = if let Some(qtype) = &quiz_req.quiz_type {
|
||||||
|
if qtype == "memory-match" {
|
||||||
|
"You are an expert English Teacher. Generate a Memory Match game (Memory match concepts) based on the lesson content. Extract 6 important concepts or vocabulary terms and their corresponding definitions or translations. Return ONLY a JSON object with a field 'blocks' which is an array. The array must contain ONE block with this structure: { \"id\": \"string-uuid\", \"type\": \"memory-match\", \"title\": \"Memory Match: Concept Review\", \"pairs\": [ { \"id\": \"1\", \"left\": \"Concept 1\", \"right\": \"Definition/Match 1\" }, { \"id\": \"2\", \"left\": \"Concept 2\", \"right\": \"Definition/Match 2\" } ] }. Provide 6 pairs in total.".to_string()
|
||||||
|
} else {
|
||||||
|
"You are an expert English Teacher. Generate 3 questions based on the lesson content to test the student's understanding of English grammar, vocabulary, or comprehension. Instructions can be in Spanish or English. Return ONLY a JSON object with a field 'blocks' which is an array of content blocks. Each block in the array must follow this exact structure: { \"id\": \"string-uuid\", \"type\": \"quiz\", \"title\": \"Quiz: Concept Check\", \"quiz_data\": { \"questions\": [ { \"id\": \"q-string\", \"type\": \"multiple-choice\", \"question\": \"String\", \"options\": [\"Option 1\", \"Option 2\", \"Option 3\", \"Option 4\"], \"correct\": [0], \"explanation\": \"Explain why the answer is correct.\" } ] } }. Important: 'correct' MUST be an array of integers.".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"You are an expert English Teacher. Generate 3 questions based on the lesson content to test the student's understanding of English grammar, vocabulary, or comprehension. Instructions can be in Spanish or English. Return ONLY a JSON object with a field 'blocks' which is an array of content blocks. Each block in the array must follow this exact structure: { \"id\": \"string-uuid\", \"type\": \"quiz\", \"title\": \"Quiz: Concept Check\", \"quiz_data\": { \"questions\": [ { \"id\": \"q-string\", \"type\": \"multiple-choice\", \"question\": \"String\", \"options\": [\"Option 1\", \"Option 2\", \"Option 3\", \"Option 4\"], \"correct\": [0], \"explanation\": \"Explain why the answer is correct.\" } ] } }. Important: 'correct' MUST be an array of integers.".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(ctx) = &quiz_req.context {
|
if let Some(ctx) = &quiz_req.context {
|
||||||
if !ctx.is_empty() {
|
if !ctx.is_empty() {
|
||||||
@@ -1213,7 +1221,7 @@ pub async fn generate_quiz(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(qtype) = &quiz_req.quiz_type {
|
if let Some(qtype) = &quiz_req.quiz_type {
|
||||||
if !qtype.is_empty() {
|
if !qtype.is_empty() && qtype != "memory-match" {
|
||||||
system_prompt.push_str(&format!(" Question Type to use: {}. If the type is 'multiple-choice', follow the structure above. If it's something else, adapt the block 'type' (e.g., 'true-false') accordingly, but keep it within the 'blocks' array.", qtype));
|
system_prompt.push_str(&format!(" Question Type to use: {}. If the type is 'multiple-choice', follow the structure above. If it's something else, adapt the block 'type' (e.g., 'true-false') accordingly, but keep it within the 'blocks' array.", qtype));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import {
|
|||||||
Brain,
|
Brain,
|
||||||
Library,
|
Library,
|
||||||
BookMarked,
|
BookMarked,
|
||||||
ArrowLeft,
|
ArrowLeft
|
||||||
ImageIcon
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import DescriptionBlock from "@/components/blocks/DescriptionBlock";
|
import DescriptionBlock from "@/components/blocks/DescriptionBlock";
|
||||||
import MediaBlock from "@/components/blocks/MediaBlock";
|
import MediaBlock from "@/components/blocks/MediaBlock";
|
||||||
@@ -80,42 +79,10 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
|||||||
const [isAIQuizModalOpen, setIsAIQuizModalOpen] = useState(false);
|
const [isAIQuizModalOpen, setIsAIQuizModalOpen] = useState(false);
|
||||||
const [aiQuizContext, setAiQuizContext] = useState("");
|
const [aiQuizContext, setAiQuizContext] = useState("");
|
||||||
const [aiQuizType, setAiQuizType] = useState("multiple-choice");
|
const [aiQuizType, setAiQuizType] = useState("multiple-choice");
|
||||||
const [aiImagePrompt, setAiImagePrompt] = useState("");
|
|
||||||
const [isGeneratingImage, setIsGeneratingImage] = useState(false);
|
|
||||||
|
|
||||||
const [editValue, setEditValue] = useState("");
|
const [editValue, setEditValue] = useState("");
|
||||||
|
|
||||||
|
|
||||||
// Polling for AI status
|
|
||||||
useEffect(() => {
|
|
||||||
let interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
if (lesson && (
|
|
||||||
lesson.video_generation_status === 'queued' ||
|
|
||||||
lesson.video_generation_status === 'processing'
|
|
||||||
)) {
|
|
||||||
interval = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const updated = await cmsApi.getLesson(params.lessonId);
|
|
||||||
setLesson(updated);
|
|
||||||
|
|
||||||
// If it finished, update local states
|
|
||||||
if (updated.transcription_status === 'completed') {
|
|
||||||
if (updated.transcription) {
|
|
||||||
// Automatically update summary if available? No, wait for manual trigger or auto-trigger?
|
|
||||||
// For now just update lesson
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Polling failed", err);
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (interval) clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [lesson, lesson?.transcription_status, params.lessonId]);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -369,31 +336,6 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenerateImage = async () => {
|
|
||||||
if (!lesson) return;
|
|
||||||
setIsGeneratingImage(true);
|
|
||||||
try {
|
|
||||||
// Option 2: Use lesson content (blocks) as script if no prompt
|
|
||||||
const scriptFromBlocks = blocks
|
|
||||||
.map(b => b.content || b.description || b.title || "")
|
|
||||||
.filter(txt => txt.trim().length > 0)
|
|
||||||
.join(". ");
|
|
||||||
|
|
||||||
// Option 3: Prioritize manual prompt, then script
|
|
||||||
const finalPrompt = aiImagePrompt.trim() || scriptFromBlocks;
|
|
||||||
|
|
||||||
const updated = await cmsApi.generateImage(lesson.id, {
|
|
||||||
prompt: finalPrompt
|
|
||||||
});
|
|
||||||
setLesson(updated);
|
|
||||||
setAiImagePrompt("");
|
|
||||||
alert("Generación de imagen iniciada con el prompt/guion detectado.");
|
|
||||||
} catch (err: any) {
|
|
||||||
alert(err.message || "Failed to initiate image generation.");
|
|
||||||
} finally {
|
|
||||||
setIsGeneratingImage(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) return <div className="py-20 text-center text-gray-500 animate-pulse font-medium">Initializing Activity Builder...</div>;
|
if (loading) return <div className="py-20 text-center text-gray-500 animate-pulse font-medium">Initializing Activity Builder...</div>;
|
||||||
if (!lesson) return <div className="py-20 text-center text-red-400">Activity not found.</div>;
|
if (!lesson) return <div className="py-20 text-center text-red-400">Activity not found.</div>;
|
||||||
@@ -906,34 +848,6 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="col-span-full space-y-3 mt-4">
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase tracking-wider flex items-center gap-2">
|
|
||||||
<ImageIcon size={14} className="text-amber-500" />
|
|
||||||
Custom Image Prompt (Optional)
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
value={aiImagePrompt}
|
|
||||||
onChange={(e) => setAiImagePrompt(e.target.value)}
|
|
||||||
placeholder="Describe what you want to see in the image, or leave blank to use the lesson script..."
|
|
||||||
className="w-full bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-2xl p-4 text-sm focus:ring-2 focus:ring-amber-500/20 focus:border-amber-500 outline-none transition-all min-h-[100px] text-slate-700 dark:text-gray-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleGenerateImage}
|
|
||||||
disabled={isGeneratingImage || lesson.video_generation_status === 'queued' || lesson.video_generation_status === 'processing'}
|
|
||||||
className={`group/btn p-8 border rounded-[2rem] transition-all text-left flex flex-col gap-4 shadow-sm relative overflow-hidden ${isGeneratingImage || lesson.video_generation_status === 'queued' || lesson.video_generation_status === 'processing' ? 'bg-amber-50 dark:bg-amber-500/20 border-amber-200 dark:border-amber-500/50 text-amber-600 dark:text-amber-300 animate-pulse' : 'bg-white dark:bg-white/5 border-slate-100 dark:border-white/10 text-slate-400 dark:text-amber-400 hover:border-amber-500/50 hover:bg-amber-50 hover:text-amber-600 dark:hover:bg-amber-500/10'}`}
|
|
||||||
>
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-amber-100 dark:bg-amber-500/20 flex items-center justify-center text-amber-600 dark:text-amber-400 shadow-sm group-hover/btn:scale-110 transition-transform">
|
|
||||||
{(isGeneratingImage || lesson.video_generation_status === 'queued' || lesson.video_generation_status === 'processing') ? '⏳' : <ImageIcon />}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-[10px] font-black uppercase tracking-[0.2em] opacity-80">Generative AI</div>
|
|
||||||
<div className="font-black text-xl tracking-tight">
|
|
||||||
{(isGeneratingImage || lesson.video_generation_status === 'queued' || lesson.video_generation_status === 'processing') ? 'Generating Image...' : 'Generate AI Image'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -1282,6 +1196,7 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
|||||||
<option value="true-false">Binary Validation (T/F)</option>
|
<option value="true-false">Binary Validation (T/F)</option>
|
||||||
<option value="vocabulary">Lexical Focus / Vocab</option>
|
<option value="vocabulary">Lexical Focus / Vocab</option>
|
||||||
<option value="grammar">Structural / Grammar Focus</option>
|
<option value="grammar">Structural / Grammar Focus</option>
|
||||||
|
<option value="memory-match">Conceptual Memory Match</option>
|
||||||
</select>
|
</select>
|
||||||
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400">
|
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400">
|
||||||
<ChevronDown size={18} />
|
<ChevronDown size={18} />
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export interface Lesson {
|
|||||||
cues?: { start: number; end: number; text: string }[];
|
cues?: { start: number; end: number; text: string }[];
|
||||||
} | null;
|
} | null;
|
||||||
transcription_status?: 'idle' | 'queued' | 'processing' | 'completed' | 'failed';
|
transcription_status?: 'idle' | 'queued' | 'processing' | 'completed' | 'failed';
|
||||||
video_generation_status?: 'idle' | 'queued' | 'processing' | 'completed' | 'failed';
|
|
||||||
is_previewable: boolean;
|
is_previewable: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user