feat: Implement AI-driven lesson summaries, automate quiz generation, add gamification base, and introduce Studio organization management.
This commit is contained in:
@@ -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">
|
||||
"{summary}"
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user