feat: Implement AI-driven lesson summaries, automate quiz generation, add gamification base, and introduce Studio organization management.

This commit is contained in:
2025-12-26 14:58:58 -03:00
parent 2378f616aa
commit e98a16d860
26 changed files with 791 additions and 82 deletions
@@ -19,6 +19,9 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
// Activity State (Blocks)
const [blocks, setBlocks] = useState<Block[]>([]);
const [summary, setSummary] = useState<string>("");
const [isGeneratingSummary, setIsGeneratingSummary] = useState(false);
const [isGeneratingQuiz, setIsGeneratingQuiz] = useState(false);
const [gradingCategories, setGradingCategories] = useState<GradingCategory[]>([]);
const [isGraded, setIsGraded] = useState(false);
const [selectedCategoryId, setSelectedCategoryId] = useState<string | "">("");
@@ -31,7 +34,8 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
// Use cmsApi for consistency
const lessonData = await cmsApi.getLesson(params.lessonId);
setLesson(lessonData);
setIsGraded(lessonData.is_graded);
setSummary(lessonData.summary || "");
setIsGraded(lessonData.is_graded || false);
setSelectedCategoryId(lessonData.grading_category_id || "");
setMaxAttempts(lessonData.max_attempts);
setAllowRetry(lessonData.allow_retry);
@@ -66,6 +70,7 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
try {
const updated = await cmsApi.updateLesson(lesson.id, {
metadata: { ...lesson.metadata, blocks },
summary,
is_graded: isGraded,
grading_category_id: selectedCategoryId || null,
max_attempts: maxAttempts,
@@ -112,6 +117,32 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
setBlocks(newBlocks);
};
const handleSummarize = async () => {
if (!lesson) return;
setIsGeneratingSummary(true);
try {
const updated = await cmsApi.summarizeLesson(lesson.id);
setSummary(updated.summary || "");
} catch {
alert("Failed to generate summary.");
} finally {
setIsGeneratingSummary(false);
}
};
const handleGenerateQuiz = async () => {
if (!lesson) return;
setIsGeneratingQuiz(true);
try {
const newBlocks = await cmsApi.generateQuiz(lesson.id);
setBlocks([...blocks, ...newBlocks]);
} catch {
alert("Failed to generate quiz.");
} finally {
setIsGeneratingQuiz(false);
}
};
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>;
@@ -228,6 +259,43 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
</div>
)}
{/* AI Summary Section */}
{(summary || editMode) && (
<div className="bg-gradient-to-br from-indigo-500/10 to-blue-500/10 border border-indigo-500/20 rounded-3xl p-8 space-y-6 animate-in fade-in duration-700">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-2xl"></span>
<div>
<h3 className="text-xl font-bold font-black italic tracking-tight">AI Lesson Summary</h3>
<p className="text-xs text-gray-400 mt-1 uppercase tracking-widest font-bold">Key insights generated by intelligence</p>
</div>
</div>
{editMode && (
<button
onClick={handleSummarize}
disabled={isGeneratingSummary}
className="px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 text-[10px] font-black uppercase tracking-widest rounded-xl border border-blue-500/20 transition-all flex items-center gap-2"
>
{isGeneratingSummary ? "Generating..." : "Regenerate Summary"}
</button>
)}
</div>
{editMode ? (
<textarea
value={summary}
onChange={(e) => setSummary(e.target.value)}
placeholder="A concise summary of the lesson content..."
className="w-full bg-white/5 border border-white/10 rounded-2xl p-6 text-sm text-gray-300 focus:outline-none focus:border-blue-500/50 min-h-[120px] transition-all"
/>
) : (
<div className="text-sm text-gray-400 leading-relaxed italic border-l-2 border-indigo-500/30 pl-6 py-2">
&quot;{summary}&quot;
</div>
)}
</div>
)}
<div className="space-y-16">
{blocks.map((block, index) => (
<div key={block.id} className="relative group/block animate-in fade-in slide-in-from-bottom-4 duration-500" style={{ animationDelay: `${index * 100}ms` }}>
@@ -387,6 +455,17 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
<span className="text-2xl group-hover:scale-110 transition-transform">💬</span>
<span className="text-[10px] font-bold uppercase tracking-widest text-gray-400">Short</span>
</button>
<div className="w-px h-12 bg-white/5"></div>
<button
onClick={handleGenerateQuiz}
disabled={isGeneratingQuiz}
className="flex flex-col items-center gap-2 p-6 bg-gradient-to-b from-indigo-500/20 to-blue-500/20 border border-indigo-500/30 hover:border-indigo-500/60 rounded-3xl transition-all group w-36"
>
<span className="text-2xl group-hover:scale-110 transition-transform">{isGeneratingQuiz ? '⏳' : '✨'}</span>
<span className="text-[10px] font-black uppercase tracking-widest text-indigo-400">{isGeneratingQuiz ? 'Building...' : 'AI Builder'}</span>
</button>
</div>
</div>
</div>