| import React, { useState, useEffect } from "react"; | |
| import { User } from "@/entities/User"; | |
| import { InvokeLLM } from "@/integrations/Core"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | |
| import { Alert, AlertDescription } from "@/components/ui/alert"; | |
| import { | |
| BookOpen, | |
| FileText, | |
| Lightbulb, | |
| RefreshCw, | |
| Download, | |
| Sparkles, | |
| GraduationCap, | |
| Target, | |
| Brain, | |
| CheckCircle | |
| } from "lucide-react"; | |
| const TOPICS = [ | |
| { value: "organic_chemistry", label: "Organic Chemistry", icon: "🧪", color: "bg-green-100 text-green-800" }, | |
| { value: "inorganic_chemistry", label: "Inorganic Chemistry", icon: "⚛️", color: "bg-blue-100 text-blue-800" }, | |
| { value: "physical_chemistry", label: "Physical Chemistry", icon: "🔬", color: "bg-purple-100 text-purple-800" }, | |
| { value: "analytical_chemistry", label: "Analytical Chemistry", icon: "📊", color: "bg-orange-100 text-orange-800" }, | |
| { value: "biochemistry", label: "Biochemistry", icon: "🧬", color: "bg-pink-100 text-pink-800" }, | |
| { value: "quantum_chemistry", label: "Quantum Chemistry", icon: "⚡", color: "bg-indigo-100 text-indigo-800" }, | |
| { value: "materials_science", label: "Materials Science", icon: "🏗️", color: "bg-cyan-100 text-cyan-800" } | |
| ]; | |
| const MATERIAL_TYPES = [ | |
| { value: "study_guide", label: "Study Guide", icon: BookOpen, description: "Comprehensive topic overview" }, | |
| { value: "flashcards", label: "Flashcards", icon: Brain, description: "Key terms and definitions" }, | |
| { value: "practice_problems", label: "Practice Problems", icon: Target, description: "Problems with solutions" }, | |
| { value: "concept_map", label: "Concept Map", icon: Lightbulb, description: "Visual concept connections" } | |
| ]; | |
| export default function StudyMaterials() { | |
| const [user, setUser] = useState(null); | |
| const [selectedTopic, setSelectedTopic] = useState(""); | |
| const [selectedType, setSelectedType] = useState(""); | |
| const [generatedMaterial, setGeneratedMaterial] = useState(null); | |
| const [isGenerating, setIsGenerating] = useState(false); | |
| useEffect(() => { | |
| loadUser(); | |
| }, []); | |
| const loadUser = async () => { | |
| try { | |
| const userData = await User.me(); | |
| setUser(userData); | |
| } catch (error) { | |
| await User.login(); | |
| } | |
| }; | |
| const generateMaterial = async () => { | |
| if (!selectedTopic || !selectedType) return; | |
| setIsGenerating(true); | |
| setGeneratedMaterial(null); | |
| try { | |
| const topic = TOPICS.find(t => t.value === selectedTopic); | |
| const materialType = MATERIAL_TYPES.find(t => t.value === selectedType); | |
| let prompt = ""; | |
| let schema = {}; | |
| switch (selectedType) { | |
| case "study_guide": | |
| prompt = `Create a comprehensive study guide for ${topic.label} suitable for a ${user?.learning_level || 'beginner'} level student. Include: | |
| - Overview and importance | |
| - Key concepts with clear explanations | |
| - Important formulas or reactions | |
| - Real-world applications | |
| - Common misconceptions | |
| - Study tips and memory aids`; | |
| schema = { | |
| type: "object", | |
| properties: { | |
| title: { type: "string" }, | |
| overview: { type: "string" }, | |
| key_concepts: { | |
| type: "array", | |
| items: { | |
| type: "object", | |
| properties: { | |
| concept: { type: "string" }, | |
| explanation: { type: "string" } | |
| } | |
| } | |
| }, | |
| formulas: { type: "array", items: { type: "string" } }, | |
| applications: { type: "array", items: { type: "string" } }, | |
| misconceptions: { type: "array", items: { type: "string" } }, | |
| study_tips: { type: "array", items: { type: "string" } } | |
| } | |
| }; | |
| break; | |
| case "flashcards": | |
| prompt = `Create 15-20 flashcards for ${topic.label} at ${user?.learning_level || 'beginner'} level. Each flashcard should have: | |
| - A clear, concise question or term | |
| - A comprehensive answer or definition | |
| - Include key formulas, reactions, or concepts`; | |
| schema = { | |
| type: "object", | |
| properties: { | |
| title: { type: "string" }, | |
| cards: { | |
| type: "array", | |
| items: { | |
| type: "object", | |
| properties: { | |
| front: { type: "string" }, | |
| back: { type: "string" } | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| break; | |
| case "practice_problems": | |
| prompt = `Create 8-10 practice problems for ${topic.label} at ${user?.learning_level || 'beginner'} level. Include: | |
| - Varied difficulty within the level | |
| - Step-by-step solutions | |
| - Brief explanations of key concepts used`; | |
| schema = { | |
| type: "object", | |
| properties: { | |
| title: { type: "string" }, | |
| problems: { | |
| type: "array", | |
| items: { | |
| type: "object", | |
| properties: { | |
| question: { type: "string" }, | |
| solution: { type: "string" }, | |
| concepts_used: { type: "array", items: { type: "string" } } | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| break; | |
| case "concept_map": | |
| prompt = `Create a concept map structure for ${topic.label} at ${user?.learning_level || 'beginner'} level showing: | |
| - Main concepts and their relationships | |
| - Hierarchical organization | |
| - Key connections between ideas | |
| - Brief descriptions for each concept`; | |
| schema = { | |
| type: "object", | |
| properties: { | |
| title: { type: "string" }, | |
| main_concept: { type: "string" }, | |
| concepts: { | |
| type: "array", | |
| items: { | |
| type: "object", | |
| properties: { | |
| name: { type: "string" }, | |
| description: { type: "string" }, | |
| level: { type: "number" }, | |
| connections: { type: "array", items: { type: "string" } } | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| break; | |
| } | |
| const result = await InvokeLLM({ | |
| prompt, | |
| response_json_schema: schema | |
| }); | |
| setGeneratedMaterial({ | |
| type: selectedType, | |
| topic: selectedTopic, | |
| data: result | |
| }); | |
| } catch (error) { | |
| console.error("Error generating material:", error); | |
| } | |
| setIsGenerating(false); | |
| }; | |
| const downloadMaterial = () => { | |
| if (!generatedMaterial) return; | |
| let content = ""; | |
| const { data } = generatedMaterial; | |
| switch (generatedMaterial.type) { | |
| case "study_guide": | |
| content = `${data.title}\n\n`; | |
| content += `Overview:\n${data.overview}\n\n`; | |
| if (data.key_concepts) { | |
| content += "Key Concepts:\n"; | |
| data.key_concepts.forEach(concept => { | |
| content += `- ${concept.concept}: ${concept.explanation}\n`; | |
| }); | |
| content += "\n"; | |
| } | |
| if (data.formulas && data.formulas.length > 0) { | |
| content += "Important Formulas/Reactions:\n"; | |
| data.formulas.forEach(formula => { | |
| content += `- ${formula}\n`; | |
| }); | |
| content += "\n"; | |
| } | |
| if (data.applications && data.applications.length > 0) { | |
| content += "Real-world Applications:\n"; | |
| data.applications.forEach(app => { | |
| content += `- ${app}\n`; | |
| }); | |
| content += "\n"; | |
| } | |
| if (data.misconceptions && data.misconceptions.length > 0) { | |
| content += "Common Misconceptions:\n"; | |
| data.misconceptions.forEach(misconception => { | |
| content += `- ${misconception}\n`; | |
| }); | |
| content += "\n"; | |
| } | |
| if (data.study_tips && data.study_tips.length > 0) { | |
| content += "Study Tips:\n"; | |
| data.study_tips.forEach(tip => { | |
| content += `- ${tip}\n`; | |
| }); | |
| content += "\n"; | |
| } | |
| break; | |
| case "flashcards": | |
| content = `${data.title}\n\nFlashcards:\n\n`; | |
| data.cards?.forEach((card, index) => { | |
| content += `Card ${index + 1}:\nQ: ${card.front}\nA: ${card.back}\n\n`; | |
| }); | |
| break; | |
| case "practice_problems": | |
| content = `${data.title}\n\nPractice Problems:\n\n`; | |
| data.problems?.forEach((problem, index) => { | |
| content += `Problem ${index + 1}:\nQuestion: ${problem.question}\nSolution: ${problem.solution}\nConcepts Used: ${problem.concepts_used?.join(', ')}\n\n`; | |
| }); | |
| break; | |
| case "concept_map": | |
| content = `${data.title}\n\nMain Concept: ${data.main_concept}\n\n`; | |
| content += "Concepts:\n"; | |
| data.concepts?.forEach(concept => { | |
| content += `- Name: ${concept.name}\n Description: ${concept.description}\n Level: ${concept.level}\n Connections: ${concept.connections?.join(', ')}\n\n`; | |
| }); | |
| break; | |
| } | |
| const blob = new Blob([content], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `${data.title.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.]/g, '')}.txt`; // Sanitize filename | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }; | |
| const renderMaterial = () => { | |
| if (!generatedMaterial) return null; | |
| const { data, type } = generatedMaterial; | |
| switch (type) { | |
| case "study_guide": | |
| return ( | |
| <div className="space-y-6"> | |
| <Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-200"> | |
| <CardHeader> | |
| <CardTitle className="text-2xl">{data.title}</CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <p className="text-lg text-slate-700 leading-relaxed">{data.overview}</p> | |
| </CardContent> | |
| </Card> | |
| {data.key_concepts && data.key_concepts.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Lightbulb className="w-5 h-5 text-amber-500" /> | |
| Key Concepts | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-4"> | |
| {data.key_concepts.map((concept, index) => ( | |
| <div key={index} className="p-4 bg-slate-50 rounded-lg"> | |
| <h4 className="font-semibold text-slate-900 mb-2">{concept.concept}</h4> | |
| <p className="text-slate-700">{concept.explanation}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| )} | |
| {data.formulas && data.formulas.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <FileText className="w-5 h-5 text-red-500" /> | |
| Important Formulas/Reactions | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <ul className="list-disc list-inside space-y-2"> | |
| {data.formulas.map((formula, index) => ( | |
| <li key={index} className="text-slate-700">{formula}</li> | |
| ))} | |
| </ul> | |
| </CardContent> | |
| </Card> | |
| )} | |
| {data.applications && data.applications.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Target className="w-5 h-5 text-orange-500" /> | |
| Real-world Applications | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <ul className="list-disc list-inside space-y-2"> | |
| {data.applications.map((app, index) => ( | |
| <li key={index} className="text-slate-700">{app}</li> | |
| ))} | |
| </ul> | |
| </CardContent> | |
| </Card> | |
| )} | |
| {data.misconceptions && data.misconceptions.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Lightbulb className="w-5 h-5 text-purple-500" /> | |
| Common Misconceptions | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <ul className="list-disc list-inside space-y-2"> | |
| {data.misconceptions.map((misconception, index) => ( | |
| <li key={index} className="text-slate-700">{misconception}</li> | |
| ))} | |
| </ul> | |
| </CardContent> | |
| </Card> | |
| )} | |
| {data.study_tips && data.study_tips.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <GraduationCap className="w-5 h-5 text-emerald-500" /> | |
| Study Tips | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <ul className="space-y-2"> | |
| {data.study_tips.map((tip, index) => ( | |
| <li key={index} className="flex items-start gap-3"> | |
| <CheckCircle className="w-5 h-5 text-emerald-500 mt-0.5 flex-shrink-0" /> | |
| <span className="text-slate-700">{tip}</span> | |
| </li> | |
| ))} | |
| </ul> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| ); | |
| case "flashcards": | |
| return ( | |
| <div className="space-y-6"> | |
| <Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-purple-200"> | |
| <CardHeader> | |
| <CardTitle className="text-2xl">{data.title}</CardTitle> | |
| <p className="text-slate-600">{data.cards?.length} flashcards</p> | |
| </CardHeader> | |
| </Card> | |
| <div className="grid md:grid-cols-2 gap-4"> | |
| {data.cards?.map((card, index) => ( | |
| <Card key={index} className="hover:shadow-lg transition-shadow"> | |
| <CardContent className="p-6"> | |
| <div className="text-center"> | |
| <Badge className="mb-4">Card {index + 1}</Badge> | |
| <div className="space-y-4"> | |
| <div> | |
| <h4 className="font-medium text-slate-600 mb-2">Question:</h4> | |
| <p className="font-semibold text-slate-900">{card.front}</p> | |
| </div> | |
| <div className="border-t pt-4"> | |
| <h4 className="font-medium text-slate-600 mb-2">Answer:</h4> | |
| <p className="text-slate-800">{card.back}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| case "practice_problems": | |
| return ( | |
| <div className="space-y-6"> | |
| <Card className="bg-gradient-to-br from-teal-50 to-emerald-50 border-teal-200"> | |
| <CardHeader> | |
| <CardTitle className="text-2xl">{data.title}</CardTitle> | |
| <p className="text-slate-600">{data.problems?.length} practice problems</p> | |
| </CardHeader> | |
| </Card> | |
| <div className="space-y-4"> | |
| {data.problems?.map((problem, index) => ( | |
| <Card key={index} className="hover:shadow-lg transition-shadow"> | |
| <CardContent className="p-6"> | |
| <div className="space-y-4"> | |
| <div> | |
| <h4 className="font-medium text-slate-600 mb-2">Problem {index + 1}:</h4> | |
| <p className="font-semibold text-slate-900">{problem.question}</p> | |
| </div> | |
| <div className="border-t pt-4"> | |
| <h4 className="font-medium text-slate-600 mb-2">Solution:</h4> | |
| <p className="text-slate-800 whitespace-pre-wrap">{problem.solution}</p> | |
| </div> | |
| {problem.concepts_used && problem.concepts_used.length > 0 && ( | |
| <div className="border-t pt-4"> | |
| <h4 className="font-medium text-slate-600 mb-2">Concepts Used:</h4> | |
| <div className="flex flex-wrap gap-2"> | |
| {problem.concepts_used.map((concept, idx) => ( | |
| <Badge key={idx} variant="outline" className="bg-blue-50 text-blue-700">{concept}</Badge> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| case "concept_map": | |
| return ( | |
| <div className="space-y-6"> | |
| <Card className="bg-gradient-to-br from-yellow-50 to-orange-50 border-yellow-200"> | |
| <CardHeader> | |
| <CardTitle className="text-2xl">{data.title}</CardTitle> | |
| <p className="text-slate-600">Main Concept: <span className="font-medium">{data.main_concept}</span></p> | |
| </CardHeader> | |
| </Card> | |
| {data.concepts && data.concepts.length > 0 && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Lightbulb className="w-5 h-5 text-amber-500" /> | |
| Related Concepts | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
| {data.concepts.map((concept, index) => ( | |
| <Card key={index} className="p-4 bg-slate-50 rounded-lg shadow-sm"> | |
| <h4 className="font-semibold text-slate-900 mb-2">{concept.name}</h4> | |
| <p className="text-sm text-slate-700 mb-2">{concept.description}</p> | |
| {concept.connections && concept.connections.length > 0 && ( | |
| <div className="mt-2"> | |
| <span className="font-medium text-xs text-slate-600">Connections: </span> | |
| <div className="flex flex-wrap gap-1"> | |
| {concept.connections.map((conn, idx) => ( | |
| <Badge key={idx} variant="secondary" className="text-xs">{conn}</Badge> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </Card> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| ); | |
| default: | |
| return ( | |
| <Alert> | |
| <AlertDescription> | |
| Material type not yet implemented for display. | |
| </AlertDescription> | |
| </Alert> | |
| ); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-slate-50 to-emerald-50 p-4 md:p-8"> | |
| <div className="max-w-6xl mx-auto"> | |
| <div className="mb-8"> | |
| <div className="flex items-center gap-3 mb-4"> | |
| <div className="w-12 h-12 bg-gradient-to-br from-emerald-600 to-teal-700 rounded-xl flex items-center justify-center"> | |
| <BookOpen className="w-6 h-6 text-white" /> | |
| </div> | |
| <div> | |
| <h1 className="text-3xl font-bold text-slate-900">AI Study Materials</h1> | |
| <p className="text-slate-600">Generate personalized learning resources with AI</p> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Material Generator */} | |
| <Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm mb-8"> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Sparkles className="w-5 h-5 text-amber-500" /> | |
| Generate Study Material | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="grid md:grid-cols-2 gap-6 mb-6"> | |
| <div> | |
| <label className="block text-sm font-medium text-slate-700 mb-3">Chemistry Topic</label> | |
| <div className="grid grid-cols-2 gap-2"> | |
| {TOPICS.map((topic) => ( | |
| <button | |
| key={topic.value} | |
| onClick={() => setSelectedTopic(topic.value)} | |
| className={`p-3 text-left rounded-lg border transition-all duration-200 ${ | |
| selectedTopic === topic.value | |
| ? 'border-emerald-500 bg-emerald-50' | |
| : 'border-slate-200 hover:border-emerald-300' | |
| }`} | |
| > | |
| <div className="text-lg mb-1">{topic.icon}</div> | |
| <div className="text-sm font-medium text-slate-900">{topic.label}</div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-slate-700 mb-3">Material Type</label> | |
| <div className="space-y-2"> | |
| {MATERIAL_TYPES.map((type) => ( | |
| <button | |
| key={type.value} | |
| onClick={() => setSelectedType(type.value)} | |
| className={`w-full p-4 text-left rounded-lg border transition-all duration-200 ${ | |
| selectedType === type.value | |
| ? 'border-emerald-500 bg-emerald-50' | |
| : 'border-slate-200 hover:border-emerald-300' | |
| }`} | |
| > | |
| <div className="flex items-center gap-3"> | |
| <type.icon className="w-5 h-5 text-emerald-600" /> | |
| <div> | |
| <div className="font-medium text-slate-900">{type.label}</div> | |
| <div className="text-sm text-slate-500">{type.description}</div> | |
| </div> | |
| </div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| <Button | |
| onClick={generateMaterial} | |
| disabled={!selectedTopic || !selectedType || isGenerating} | |
| className="w-full bg-gradient-to-r from-emerald-600 to-teal-700 hover:from-emerald-700 hover:to-teal-800 font-semibold py-3" | |
| > | |
| {isGenerating ? ( | |
| <> | |
| <RefreshCw className="w-5 h-5 mr-2 animate-spin" /> | |
| Generating Material... | |
| </> | |
| ) : ( | |
| <> | |
| <Brain className="w-5 h-5 mr-2" /> | |
| Generate Study Material | |
| </> | |
| )} | |
| </Button> | |
| </CardContent> | |
| </Card> | |
| {/* Generated Material */} | |
| {generatedMaterial && ( | |
| <Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm"> | |
| <CardHeader> | |
| <div className="flex items-center justify-between flex-wrap gap-4"> | |
| <div> | |
| <CardTitle className="text-xl">Generated Material</CardTitle> | |
| <div className="flex gap-2 mt-2"> | |
| <Badge className={TOPICS.find(t => t.value === generatedMaterial.topic)?.color}> | |
| {TOPICS.find(t => t.value === generatedMaterial.topic)?.label} | |
| </Badge> | |
| <Badge variant="outline"> | |
| {MATERIAL_TYPES.find(t => t.value === generatedMaterial.type)?.label} | |
| </Badge> | |
| </div> | |
| </div> | |
| <Button onClick={downloadMaterial} variant="outline"> | |
| <Download className="w-4 h-4 mr-2" /> | |
| Download | |
| </Button> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| {renderMaterial()} | |
| </CardContent> | |
| </Card> | |
| )} | |
| {/* Empty State */} | |
| {!generatedMaterial && !isGenerating && ( | |
| <Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm"> | |
| <CardContent className="text-center py-12"> | |
| <BookOpen className="w-16 h-16 mx-auto text-slate-400 mb-4" /> | |
| <h3 className="text-xl font-semibold text-slate-900 mb-2">Ready to Study?</h3> | |
| <p className="text-slate-600 mb-6"> | |
| Select a chemistry topic and material type above to generate personalized study materials! | |
| </p> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |