Upload 7 files
#1
by
Abbasaabdul
- opened
- Entities/Study_Material/Problems.txt +95 -0
- Entities/Study_Material/Study_Sessions.txt +69 -0
- Layouts/Layouts.txt +224 -0
- Pages/Items/Dashboard.txt +338 -0
- Pages/Items/Molecular_View.txt +850 -0
- Pages/Items/Prctice.txt +436 -0
- Pages/Items/Study_Material.txt +659 -0
Entities/Study_Material/Problems.txt
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Problem",
|
| 3 |
+
"type": "object",
|
| 4 |
+
"properties": {
|
| 5 |
+
"title": {
|
| 6 |
+
"type": "string",
|
| 7 |
+
"description": "Problem title"
|
| 8 |
+
},
|
| 9 |
+
"question": {
|
| 10 |
+
"type": "string",
|
| 11 |
+
"description": "The chemistry problem statement"
|
| 12 |
+
},
|
| 13 |
+
"topic": {
|
| 14 |
+
"type": "string",
|
| 15 |
+
"enum": [
|
| 16 |
+
"organic_chemistry",
|
| 17 |
+
"inorganic_chemistry",
|
| 18 |
+
"physical_chemistry",
|
| 19 |
+
"analytical_chemistry",
|
| 20 |
+
"biochemistry",
|
| 21 |
+
"quantum_chemistry",
|
| 22 |
+
"materials_science"
|
| 23 |
+
],
|
| 24 |
+
"description": "Chemistry topic category"
|
| 25 |
+
},
|
| 26 |
+
"difficulty": {
|
| 27 |
+
"type": "string",
|
| 28 |
+
"enum": [
|
| 29 |
+
"beginner",
|
| 30 |
+
"intermediate",
|
| 31 |
+
"advanced"
|
| 32 |
+
],
|
| 33 |
+
"description": "Problem difficulty level"
|
| 34 |
+
},
|
| 35 |
+
"solution": {
|
| 36 |
+
"type": "string",
|
| 37 |
+
"description": "Step-by-step solution explanation"
|
| 38 |
+
},
|
| 39 |
+
"molecular_formula": {
|
| 40 |
+
"type": "string",
|
| 41 |
+
"description": "Chemical formula if applicable"
|
| 42 |
+
},
|
| 43 |
+
"molecular_structure": {
|
| 44 |
+
"type": "string",
|
| 45 |
+
"description": "3D molecular structure data for visualization"
|
| 46 |
+
},
|
| 47 |
+
"concepts": {
|
| 48 |
+
"type": "array",
|
| 49 |
+
"items": {
|
| 50 |
+
"type": "string"
|
| 51 |
+
},
|
| 52 |
+
"description": "Key chemistry concepts covered"
|
| 53 |
+
},
|
| 54 |
+
"hints": {
|
| 55 |
+
"type": "array",
|
| 56 |
+
"items": {
|
| 57 |
+
"type": "string"
|
| 58 |
+
},
|
| 59 |
+
"description": "Progressive hints for problem solving"
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
"required": [
|
| 63 |
+
"title",
|
| 64 |
+
"question",
|
| 65 |
+
"topic",
|
| 66 |
+
"difficulty",
|
| 67 |
+
"solution"
|
| 68 |
+
],
|
| 69 |
+
"rls": {
|
| 70 |
+
"read": {
|
| 71 |
+
"$or": [
|
| 72 |
+
{
|
| 73 |
+
"created_by": "{{user.email}}"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"user_condition": {
|
| 77 |
+
"role": "admin"
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
]
|
| 81 |
+
},
|
| 82 |
+
"write": {
|
| 83 |
+
"$or": [
|
| 84 |
+
{
|
| 85 |
+
"created_by": "{{user.email}}"
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"user_condition": {
|
| 89 |
+
"role": "admin"
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
]
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
Entities/Study_Material/Study_Sessions.txt
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "StudySession",
|
| 3 |
+
"type": "object",
|
| 4 |
+
"properties": {
|
| 5 |
+
"user_email": {
|
| 6 |
+
"type": "string",
|
| 7 |
+
"description": "Email of the student"
|
| 8 |
+
},
|
| 9 |
+
"problem_id": {
|
| 10 |
+
"type": "string",
|
| 11 |
+
"description": "ID of the problem attempted"
|
| 12 |
+
},
|
| 13 |
+
"completed": {
|
| 14 |
+
"type": "boolean",
|
| 15 |
+
"default": false,
|
| 16 |
+
"description": "Whether the problem was successfully solved"
|
| 17 |
+
},
|
| 18 |
+
"time_spent": {
|
| 19 |
+
"type": "number",
|
| 20 |
+
"description": "Time spent on problem in minutes"
|
| 21 |
+
},
|
| 22 |
+
"hints_used": {
|
| 23 |
+
"type": "number",
|
| 24 |
+
"default": 0,
|
| 25 |
+
"description": "Number of hints the student used"
|
| 26 |
+
},
|
| 27 |
+
"score": {
|
| 28 |
+
"type": "number",
|
| 29 |
+
"minimum": 0,
|
| 30 |
+
"maximum": 100,
|
| 31 |
+
"description": "Score achieved on the problem"
|
| 32 |
+
},
|
| 33 |
+
"session_date": {
|
| 34 |
+
"type": "string",
|
| 35 |
+
"format": "date",
|
| 36 |
+
"description": "Date of the study session"
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
"required": [
|
| 40 |
+
"user_email",
|
| 41 |
+
"problem_id"
|
| 42 |
+
],
|
| 43 |
+
"rls": {
|
| 44 |
+
"read": {
|
| 45 |
+
"$or": [
|
| 46 |
+
{
|
| 47 |
+
"user_email": "{{user.email}}"
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"user_condition": {
|
| 51 |
+
"role": "admin"
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
]
|
| 55 |
+
},
|
| 56 |
+
"write": {
|
| 57 |
+
"$or": [
|
| 58 |
+
{
|
| 59 |
+
"user_email": "{{user.email}}"
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"user_condition": {
|
| 63 |
+
"role": "admin"
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
]
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
}
|
Layouts/Layouts.txt
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from "react";
|
| 2 |
+
import { Link, useLocation } from "react-router-dom";
|
| 3 |
+
import { createPageUrl } from "@/utils";
|
| 4 |
+
import {
|
| 5 |
+
BookOpen,
|
| 6 |
+
Brain,
|
| 7 |
+
Target,
|
| 8 |
+
BarChart3,
|
| 9 |
+
Atom,
|
| 10 |
+
User,
|
| 11 |
+
Menu,
|
| 12 |
+
X,
|
| 13 |
+
Sparkles,
|
| 14 |
+
Trophy
|
| 15 |
+
} from "lucide-react";
|
| 16 |
+
import { User as UserEntity } from "@/entities/User";
|
| 17 |
+
import {
|
| 18 |
+
Sidebar,
|
| 19 |
+
SidebarContent,
|
| 20 |
+
SidebarGroup,
|
| 21 |
+
SidebarGroupContent,
|
| 22 |
+
SidebarGroupLabel,
|
| 23 |
+
SidebarMenu,
|
| 24 |
+
SidebarMenuButton,
|
| 25 |
+
SidebarMenuItem,
|
| 26 |
+
SidebarHeader,
|
| 27 |
+
SidebarFooter,
|
| 28 |
+
SidebarProvider,
|
| 29 |
+
SidebarTrigger,
|
| 30 |
+
} from "@/components/ui/sidebar";
|
| 31 |
+
|
| 32 |
+
const navigationItems = [
|
| 33 |
+
{
|
| 34 |
+
title: "Dashboard",
|
| 35 |
+
url: createPageUrl("Dashboard"),
|
| 36 |
+
icon: BarChart3,
|
| 37 |
+
description: "Overview & Progress"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
title: "Practice",
|
| 41 |
+
url: createPageUrl("Practice"),
|
| 42 |
+
icon: Brain,
|
| 43 |
+
description: "AI-Generated Problems"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
title: "Molecular Viewer",
|
| 47 |
+
url: createPageUrl("MolecularViewer"),
|
| 48 |
+
icon: Atom,
|
| 49 |
+
description: "3D Visualizations"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
title: "Study Materials",
|
| 53 |
+
url: createPageUrl("StudyMaterials"),
|
| 54 |
+
icon: BookOpen,
|
| 55 |
+
description: "Custom Learning Resources"
|
| 56 |
+
}
|
| 57 |
+
];
|
| 58 |
+
|
| 59 |
+
export default function Layout({ children, currentPageName }) {
|
| 60 |
+
const location = useLocation();
|
| 61 |
+
const [user, setUser] = useState(null);
|
| 62 |
+
const [loading, setLoading] = useState(true);
|
| 63 |
+
|
| 64 |
+
useEffect(() => {
|
| 65 |
+
loadUser();
|
| 66 |
+
}, []);
|
| 67 |
+
|
| 68 |
+
const loadUser = async () => {
|
| 69 |
+
try {
|
| 70 |
+
const userData = await UserEntity.me();
|
| 71 |
+
setUser(userData);
|
| 72 |
+
} catch (error) {
|
| 73 |
+
console.log("User not logged in");
|
| 74 |
+
}
|
| 75 |
+
setLoading(false);
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
if (loading) {
|
| 79 |
+
return (
|
| 80 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 flex items-center justify-center">
|
| 81 |
+
<div className="flex items-center gap-3">
|
| 82 |
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
| 83 |
+
<span className="text-slate-600 font-medium">Loading ChemTutor AI...</span>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
return (
|
| 90 |
+
<SidebarProvider>
|
| 91 |
+
<div className="min-h-screen flex w-full bg-gradient-to-br from-slate-50 to-blue-50">
|
| 92 |
+
<Sidebar className="border-r border-slate-200 bg-white/80 backdrop-blur-sm">
|
| 93 |
+
<SidebarHeader className="border-b border-slate-200 p-6">
|
| 94 |
+
<div className="flex items-center gap-3">
|
| 95 |
+
<div className="relative w-10 h-10 bg-gradient-to-br from-blue-600 to-indigo-700 rounded-xl flex items-center justify-center shadow-lg">
|
| 96 |
+
<Atom className="w-6 h-6 text-white" />
|
| 97 |
+
<div className="absolute -top-1 -right-1 w-4 h-4 bg-gradient-to-r from-emerald-400 to-teal-500 rounded-full flex items-center justify-center">
|
| 98 |
+
<Sparkles className="w-2.5 h-2.5 text-white" />
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
<div>
|
| 102 |
+
<h2 className="text-xl font-bold bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent">
|
| 103 |
+
ChemTutor AI
|
| 104 |
+
</h2>
|
| 105 |
+
<p className="text-sm text-slate-500">Intelligent Chemistry Learning</p>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
</SidebarHeader>
|
| 109 |
+
|
| 110 |
+
<SidebarContent className="p-4">
|
| 111 |
+
<SidebarGroup>
|
| 112 |
+
<SidebarGroupLabel className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3 py-2">
|
| 113 |
+
Learning Hub
|
| 114 |
+
</SidebarGroupLabel>
|
| 115 |
+
<SidebarGroupContent>
|
| 116 |
+
<SidebarMenu>
|
| 117 |
+
{navigationItems.map((item) => (
|
| 118 |
+
<SidebarMenuItem key={item.title}>
|
| 119 |
+
<SidebarMenuButton
|
| 120 |
+
asChild
|
| 121 |
+
className={`group hover:bg-blue-50 hover:text-blue-700 transition-all duration-200 rounded-xl mb-1 ${
|
| 122 |
+
location.pathname === item.url
|
| 123 |
+
? 'bg-gradient-to-r from-blue-50 to-indigo-50 text-blue-700 shadow-sm border border-blue-100'
|
| 124 |
+
: ''
|
| 125 |
+
}`}
|
| 126 |
+
>
|
| 127 |
+
<Link to={item.url} className="flex items-center gap-4 px-4 py-3">
|
| 128 |
+
<item.icon className="w-5 h-5 group-hover:scale-110 transition-transform duration-200" />
|
| 129 |
+
<div>
|
| 130 |
+
<span className="font-semibold">{item.title}</span>
|
| 131 |
+
<p className="text-xs text-slate-500 group-hover:text-blue-600 transition-colors">
|
| 132 |
+
{item.description}
|
| 133 |
+
</p>
|
| 134 |
+
</div>
|
| 135 |
+
</Link>
|
| 136 |
+
</SidebarMenuButton>
|
| 137 |
+
</SidebarMenuItem>
|
| 138 |
+
))}
|
| 139 |
+
</SidebarMenu>
|
| 140 |
+
</SidebarGroupContent>
|
| 141 |
+
</SidebarGroup>
|
| 142 |
+
|
| 143 |
+
{user && (
|
| 144 |
+
<SidebarGroup className="mt-6">
|
| 145 |
+
<SidebarGroupLabel className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3 py-2">
|
| 146 |
+
Your Progress
|
| 147 |
+
</SidebarGroupLabel>
|
| 148 |
+
<SidebarGroupContent>
|
| 149 |
+
<div className="px-4 py-3 space-y-3">
|
| 150 |
+
<div className="flex items-center justify-between">
|
| 151 |
+
<div className="flex items-center gap-2">
|
| 152 |
+
<Trophy className="w-4 h-4 text-amber-500" />
|
| 153 |
+
<span className="text-sm font-medium text-slate-700">Problems Solved</span>
|
| 154 |
+
</div>
|
| 155 |
+
<span className="text-lg font-bold text-blue-600">
|
| 156 |
+
{user.total_problems_solved || 0}
|
| 157 |
+
</span>
|
| 158 |
+
</div>
|
| 159 |
+
<div className="flex items-center justify-between">
|
| 160 |
+
<div className="flex items-center gap-2">
|
| 161 |
+
<Target className="w-4 h-4 text-emerald-500" />
|
| 162 |
+
<span className="text-sm font-medium text-slate-700">Current Streak</span>
|
| 163 |
+
</div>
|
| 164 |
+
<span className="text-lg font-bold text-emerald-600">
|
| 165 |
+
{user.current_streak || 0} days
|
| 166 |
+
</span>
|
| 167 |
+
</div>
|
| 168 |
+
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-3 border border-blue-100">
|
| 169 |
+
<p className="text-xs font-medium text-blue-700 mb-1">Learning Level</p>
|
| 170 |
+
<p className="text-sm font-bold text-blue-900 capitalize">
|
| 171 |
+
{user.learning_level || 'Beginner'}
|
| 172 |
+
</p>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</SidebarGroupContent>
|
| 176 |
+
</SidebarGroup>
|
| 177 |
+
)}
|
| 178 |
+
</SidebarContent>
|
| 179 |
+
|
| 180 |
+
<SidebarFooter className="border-t border-slate-200 p-4">
|
| 181 |
+
{user ? (
|
| 182 |
+
<div className="flex items-center gap-3 p-3 rounded-xl bg-gradient-to-r from-slate-50 to-blue-50 border border-slate-200">
|
| 183 |
+
<div className="w-10 h-10 bg-gradient-to-br from-blue-600 to-indigo-700 rounded-full flex items-center justify-center">
|
| 184 |
+
<User className="w-5 h-5 text-white" />
|
| 185 |
+
</div>
|
| 186 |
+
<div className="flex-1 min-w-0">
|
| 187 |
+
<p className="font-semibold text-slate-900 text-sm truncate">
|
| 188 |
+
{user.full_name || 'Student'}
|
| 189 |
+
</p>
|
| 190 |
+
<p className="text-xs text-slate-500 truncate">{user.email}</p>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
) : (
|
| 194 |
+
<button
|
| 195 |
+
onClick={() => UserEntity.login()}
|
| 196 |
+
className="w-full bg-gradient-to-r from-blue-600 to-indigo-700 text-white font-semibold py-3 px-4 rounded-xl hover:from-blue-700 hover:to-indigo-800 transition-all duration-200 shadow-lg hover:shadow-xl"
|
| 197 |
+
>
|
| 198 |
+
Start Learning
|
| 199 |
+
</button>
|
| 200 |
+
)}
|
| 201 |
+
</SidebarFooter>
|
| 202 |
+
</Sidebar>
|
| 203 |
+
|
| 204 |
+
<main className="flex-1 flex flex-col">
|
| 205 |
+
{/* Mobile header */}
|
| 206 |
+
<header className="bg-white/80 backdrop-blur-sm border-b border-slate-200 px-6 py-4 md:hidden">
|
| 207 |
+
<div className="flex items-center gap-4">
|
| 208 |
+
<SidebarTrigger className="hover:bg-slate-100 p-2 rounded-lg transition-colors duration-200" />
|
| 209 |
+
<div className="flex items-center gap-2">
|
| 210 |
+
<Atom className="w-6 h-6 text-blue-600" />
|
| 211 |
+
<h1 className="text-lg font-bold text-slate-900">ChemTutor AI</h1>
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
</header>
|
| 215 |
+
|
| 216 |
+
{/* Main content area */}
|
| 217 |
+
<div className="flex-1 overflow-auto">
|
| 218 |
+
{children}
|
| 219 |
+
</div>
|
| 220 |
+
</main>
|
| 221 |
+
</div>
|
| 222 |
+
</SidebarProvider>
|
| 223 |
+
);
|
| 224 |
+
}
|
Pages/Items/Dashboard.txt
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from "react";
|
| 2 |
+
import { User } from "@/entities/User";
|
| 3 |
+
import { Problem } from "@/entities/Problem";
|
| 4 |
+
import { StudySession } from "@/entities/StudySession";
|
| 5 |
+
import { Button } from "@/components/ui/button";
|
| 6 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 7 |
+
import { Progress } from "@/components/ui/progress";
|
| 8 |
+
import { Badge } from "@/components/ui/badge";
|
| 9 |
+
import { Link } from "react-router-dom";
|
| 10 |
+
import { createPageUrl } from "@/utils";
|
| 11 |
+
import {
|
| 12 |
+
Brain,
|
| 13 |
+
Target,
|
| 14 |
+
TrendingUp,
|
| 15 |
+
BookOpen,
|
| 16 |
+
Trophy,
|
| 17 |
+
Clock,
|
| 18 |
+
Atom,
|
| 19 |
+
Sparkles,
|
| 20 |
+
Play,
|
| 21 |
+
ChevronRight,
|
| 22 |
+
Calendar,
|
| 23 |
+
Award
|
| 24 |
+
} from "lucide-react";
|
| 25 |
+
import { format, startOfWeek, endOfWeek } from "date-fns";
|
| 26 |
+
|
| 27 |
+
export default function Dashboard() {
|
| 28 |
+
const [user, setUser] = useState(null);
|
| 29 |
+
const [recentSessions, setRecentSessions] = useState([]);
|
| 30 |
+
const [weeklyStats, setWeeklyStats] = useState({ solved: 0, timeSpent: 0 });
|
| 31 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 32 |
+
|
| 33 |
+
useEffect(() => {
|
| 34 |
+
loadDashboardData();
|
| 35 |
+
}, []);
|
| 36 |
+
|
| 37 |
+
const loadDashboardData = async () => {
|
| 38 |
+
try {
|
| 39 |
+
const userData = await User.me();
|
| 40 |
+
if (!userData) {
|
| 41 |
+
await User.login();
|
| 42 |
+
return;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
setUser(userData);
|
| 46 |
+
|
| 47 |
+
// Load recent study sessions
|
| 48 |
+
const sessions = await StudySession.filter(
|
| 49 |
+
{ user_email: userData.email },
|
| 50 |
+
'-session_date',
|
| 51 |
+
10
|
| 52 |
+
);
|
| 53 |
+
setRecentSessions(sessions);
|
| 54 |
+
|
| 55 |
+
// Calculate weekly stats
|
| 56 |
+
const weekStart = startOfWeek(new Date());
|
| 57 |
+
const weekEnd = endOfWeek(new Date());
|
| 58 |
+
const weeklySessions = sessions.filter(session => {
|
| 59 |
+
const sessionDate = new Date(session.session_date);
|
| 60 |
+
return sessionDate >= weekStart && sessionDate <= weekEnd;
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
setWeeklyStats({
|
| 64 |
+
solved: weeklySessions.filter(s => s.completed).length,
|
| 65 |
+
timeSpent: weeklySessions.reduce((total, s) => total + (s.time_spent || 0), 0)
|
| 66 |
+
});
|
| 67 |
+
|
| 68 |
+
} catch (error) {
|
| 69 |
+
console.error("Error loading dashboard:", error);
|
| 70 |
+
}
|
| 71 |
+
setIsLoading(false);
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
const handleGetStarted = async () => {
|
| 75 |
+
if (!user) {
|
| 76 |
+
await User.login();
|
| 77 |
+
}
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
if (isLoading) {
|
| 81 |
+
return (
|
| 82 |
+
<div className="flex items-center justify-center min-h-screen">
|
| 83 |
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
| 84 |
+
</div>
|
| 85 |
+
);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
if (!user) {
|
| 89 |
+
return (
|
| 90 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 flex items-center justify-center p-6">
|
| 91 |
+
<Card className="max-w-2xl w-full border-0 shadow-2xl bg-white/80 backdrop-blur-sm">
|
| 92 |
+
<CardHeader className="text-center pb-8">
|
| 93 |
+
<div className="w-20 h-20 mx-auto mb-6 bg-gradient-to-br from-blue-600 to-indigo-700 rounded-full flex items-center justify-center shadow-xl">
|
| 94 |
+
<Atom className="w-10 h-10 text-white" />
|
| 95 |
+
</div>
|
| 96 |
+
<CardTitle className="text-4xl font-bold bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent mb-4">
|
| 97 |
+
Welcome to ChemTutor AI
|
| 98 |
+
</CardTitle>
|
| 99 |
+
<p className="text-xl text-slate-600 leading-relaxed">
|
| 100 |
+
Your intelligent chemistry learning companion. Master chemistry concepts through AI-powered personalized problems, interactive 3D molecular visualization, and adaptive tutoring.
|
| 101 |
+
</p>
|
| 102 |
+
</CardHeader>
|
| 103 |
+
<CardContent className="pt-0">
|
| 104 |
+
<div className="grid md:grid-cols-3 gap-6 mb-8">
|
| 105 |
+
<div className="text-center p-4">
|
| 106 |
+
<Brain className="w-12 h-12 mx-auto mb-3 text-blue-600" />
|
| 107 |
+
<h3 className="font-semibold text-slate-900 mb-2">AI-Generated Problems</h3>
|
| 108 |
+
<p className="text-sm text-slate-600">Custom chemistry problems tailored to your skill level</p>
|
| 109 |
+
</div>
|
| 110 |
+
<div className="text-center p-4">
|
| 111 |
+
<Atom className="w-12 h-12 mx-auto mb-3 text-indigo-600" />
|
| 112 |
+
<h3 className="font-semibold text-slate-900 mb-2">3D Molecular Models</h3>
|
| 113 |
+
<p className="text-sm text-slate-600">Interactive visualizations to understand molecular structures</p>
|
| 114 |
+
</div>
|
| 115 |
+
<div className="text-center p-4">
|
| 116 |
+
<TrendingUp className="w-12 h-12 mx-auto mb-3 text-emerald-600" />
|
| 117 |
+
<h3 className="font-semibold text-slate-900 mb-2">Adaptive Learning</h3>
|
| 118 |
+
<p className="text-sm text-slate-600">Personalized difficulty adjustment based on your progress</p>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
<Button
|
| 122 |
+
onClick={handleGetStarted}
|
| 123 |
+
className="w-full bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 text-white font-semibold py-4 px-8 text-lg shadow-lg hover:shadow-xl transition-all duration-200"
|
| 124 |
+
>
|
| 125 |
+
<Play className="w-5 h-5 mr-2" />
|
| 126 |
+
Start Learning Now
|
| 127 |
+
</Button>
|
| 128 |
+
</CardContent>
|
| 129 |
+
</Card>
|
| 130 |
+
</div>
|
| 131 |
+
);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
const dailyGoalProgress = user.study_goals?.daily_problems
|
| 135 |
+
? Math.min(100, (weeklyStats.solved / 7) / user.study_goals.daily_problems * 100)
|
| 136 |
+
: 0;
|
| 137 |
+
|
| 138 |
+
return (
|
| 139 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-4 md:p-8">
|
| 140 |
+
<div className="max-w-7xl mx-auto">
|
| 141 |
+
{/* Welcome Header */}
|
| 142 |
+
<div className="mb-8">
|
| 143 |
+
<div className="flex items-center gap-3 mb-2">
|
| 144 |
+
<h1 className="text-3xl md:text-4xl font-bold text-slate-900">
|
| 145 |
+
Welcome back, {user.full_name?.split(' ')[0]}!
|
| 146 |
+
</h1>
|
| 147 |
+
<Sparkles className="w-8 h-8 text-amber-500" />
|
| 148 |
+
</div>
|
| 149 |
+
<p className="text-lg text-slate-600">Ready to explore chemistry today? Let's continue your learning journey.</p>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
{/* Stats Cards */}
|
| 153 |
+
<div className="grid md:grid-cols-4 gap-6 mb-8">
|
| 154 |
+
<Card className="border-0 shadow-lg bg-gradient-to-br from-blue-600 to-indigo-700 text-white">
|
| 155 |
+
<CardContent className="p-6">
|
| 156 |
+
<div className="flex items-center justify-between">
|
| 157 |
+
<div>
|
| 158 |
+
<p className="text-blue-100 text-sm font-medium">Problems Solved</p>
|
| 159 |
+
<p className="text-3xl font-bold">{user.total_problems_solved || 0}</p>
|
| 160 |
+
</div>
|
| 161 |
+
<Trophy className="w-12 h-12 text-blue-200" />
|
| 162 |
+
</div>
|
| 163 |
+
</CardContent>
|
| 164 |
+
</Card>
|
| 165 |
+
|
| 166 |
+
<Card className="border-0 shadow-lg bg-gradient-to-br from-emerald-600 to-teal-700 text-white">
|
| 167 |
+
<CardContent className="p-6">
|
| 168 |
+
<div className="flex items-center justify-between">
|
| 169 |
+
<div>
|
| 170 |
+
<p className="text-emerald-100 text-sm font-medium">Current Streak</p>
|
| 171 |
+
<p className="text-3xl font-bold">{user.current_streak || 0} days</p>
|
| 172 |
+
</div>
|
| 173 |
+
<Target className="w-12 h-12 text-emerald-200" />
|
| 174 |
+
</div>
|
| 175 |
+
</CardContent>
|
| 176 |
+
</Card>
|
| 177 |
+
|
| 178 |
+
<Card className="border-0 shadow-lg bg-gradient-to-br from-purple-600 to-pink-700 text-white">
|
| 179 |
+
<CardContent className="p-6">
|
| 180 |
+
<div className="flex items-center justify-between">
|
| 181 |
+
<div>
|
| 182 |
+
<p className="text-purple-100 text-sm font-medium">This Week</p>
|
| 183 |
+
<p className="text-3xl font-bold">{weeklyStats.solved}</p>
|
| 184 |
+
</div>
|
| 185 |
+
<Calendar className="w-12 h-12 text-purple-200" />
|
| 186 |
+
</div>
|
| 187 |
+
</CardContent>
|
| 188 |
+
</Card>
|
| 189 |
+
|
| 190 |
+
<Card className="border-0 shadow-lg bg-gradient-to-br from-orange-600 to-red-700 text-white">
|
| 191 |
+
<CardContent className="p-6">
|
| 192 |
+
<div className="flex items-center justify-between">
|
| 193 |
+
<div>
|
| 194 |
+
<p className="text-orange-100 text-sm font-medium">Study Time</p>
|
| 195 |
+
<p className="text-3xl font-bold">{Math.round(weeklyStats.timeSpent)}m</p>
|
| 196 |
+
</div>
|
| 197 |
+
<Clock className="w-12 h-12 text-orange-200" />
|
| 198 |
+
</div>
|
| 199 |
+
</CardContent>
|
| 200 |
+
</Card>
|
| 201 |
+
</div>
|
| 202 |
+
|
| 203 |
+
<div className="grid lg:grid-cols-3 gap-8">
|
| 204 |
+
{/* Quick Actions */}
|
| 205 |
+
<div className="lg:col-span-2 space-y-6">
|
| 206 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 207 |
+
<CardHeader>
|
| 208 |
+
<CardTitle className="flex items-center gap-2 text-2xl">
|
| 209 |
+
<Brain className="w-6 h-6 text-blue-600" />
|
| 210 |
+
Quick Start
|
| 211 |
+
</CardTitle>
|
| 212 |
+
</CardHeader>
|
| 213 |
+
<CardContent>
|
| 214 |
+
<div className="grid md:grid-cols-2 gap-4">
|
| 215 |
+
<Link to={createPageUrl("Practice")}>
|
| 216 |
+
<Card className="hover:shadow-lg transition-all duration-200 cursor-pointer group bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-200">
|
| 217 |
+
<CardContent className="p-6">
|
| 218 |
+
<div className="flex items-center justify-between mb-4">
|
| 219 |
+
<Brain className="w-8 h-8 text-blue-600 group-hover:scale-110 transition-transform" />
|
| 220 |
+
<ChevronRight className="w-5 h-5 text-blue-400 group-hover:translate-x-1 transition-transform" />
|
| 221 |
+
</div>
|
| 222 |
+
<h3 className="font-semibold text-slate-900 mb-2">Practice Problems</h3>
|
| 223 |
+
<p className="text-sm text-slate-600">Solve AI-generated chemistry problems</p>
|
| 224 |
+
</CardContent>
|
| 225 |
+
</Card>
|
| 226 |
+
</Link>
|
| 227 |
+
|
| 228 |
+
<Link to={createPageUrl("MolecularViewer")}>
|
| 229 |
+
<Card className="hover:shadow-lg transition-all duration-200 cursor-pointer group bg-gradient-to-br from-purple-50 to-pink-50 border-purple-200">
|
| 230 |
+
<CardContent className="p-6">
|
| 231 |
+
<div className="flex items-center justify-between mb-4">
|
| 232 |
+
<Atom className="w-8 h-8 text-purple-600 group-hover:scale-110 transition-transform" />
|
| 233 |
+
<ChevronRight className="w-5 h-5 text-purple-400 group-hover:translate-x-1 transition-transform" />
|
| 234 |
+
</div>
|
| 235 |
+
<h3 className="font-semibold text-slate-900 mb-2">3D Molecules</h3>
|
| 236 |
+
<p className="text-sm text-slate-600">Explore molecular structures in 3D</p>
|
| 237 |
+
</CardContent>
|
| 238 |
+
</Card>
|
| 239 |
+
</Link>
|
| 240 |
+
</div>
|
| 241 |
+
</CardContent>
|
| 242 |
+
</Card>
|
| 243 |
+
|
| 244 |
+
{/* Daily Progress */}
|
| 245 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 246 |
+
<CardHeader>
|
| 247 |
+
<CardTitle className="flex items-center gap-2">
|
| 248 |
+
<Target className="w-5 h-5 text-emerald-600" />
|
| 249 |
+
Daily Progress
|
| 250 |
+
</CardTitle>
|
| 251 |
+
</CardHeader>
|
| 252 |
+
<CardContent>
|
| 253 |
+
<div className="space-y-4">
|
| 254 |
+
<div className="flex justify-between items-center">
|
| 255 |
+
<span className="font-medium text-slate-700">Daily Goal Progress</span>
|
| 256 |
+
<span className="text-sm text-slate-500">
|
| 257 |
+
{Math.round(dailyGoalProgress)}%
|
| 258 |
+
</span>
|
| 259 |
+
</div>
|
| 260 |
+
<Progress value={dailyGoalProgress} className="h-3" />
|
| 261 |
+
<p className="text-sm text-slate-600">
|
| 262 |
+
Goal: {user.study_goals?.daily_problems || 5} problems per day
|
| 263 |
+
</p>
|
| 264 |
+
</div>
|
| 265 |
+
</CardContent>
|
| 266 |
+
</Card>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
{/* Learning Level & Recent Activity */}
|
| 270 |
+
<div className="space-y-6">
|
| 271 |
+
<Card className="border-0 shadow-xl bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
| 272 |
+
<CardContent className="p-6">
|
| 273 |
+
<div className="flex items-center gap-3 mb-4">
|
| 274 |
+
<Award className="w-6 h-6 text-amber-400" />
|
| 275 |
+
<h3 className="font-semibold text-lg">Learning Level</h3>
|
| 276 |
+
</div>
|
| 277 |
+
<Badge className="bg-amber-500 hover:bg-amber-600 text-amber-950 font-semibold text-lg px-4 py-2 mb-4">
|
| 278 |
+
{(user.learning_level || 'beginner').charAt(0).toUpperCase() + (user.learning_level || 'beginner').slice(1)}
|
| 279 |
+
</Badge>
|
| 280 |
+
<p className="text-slate-300 text-sm leading-relaxed">
|
| 281 |
+
{user.learning_level === 'advanced'
|
| 282 |
+
? "You're mastering advanced chemistry concepts! Keep pushing your limits."
|
| 283 |
+
: user.learning_level === 'intermediate'
|
| 284 |
+
? "You're building solid chemistry fundamentals. Ready for more challenges?"
|
| 285 |
+
: "Welcome to your chemistry journey! Let's start with the basics and build up your confidence."
|
| 286 |
+
}
|
| 287 |
+
</p>
|
| 288 |
+
</CardContent>
|
| 289 |
+
</Card>
|
| 290 |
+
|
| 291 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 292 |
+
<CardHeader>
|
| 293 |
+
<CardTitle className="flex items-center gap-2 text-lg">
|
| 294 |
+
<BookOpen className="w-5 h-5 text-slate-600" />
|
| 295 |
+
Recent Activity
|
| 296 |
+
</CardTitle>
|
| 297 |
+
</CardHeader>
|
| 298 |
+
<CardContent>
|
| 299 |
+
{recentSessions.length > 0 ? (
|
| 300 |
+
<div className="space-y-3">
|
| 301 |
+
{recentSessions.slice(0, 5).map((session, index) => (
|
| 302 |
+
<div key={session.id} className="flex items-center gap-3 p-3 rounded-lg bg-slate-50 hover:bg-slate-100 transition-colors">
|
| 303 |
+
<div className={`w-3 h-3 rounded-full ${session.completed ? 'bg-emerald-500' : 'bg-slate-400'}`} />
|
| 304 |
+
<div className="flex-1">
|
| 305 |
+
<p className="text-sm font-medium text-slate-900">
|
| 306 |
+
Problem #{session.problem_id?.slice(-4)}
|
| 307 |
+
</p>
|
| 308 |
+
<p className="text-xs text-slate-500">
|
| 309 |
+
{session.session_date && format(new Date(session.session_date), "MMM d")}
|
| 310 |
+
</p>
|
| 311 |
+
</div>
|
| 312 |
+
{session.score && (
|
| 313 |
+
<Badge variant="secondary" className="text-xs">
|
| 314 |
+
{session.score}%
|
| 315 |
+
</Badge>
|
| 316 |
+
)}
|
| 317 |
+
</div>
|
| 318 |
+
))}
|
| 319 |
+
</div>
|
| 320 |
+
) : (
|
| 321 |
+
<div className="text-center py-8">
|
| 322 |
+
<BookOpen className="w-12 h-12 mx-auto text-slate-400 mb-3" />
|
| 323 |
+
<p className="text-slate-500 mb-4">No recent activity yet</p>
|
| 324 |
+
<Link to={createPageUrl("Practice")}>
|
| 325 |
+
<Button variant="outline" size="sm">
|
| 326 |
+
Start Your First Problem
|
| 327 |
+
</Button>
|
| 328 |
+
</Link>
|
| 329 |
+
</div>
|
| 330 |
+
)}
|
| 331 |
+
</CardContent>
|
| 332 |
+
</Card>
|
| 333 |
+
</div>
|
| 334 |
+
</div>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
);
|
| 338 |
+
}
|
Pages/Items/Molecular_View.txt
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useRef } from "react";
|
| 2 |
+
import * as THREE from "three";
|
| 3 |
+
import { Button } from "@/components/ui/button";
|
| 4 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 5 |
+
import { Input } from "@/components/ui/input";
|
| 6 |
+
import { Badge } from "@/components/ui/badge";
|
| 7 |
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 8 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 9 |
+
import {
|
| 10 |
+
Atom,
|
| 11 |
+
RotateCcw,
|
| 12 |
+
Search,
|
| 13 |
+
Play,
|
| 14 |
+
Pause,
|
| 15 |
+
Info,
|
| 16 |
+
Filter
|
| 17 |
+
} from "lucide-react";
|
| 18 |
+
|
| 19 |
+
// Comprehensive molecules database organized by category
|
| 20 |
+
const MOLECULES = [
|
| 21 |
+
// Simple Molecules
|
| 22 |
+
{
|
| 23 |
+
name: "Hydrogen Gas (H₂)",
|
| 24 |
+
formula: "H2",
|
| 25 |
+
category: "simple",
|
| 26 |
+
atoms: [
|
| 27 |
+
{ element: "H", position: [-0.37, 0, 0], color: "#ffffff" },
|
| 28 |
+
{ element: "H", position: [0.37, 0, 0], color: "#ffffff" }
|
| 29 |
+
],
|
| 30 |
+
bonds: [{ from: 0, to: 1 }],
|
| 31 |
+
description: "The simplest and most abundant element in the universe, existing as diatomic molecules under normal conditions."
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
name: "Oxygen Gas (O₂)",
|
| 35 |
+
formula: "O2",
|
| 36 |
+
category: "simple",
|
| 37 |
+
atoms: [
|
| 38 |
+
{ element: "O", position: [-0.60, 0, 0], color: "#ff0000" },
|
| 39 |
+
{ element: "O", position: [0.60, 0, 0], color: "#ff0000" }
|
| 40 |
+
],
|
| 41 |
+
bonds: [{ from: 0, to: 1 }],
|
| 42 |
+
description: "Essential for respiration and combustion, making up about 21% of Earth's atmosphere."
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
name: "Nitrogen Gas (N₂)",
|
| 46 |
+
formula: "N2",
|
| 47 |
+
category: "simple",
|
| 48 |
+
atoms: [
|
| 49 |
+
{ element: "N", position: [-0.55, 0, 0], color: "#0000ff" },
|
| 50 |
+
{ element: "N", position: [0.55, 0, 0], color: "#0000ff" }
|
| 51 |
+
],
|
| 52 |
+
bonds: [{ from: 0, to: 1 }],
|
| 53 |
+
description: "Makes up about 78% of Earth's atmosphere. The triple bond makes it very stable."
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
name: "Carbon Dioxide (CO₂)",
|
| 57 |
+
formula: "CO2",
|
| 58 |
+
category: "simple",
|
| 59 |
+
atoms: [
|
| 60 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 61 |
+
{ element: "O", position: [-1.16, 0, 0], color: "#ff0000" },
|
| 62 |
+
{ element: "O", position: [1.16, 0, 0], color: "#ff0000" }
|
| 63 |
+
],
|
| 64 |
+
bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }],
|
| 65 |
+
description: "A greenhouse gas produced by combustion and respiration, absorbed by plants during photosynthesis."
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
name: "Water (H₂O)",
|
| 69 |
+
formula: "H2O",
|
| 70 |
+
category: "simple",
|
| 71 |
+
atoms: [
|
| 72 |
+
{ element: "O", position: [0, 0, 0], color: "#ff0000" },
|
| 73 |
+
{ element: "H", position: [0.96, 0, 0], color: "#ffffff" },
|
| 74 |
+
{ element: "H", position: [-0.24, 0.93, 0], color: "#ffffff" }
|
| 75 |
+
],
|
| 76 |
+
bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }],
|
| 77 |
+
description: "The most essential compound for life, consisting of two hydrogen atoms bonded to one oxygen atom."
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
name: "Ammonia (NH₃)",
|
| 81 |
+
formula: "NH3",
|
| 82 |
+
category: "simple",
|
| 83 |
+
atoms: [
|
| 84 |
+
{ element: "N", position: [0, 0, 0], color: "#0000ff" },
|
| 85 |
+
{ element: "H", position: [0.94, 0, 0.33], color: "#ffffff" },
|
| 86 |
+
{ element: "H", position: [-0.47, 0.82, 0.33], color: "#ffffff" },
|
| 87 |
+
{ element: "H", position: [-0.47, -0.82, 0.33], color: "#ffffff" }
|
| 88 |
+
],
|
| 89 |
+
bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }],
|
| 90 |
+
description: "A basic compound used in fertilizers and cleaning products, with a trigonal pyramidal shape."
|
| 91 |
+
},
|
| 92 |
+
|
| 93 |
+
// Hydrocarbons - Alkanes
|
| 94 |
+
{
|
| 95 |
+
name: "Methane (CH₄)",
|
| 96 |
+
formula: "CH4",
|
| 97 |
+
category: "hydrocarbons",
|
| 98 |
+
atoms: [
|
| 99 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 100 |
+
{ element: "H", position: [0.63, 0.63, 0.63], color: "#ffffff" },
|
| 101 |
+
{ element: "H", position: [-0.63, -0.63, 0.63], color: "#ffffff" },
|
| 102 |
+
{ element: "H", position: [-0.63, 0.63, -0.63], color: "#ffffff" },
|
| 103 |
+
{ element: "H", position: [0.63, -0.63, -0.63], color: "#ffffff" }
|
| 104 |
+
],
|
| 105 |
+
bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 }],
|
| 106 |
+
description: "The simplest hydrocarbon and main component of natural gas, featuring a tetrahedral geometry."
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
name: "Ethane (C₂H₆)",
|
| 110 |
+
formula: "C2H6",
|
| 111 |
+
category: "hydrocarbons",
|
| 112 |
+
atoms: [
|
| 113 |
+
{ element: "C", position: [-0.77, 0, 0], color: "#404040" },
|
| 114 |
+
{ element: "C", position: [0.77, 0, 0], color: "#404040" },
|
| 115 |
+
{ element: "H", position: [-1.17, 0.89, 0.51], color: "#ffffff" },
|
| 116 |
+
{ element: "H", position: [-1.17, -0.89, 0.51], color: "#ffffff" },
|
| 117 |
+
{ element: "H", position: [-1.17, 0, -1.03], color: "#ffffff" },
|
| 118 |
+
{ element: "H", position: [1.17, 0.89, 0.51], color: "#ffffff" },
|
| 119 |
+
{ element: "H", position: [1.17, -0.89, 0.51], color: "#ffffff" },
|
| 120 |
+
{ element: "H", position: [1.17, 0, -1.03], color: "#ffffff" }
|
| 121 |
+
],
|
| 122 |
+
bonds: [
|
| 123 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 },
|
| 124 |
+
{ from: 1, to: 5 }, { from: 1, to: 6 }, { from: 1, to: 7 }
|
| 125 |
+
],
|
| 126 |
+
description: "A simple alkane used as fuel, with free rotation around the C-C single bond."
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
name: "Propane (C₃H₈)",
|
| 130 |
+
formula: "C3H8",
|
| 131 |
+
category: "hydrocarbons",
|
| 132 |
+
atoms: [
|
| 133 |
+
{ element: "C", position: [-1.27, 0, 0], color: "#404040" },
|
| 134 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 135 |
+
{ element: "C", position: [1.27, 0, 0], color: "#404040" },
|
| 136 |
+
{ element: "H", position: [-1.67, 0.89, 0.51], color: "#ffffff" },
|
| 137 |
+
{ element: "H", position: [-1.67, -0.89, 0.51], color: "#ffffff" },
|
| 138 |
+
{ element: "H", position: [-1.67, 0, -1.03], color: "#ffffff" },
|
| 139 |
+
{ element: "H", position: [0, 0.89, 0.51], color: "#ffffff" },
|
| 140 |
+
{ element: "H", position: [0, -0.89, 0.51], color: "#ffffff" },
|
| 141 |
+
{ element: "H", position: [1.67, 0.89, 0.51], color: "#ffffff" },
|
| 142 |
+
{ element: "H", position: [1.67, -0.89, 0.51], color: "#ffffff" },
|
| 143 |
+
{ element: "H", position: [1.67, 0, -1.03], color: "#ffffff" }
|
| 144 |
+
],
|
| 145 |
+
bonds: [
|
| 146 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 },
|
| 147 |
+
{ from: 0, to: 3 }, { from: 0, to: 4 }, { from: 0, to: 5 },
|
| 148 |
+
{ from: 1, to: 6 }, { from: 1, to: 7 },
|
| 149 |
+
{ from: 2, to: 8 }, { from: 2, to: 9 }, { from: 2, to: 10 }
|
| 150 |
+
],
|
| 151 |
+
description: "A common fuel gas used for heating, grilling, and as vehicle fuel (LPG)."
|
| 152 |
+
},
|
| 153 |
+
|
| 154 |
+
// Alkenes
|
| 155 |
+
{
|
| 156 |
+
name: "Ethene (C₂H₄)",
|
| 157 |
+
formula: "C2H4",
|
| 158 |
+
category: "hydrocarbons",
|
| 159 |
+
atoms: [
|
| 160 |
+
{ element: "C", position: [-0.67, 0, 0], color: "#404040" },
|
| 161 |
+
{ element: "C", position: [0.67, 0, 0], color: "#404040" },
|
| 162 |
+
{ element: "H", position: [-1.23, 0.93, 0], color: "#ffffff" },
|
| 163 |
+
{ element: "H", position: [-1.23, -0.93, 0], color: "#ffffff" },
|
| 164 |
+
{ element: "H", position: [1.23, 0.93, 0], color: "#ffffff" },
|
| 165 |
+
{ element: "H", position: [1.23, -0.93, 0], color: "#ffffff" }
|
| 166 |
+
],
|
| 167 |
+
bonds: [
|
| 168 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 },
|
| 169 |
+
{ from: 1, to: 4 }, { from: 1, to: 5 }
|
| 170 |
+
],
|
| 171 |
+
description: "The simplest alkene with a C=C double bond, used to make polyethylene plastic."
|
| 172 |
+
},
|
| 173 |
+
|
| 174 |
+
// Aromatic Compounds
|
| 175 |
+
{
|
| 176 |
+
name: "Benzene (C₆H₆)",
|
| 177 |
+
formula: "C6H6",
|
| 178 |
+
category: "aromatics",
|
| 179 |
+
atoms: [
|
| 180 |
+
{ element: "C", position: [1.40, 0, 0], color: "#404040" },
|
| 181 |
+
{ element: "C", position: [0.70, 1.21, 0], color: "#404040" },
|
| 182 |
+
{ element: "C", position: [-0.70, 1.21, 0], color: "#404040" },
|
| 183 |
+
{ element: "C", position: [-1.40, 0, 0], color: "#404040" },
|
| 184 |
+
{ element: "C", position: [-0.70, -1.21, 0], color: "#404040" },
|
| 185 |
+
{ element: "C", position: [0.70, -1.21, 0], color: "#404040" },
|
| 186 |
+
{ element: "H", position: [2.48, 0, 0], color: "#ffffff" },
|
| 187 |
+
{ element: "H", position: [1.24, 2.15, 0], color: "#ffffff" },
|
| 188 |
+
{ element: "H", position: [-1.24, 2.15, 0], color: "#ffffff" },
|
| 189 |
+
{ element: "H", position: [-2.48, 0, 0], color: "#ffffff" },
|
| 190 |
+
{ element: "H", position: [-1.24, -2.15, 0], color: "#ffffff" },
|
| 191 |
+
{ element: "H", position: [1.24, -2.15, 0], color: "#ffffff" }
|
| 192 |
+
],
|
| 193 |
+
bonds: [
|
| 194 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 },
|
| 195 |
+
{ from: 3, to: 4 }, { from: 4, to: 5 }, { from: 5, to: 0 },
|
| 196 |
+
{ from: 0, to: 6 }, { from: 1, to: 7 }, { from: 2, to: 8 },
|
| 197 |
+
{ from: 3, to: 9 }, { from: 4, to: 10 }, { from: 5, to: 11 }
|
| 198 |
+
],
|
| 199 |
+
description: "An aromatic hydrocarbon with a ring of six carbon atoms, fundamental in organic chemistry."
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
name: "Toluene (C₇H₈)",
|
| 203 |
+
formula: "C7H8",
|
| 204 |
+
category: "aromatics",
|
| 205 |
+
atoms: [
|
| 206 |
+
{ element: "C", position: [1.40, 0, 0], color: "#404040" },
|
| 207 |
+
{ element: "C", position: [0.70, 1.21, 0], color: "#404040" },
|
| 208 |
+
{ element: "C", position: [-0.70, 1.21, 0], color: "#404040" },
|
| 209 |
+
{ element: "C", position: [-1.40, 0, 0], color: "#404040" },
|
| 210 |
+
{ element: "C", position: [-0.70, -1.21, 0], color: "#404040" },
|
| 211 |
+
{ element: "C", position: [0.70, -1.21, 0], color: "#404040" },
|
| 212 |
+
{ element: "C", position: [-2.87, 0, 0], color: "#404040" }, // methyl group
|
| 213 |
+
{ element: "H", position: [2.48, 0, 0], color: "#ffffff" },
|
| 214 |
+
{ element: "H", position: [1.24, 2.15, 0], color: "#ffffff" },
|
| 215 |
+
{ element: "H", position: [-1.24, 2.15, 0], color: "#ffffff" },
|
| 216 |
+
{ element: "H", position: [-1.24, -2.15, 0], color: "#ffffff" },
|
| 217 |
+
{ element: "H", position: [1.24, -2.15, 0], color: "#ffffff" },
|
| 218 |
+
{ element: "H", position: [-3.27, 0.89, 0.51], color: "#ffffff" },
|
| 219 |
+
{ element: "H", position: [-3.27, -0.89, 0.51], color: "#ffffff" },
|
| 220 |
+
{ element: "H", position: [-3.27, 0, -1.03], color: "#ffffff" }
|
| 221 |
+
],
|
| 222 |
+
bonds: [
|
| 223 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 },
|
| 224 |
+
{ from: 3, to: 4 }, { from: 4, to: 5 }, { from: 5, to: 0 },
|
| 225 |
+
{ from: 3, to: 6 }, { from: 0, to: 7 }, { from: 1, to: 8 },
|
| 226 |
+
{ from: 2, to: 9 }, { from: 4, to: 10 }, { from: 5, to: 11 },
|
| 227 |
+
{ from: 6, to: 12 }, { from: 6, to: 13 }, { from: 6, to: 14 }
|
| 228 |
+
],
|
| 229 |
+
description: "Methylbenzene, used as a solvent and in the production of other chemicals."
|
| 230 |
+
},
|
| 231 |
+
|
| 232 |
+
// Alcohols
|
| 233 |
+
{
|
| 234 |
+
name: "Methanol (CH₄O)",
|
| 235 |
+
formula: "CH4O",
|
| 236 |
+
category: "alcohols",
|
| 237 |
+
atoms: [
|
| 238 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 239 |
+
{ element: "O", position: [1.43, 0, 0], color: "#ff0000" },
|
| 240 |
+
{ element: "H", position: [-0.51, 0.89, 0.51], color: "#ffffff" },
|
| 241 |
+
{ element: "H", position: [-0.51, -0.89, 0.51], color: "#ffffff" },
|
| 242 |
+
{ element: "H", position: [-0.51, 0, -1.03], color: "#ffffff" },
|
| 243 |
+
{ element: "H", position: [1.83, 0, 0.96], color: "#ffffff" }
|
| 244 |
+
],
|
| 245 |
+
bonds: [
|
| 246 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 },
|
| 247 |
+
{ from: 0, to: 4 }, { from: 1, to: 5 }
|
| 248 |
+
],
|
| 249 |
+
description: "The simplest alcohol, used as fuel additive and solvent. Highly toxic to humans."
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
name: "Ethanol (C₂H₆O)",
|
| 253 |
+
formula: "C2H6O",
|
| 254 |
+
category: "alcohols",
|
| 255 |
+
atoms: [
|
| 256 |
+
{ element: "C", position: [-0.77, 0.36, 0], color: "#404040" },
|
| 257 |
+
{ element: "C", position: [0.77, 0.36, 0], color: "#404040" },
|
| 258 |
+
{ element: "O", position: [1.2, -0.8, 0], color: "#ff0000" },
|
| 259 |
+
{ element: "H", position: [-1.1, 0.7, 1], color: "#ffffff" },
|
| 260 |
+
{ element: "H", position: [-1.1, 0.7, -1], color: "#ffffff" },
|
| 261 |
+
{ element: "H", position: [-1.2, -0.6, 0], color: "#ffffff" },
|
| 262 |
+
{ element: "H", position: [1.1, 1.3, 0], color: "#ffffff" },
|
| 263 |
+
{ element: "H", position: [1.1, -0.2, 1], color: "#ffffff" },
|
| 264 |
+
{ element: "H", position: [2.1, -0.6, 0], color: "#ffffff" }
|
| 265 |
+
],
|
| 266 |
+
bonds: [
|
| 267 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 },
|
| 268 |
+
{ from: 0, to: 3 }, { from: 0, to: 4 }, { from: 0, to: 5 },
|
| 269 |
+
{ from: 1, to: 6 }, { from: 1, to: 7 },
|
| 270 |
+
{ from: 2, to: 8 }
|
| 271 |
+
],
|
| 272 |
+
description: "The alcohol found in beverages, also used as fuel and solvent."
|
| 273 |
+
},
|
| 274 |
+
|
| 275 |
+
// Acids
|
| 276 |
+
{
|
| 277 |
+
name: "Formic Acid (HCOOH)",
|
| 278 |
+
formula: "HCOOH",
|
| 279 |
+
category: "acids",
|
| 280 |
+
atoms: [
|
| 281 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 282 |
+
{ element: "O", position: [1.23, -0.67, 0], color: "#ff0000" },
|
| 283 |
+
{ element: "O", position: [0.31, 1.34, 0], color: "#ff0000" },
|
| 284 |
+
{ element: "H", position: [-1.08, 0.11, 0], color: "#ffffff" },
|
| 285 |
+
{ element: "H", position: [1.19, 1.14, 0], color: "#ffffff" }
|
| 286 |
+
],
|
| 287 |
+
bonds: [
|
| 288 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 2, to: 4 }
|
| 289 |
+
],
|
| 290 |
+
description: "The simplest carboxylic acid, found in ant stings and used as a preservative."
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
name: "Acetic Acid (CH₃COOH)",
|
| 294 |
+
formula: "CH3COOH",
|
| 295 |
+
category: "acids",
|
| 296 |
+
atoms: [
|
| 297 |
+
{ element: "C", position: [-1.27, 0, 0], color: "#404040" },
|
| 298 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 299 |
+
{ element: "O", position: [1.23, -0.67, 0], color: "#ff0000" },
|
| 300 |
+
{ element: "O", position: [0.31, 1.34, 0], color: "#ff0000" },
|
| 301 |
+
{ element: "H", position: [-1.67, 0.89, 0.51], color: "#ffffff" },
|
| 302 |
+
{ element: "H", position: [-1.67, -0.89, 0.51], color: "#ffffff" },
|
| 303 |
+
{ element: "H", position: [-1.67, 0, -1.03], color: "#ffffff" },
|
| 304 |
+
{ element: "H", position: [1.19, 1.14, 0], color: "#ffffff" }
|
| 305 |
+
],
|
| 306 |
+
bonds: [
|
| 307 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 1, to: 3 },
|
| 308 |
+
{ from: 0, to: 4 }, { from: 0, to: 5 }, { from: 0, to: 6 }, { from: 3, to: 7 }
|
| 309 |
+
],
|
| 310 |
+
description: "Found in vinegar, used as food preservative and in chemical synthesis."
|
| 311 |
+
},
|
| 312 |
+
|
| 313 |
+
// Biomolecules - Amino Acids
|
| 314 |
+
{
|
| 315 |
+
name: "Glycine (NH₂CH₂COOH)",
|
| 316 |
+
formula: "C2H5NO2",
|
| 317 |
+
category: "biomolecules",
|
| 318 |
+
atoms: [
|
| 319 |
+
{ element: "N", position: [-2.14, 0, 0], color: "#0000ff" },
|
| 320 |
+
{ element: "C", position: [-0.67, 0, 0], color: "#404040" },
|
| 321 |
+
{ element: "C", position: [0.67, 0, 0], color: "#404040" },
|
| 322 |
+
{ element: "O", position: [1.23, -1.15, 0], color: "#ff0000" },
|
| 323 |
+
{ element: "O", position: [1.23, 1.15, 0], color: "#ff0000" },
|
| 324 |
+
{ element: "H", position: [-2.54, 0.81, 0.41], color: "#ffffff" },
|
| 325 |
+
{ element: "H", position: [-2.54, -0.81, 0.41], color: "#ffffff" },
|
| 326 |
+
{ element: "H", position: [-0.67, 0.89, 0.51], color: "#ffffff" },
|
| 327 |
+
{ element: "H", position: [-0.67, -0.89, 0.51], color: "#ffffff" },
|
| 328 |
+
{ element: "H", position: [2.17, 1.08, 0], color: "#ffffff" }
|
| 329 |
+
],
|
| 330 |
+
bonds: [
|
| 331 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 2, to: 4 },
|
| 332 |
+
{ from: 0, to: 5 }, { from: 0, to: 6 }, { from: 1, to: 7 }, { from: 1, to: 8 }, { from: 4, to: 9 }
|
| 333 |
+
],
|
| 334 |
+
description: "The simplest amino acid, essential for protein synthesis and neurotransmitter function."
|
| 335 |
+
},
|
| 336 |
+
|
| 337 |
+
// Sugars
|
| 338 |
+
{
|
| 339 |
+
name: "Glucose (C₆H₁₂O₆)",
|
| 340 |
+
formula: "C6H12O6",
|
| 341 |
+
category: "biomolecules",
|
| 342 |
+
atoms: [
|
| 343 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 344 |
+
{ element: "C", position: [1.25, 0.72, 0], color: "#404040" },
|
| 345 |
+
{ element: "C", position: [1.25, 2.17, 0], color: "#404040" },
|
| 346 |
+
{ element: "C", position: [0, 2.89, 0], color: "#404040" },
|
| 347 |
+
{ element: "C", position: [-1.25, 2.17, 0], color: "#404040" },
|
| 348 |
+
{ element: "O", position: [-1.25, 0.72, 0], color: "#ff0000" },
|
| 349 |
+
{ element: "O", position: [2.5, 0, 0], color: "#ff0000" },
|
| 350 |
+
{ element: "O", position: [2.5, 2.89, 0], color: "#ff0000" },
|
| 351 |
+
{ element: "O", position: [0, 4.34, 0], color: "#ff0000" },
|
| 352 |
+
{ element: "O", position: [-2.5, 2.89, 0], color: "#ff0000" },
|
| 353 |
+
{ element: "C", position: [-2.5, 4.34, 0], color: "#404040" },
|
| 354 |
+
{ element: "O", position: [-3.75, 5.06, 0], color: "#ff0000" }
|
| 355 |
+
],
|
| 356 |
+
bonds: [
|
| 357 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 },
|
| 358 |
+
{ from: 4, to: 5 }, { from: 5, to: 0 }, { from: 1, to: 6 }, { from: 2, to: 7 },
|
| 359 |
+
{ from: 3, to: 8 }, { from: 4, to: 9 }, { from: 9, to: 10 }, { from: 10, to: 11 }
|
| 360 |
+
],
|
| 361 |
+
description: "The primary source of energy for cells, essential for metabolism in all living organisms."
|
| 362 |
+
},
|
| 363 |
+
|
| 364 |
+
// Inorganic Compounds
|
| 365 |
+
{
|
| 366 |
+
name: "Hydrogen Chloride (HCl)",
|
| 367 |
+
formula: "HCl",
|
| 368 |
+
category: "inorganic",
|
| 369 |
+
atoms: [
|
| 370 |
+
{ element: "H", position: [-0.63, 0, 0], color: "#ffffff" },
|
| 371 |
+
{ element: "Cl", position: [0.63, 0, 0], color: "#00ff00" }
|
| 372 |
+
],
|
| 373 |
+
bonds: [{ from: 0, to: 1 }],
|
| 374 |
+
description: "A strong acid when dissolved in water (hydrochloric acid), found in stomach acid."
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
name: "Sodium Chloride (NaCl)",
|
| 378 |
+
formula: "NaCl",
|
| 379 |
+
category: "inorganic",
|
| 380 |
+
atoms: [
|
| 381 |
+
{ element: "Na", position: [-1.4, 0, 0], color: "#ab5cf2" },
|
| 382 |
+
{ element: "Cl", position: [1.4, 0, 0], color: "#00ff00" }
|
| 383 |
+
],
|
| 384 |
+
bonds: [{ from: 0, to: 1 }],
|
| 385 |
+
description: "Common table salt, essential for life and widely used in food preservation."
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
name: "Sulfuric Acid (H₂SO₄)",
|
| 389 |
+
formula: "H2SO4",
|
| 390 |
+
category: "inorganic",
|
| 391 |
+
atoms: [
|
| 392 |
+
{ element: "S", position: [0, 0, 0], color: "#ffff00" },
|
| 393 |
+
{ element: "O", position: [0, 1.5, 0], color: "#ff0000" },
|
| 394 |
+
{ element: "O", position: [1.3, -0.75, 0], color: "#ff0000" },
|
| 395 |
+
{ element: "O", position: [-1.3, -0.75, 0], color: "#ff0000" },
|
| 396 |
+
{ element: "O", position: [0, 0, 1.5], color: "#ff0000" },
|
| 397 |
+
{ element: "H", position: [0, 2.4, 0], color: "#ffffff" },
|
| 398 |
+
{ element: "H", position: [0, 0, 2.4], color: "#ffffff" }
|
| 399 |
+
],
|
| 400 |
+
bonds: [
|
| 401 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 },
|
| 402 |
+
{ from: 1, to: 5 }, { from: 4, to: 6 }
|
| 403 |
+
],
|
| 404 |
+
description: "One of the most important industrial chemicals, used in batteries and chemical synthesis."
|
| 405 |
+
},
|
| 406 |
+
|
| 407 |
+
// Pharmaceuticals
|
| 408 |
+
{
|
| 409 |
+
name: "Aspirin (C₉H₈O₄)",
|
| 410 |
+
formula: "C9H8O4",
|
| 411 |
+
category: "pharmaceuticals",
|
| 412 |
+
atoms: [
|
| 413 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 414 |
+
{ element: "C", position: [1.4, 0, 0.8], color: "#404040" },
|
| 415 |
+
{ element: "C", position: [1.4, 0, 2.2], color: "#404040" },
|
| 416 |
+
{ element: "C", position: [0, 0, 3], color: "#404040" },
|
| 417 |
+
{ element: "C", position: [-1.4, 0, 2.2], color: "#404040" },
|
| 418 |
+
{ element: "C", position: [-1.4, 0, 0.8], color: "#404040" },
|
| 419 |
+
{ element: "C", position: [0, 1.5, 4.5], color: "#404040" },
|
| 420 |
+
{ element: "C", position: [-1.5, 1.5, 6], color: "#404040" },
|
| 421 |
+
{ element: "O", position: [0, 0, 4.5], color: "#ff0000" },
|
| 422 |
+
{ element: "O", position: [1.5, 1.5, 4.5], color: "#ff0000" },
|
| 423 |
+
{ element: "O", position: [-3, 1.5, 6], color: "#ff0000" },
|
| 424 |
+
{ element: "O", position: [-1.5, 3, 6], color: "#ff0000" }
|
| 425 |
+
],
|
| 426 |
+
bonds: [
|
| 427 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 },
|
| 428 |
+
{ from: 4, to: 5 }, { from: 5, to: 0 }, { from: 3, to: 8 }, { from: 8, to: 6 },
|
| 429 |
+
{ from: 6, to: 9 }, { from: 6, to: 7 }, { from: 7, to: 10 }, { from: 7, to: 11 }
|
| 430 |
+
],
|
| 431 |
+
description: "A widely used pain reliever and anti-inflammatory drug, first synthesized in 1897."
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
name: "Caffeine (C₈H₁₀N₄O₂)",
|
| 435 |
+
formula: "C8H10N4O2",
|
| 436 |
+
category: "pharmaceuticals",
|
| 437 |
+
atoms: [
|
| 438 |
+
{ element: "N", position: [0, 0, 0], color: "#0000ff" },
|
| 439 |
+
{ element: "C", position: [1.4, 0, 0], color: "#404040" },
|
| 440 |
+
{ element: "N", position: [2.1, 1.2, 0], color: "#0000ff" },
|
| 441 |
+
{ element: "C", position: [1.4, 2.4, 0], color: "#404040" },
|
| 442 |
+
{ element: "C", position: [0, 2.4, 0], color: "#404040" },
|
| 443 |
+
{ element: "N", position: [-0.7, 1.2, 0], color: "#0000ff" },
|
| 444 |
+
{ element: "C", position: [0, 3.8, 0], color: "#404040" },
|
| 445 |
+
{ element: "N", position: [1.4, 3.8, 0], color: "#0000ff" },
|
| 446 |
+
{ element: "O", position: [2.1, -1.2, 0], color: "#ff0000" },
|
| 447 |
+
{ element: "O", position: [-0.7, 4.6, 0], color: "#ff0000" },
|
| 448 |
+
{ element: "C", position: [-2.1, 1.2, 0], color: "#404040" },
|
| 449 |
+
{ element: "C", position: [2.8, 4.6, 0], color: "#404040" },
|
| 450 |
+
{ element: "C", position: [-0.7, -1.2, 0], color: "#404040" }
|
| 451 |
+
],
|
| 452 |
+
bonds: [
|
| 453 |
+
{ from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 },
|
| 454 |
+
{ from: 4, to: 5 }, { from: 5, to: 0 }, { from: 4, to: 6 }, { from: 6, to: 7 },
|
| 455 |
+
{ from: 7, to: 3 }, { from: 1, to: 8 }, { from: 6, to: 9 }, { from: 5, to: 10 },
|
| 456 |
+
{ from: 7, to: 11 }, { from: 0, to: 12 }
|
| 457 |
+
],
|
| 458 |
+
description: "A natural stimulant found in coffee, tea, and chocolate, widely consumed worldwide."
|
| 459 |
+
},
|
| 460 |
+
|
| 461 |
+
// Environmental/Atmospheric
|
| 462 |
+
{
|
| 463 |
+
name: "Ozone (O₃)",
|
| 464 |
+
formula: "O3",
|
| 465 |
+
category: "environmental",
|
| 466 |
+
atoms: [
|
| 467 |
+
{ element: "O", position: [0, 0, 0], color: "#ff0000" },
|
| 468 |
+
{ element: "O", position: [1.3, 0, 0], color: "#ff0000" },
|
| 469 |
+
{ element: "O", position: [0.65, 1.13, 0], color: "#ff0000" }
|
| 470 |
+
],
|
| 471 |
+
bonds: [{ from: 0, to: 1 }, { from: 1, to: 2 }],
|
| 472 |
+
description: "An atmospheric molecule that protects Earth from UV radiation but is toxic at ground level."
|
| 473 |
+
},
|
| 474 |
+
{
|
| 475 |
+
name: "Methyl Chloride (CH₃Cl)",
|
| 476 |
+
formula: "CH3Cl",
|
| 477 |
+
category: "environmental",
|
| 478 |
+
atoms: [
|
| 479 |
+
{ element: "C", position: [0, 0, 0], color: "#404040" },
|
| 480 |
+
{ element: "Cl", position: [1.78, 0, 0], color: "#00ff00" },
|
| 481 |
+
{ element: "H", position: [-0.51, 0.89, 0.51], color: "#ffffff" },
|
| 482 |
+
{ element: "H", position: [-0.51, -0.89, 0.51], color: "#ffffff" },
|
| 483 |
+
{ element: "H", position: [-0.51, 0, -1.03], color: "#ffffff" }
|
| 484 |
+
],
|
| 485 |
+
bonds: [
|
| 486 |
+
{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 }
|
| 487 |
+
],
|
| 488 |
+
description: "A chlorinated compound that contributes to ozone depletion in the stratosphere."
|
| 489 |
+
},
|
| 490 |
+
|
| 491 |
+
// Additional Important Molecules
|
| 492 |
+
{
|
| 493 |
+
name: "Hydrogen Peroxide (H₂O₂)",
|
| 494 |
+
formula: "H2O2",
|
| 495 |
+
category: "simple",
|
| 496 |
+
atoms: [
|
| 497 |
+
{ element: "O", position: [-0.74, 0, 0], color: "#ff0000" },
|
| 498 |
+
{ element: "O", position: [0.74, 0, 0], color: "#ff0000" },
|
| 499 |
+
{ element: "H", position: [-1.21, 0.93, 0], color: "#ffffff" },
|
| 500 |
+
{ element: "H", position: [1.21, -0.93, 0], color: "#ffffff" }
|
| 501 |
+
],
|
| 502 |
+
bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 1, to: 3 }],
|
| 503 |
+
description: "A strong oxidizing agent used as disinfectant and bleaching agent."
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
name: "Nitrous Oxide (N₂O)",
|
| 507 |
+
formula: "N2O",
|
| 508 |
+
category: "simple",
|
| 509 |
+
atoms: [
|
| 510 |
+
{ element: "N", position: [-1.13, 0, 0], color: "#0000ff" },
|
| 511 |
+
{ element: "N", position: [0, 0, 0], color: "#0000ff" },
|
| 512 |
+
{ element: "O", position: [1.19, 0, 0], color: "#ff0000" }
|
| 513 |
+
],
|
| 514 |
+
bonds: [{ from: 0, to: 1 }, { from: 1, to: 2 }],
|
| 515 |
+
description: "Laughing gas, used as anesthetic and greenhouse gas contributing to climate change."
|
| 516 |
+
}
|
| 517 |
+
];
|
| 518 |
+
|
| 519 |
+
const CATEGORIES = [
|
| 520 |
+
{ value: "all", label: "All Categories", count: MOLECULES.length },
|
| 521 |
+
{ value: "simple", label: "Simple Molecules", count: MOLECULES.filter(m => m.category === "simple").length },
|
| 522 |
+
{ value: "hydrocarbons", label: "Hydrocarbons", count: MOLECULES.filter(m => m.category === "hydrocarbons").length },
|
| 523 |
+
{ value: "aromatics", label: "Aromatics", count: MOLECULES.filter(m => m.category === "aromatics").length },
|
| 524 |
+
{ value: "alcohols", label: "Alcohols", count: MOLECULES.filter(m => m.category === "alcohols").length },
|
| 525 |
+
{ value: "acids", label: "Acids", count: MOLECULES.filter(m => m.category === "acids").length },
|
| 526 |
+
{ value: "biomolecules", label: "Biomolecules", count: MOLECULES.filter(m => m.category === "biomolecules").length },
|
| 527 |
+
{ value: "inorganic", label: "Inorganic", count: MOLECULES.filter(m => m.category === "inorganic").length },
|
| 528 |
+
{ value: "pharmaceuticals", label: "Pharmaceuticals", count: MOLECULES.filter(m => m.category === "pharmaceuticals").length },
|
| 529 |
+
{ value: "environmental", label: "Environmental", count: MOLECULES.filter(m => m.category === "environmental").length }
|
| 530 |
+
];
|
| 531 |
+
|
| 532 |
+
const ThreeCanvas = ({ molecule, isAnimating }) => {
|
| 533 |
+
const mountRef = useRef(null);
|
| 534 |
+
|
| 535 |
+
useEffect(() => {
|
| 536 |
+
if (!molecule || !mountRef.current) return;
|
| 537 |
+
|
| 538 |
+
const currentMount = mountRef.current;
|
| 539 |
+
|
| 540 |
+
// Scene, Camera, Renderer
|
| 541 |
+
const scene = new THREE.Scene();
|
| 542 |
+
scene.background = new THREE.Color(0x1e293b); // slate-800
|
| 543 |
+
const camera = new THREE.PerspectiveCamera(75, currentMount.clientWidth / currentMount.clientHeight, 0.1, 1000);
|
| 544 |
+
camera.position.z = 5;
|
| 545 |
+
|
| 546 |
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
| 547 |
+
renderer.setSize(currentMount.clientWidth, currentMount.clientHeight);
|
| 548 |
+
currentMount.appendChild(renderer.domElement);
|
| 549 |
+
|
| 550 |
+
// Lights
|
| 551 |
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
|
| 552 |
+
scene.add(ambientLight);
|
| 553 |
+
const pointLight = new THREE.PointLight(0xffffff, 0.5);
|
| 554 |
+
pointLight.position.set(5, 5, 5);
|
| 555 |
+
scene.add(pointLight);
|
| 556 |
+
|
| 557 |
+
// Molecule Group
|
| 558 |
+
const moleculeGroup = new THREE.Group();
|
| 559 |
+
scene.add(moleculeGroup);
|
| 560 |
+
|
| 561 |
+
// Create Atoms
|
| 562 |
+
molecule.atoms.forEach(atom => {
|
| 563 |
+
const geometry = new THREE.SphereGeometry(atom.element === 'H' ? 0.2 : 0.4, 32, 32);
|
| 564 |
+
const material = new THREE.MeshStandardMaterial({ color: atom.color });
|
| 565 |
+
const sphere = new THREE.Mesh(geometry, material);
|
| 566 |
+
sphere.position.set(...atom.position);
|
| 567 |
+
moleculeGroup.add(sphere);
|
| 568 |
+
});
|
| 569 |
+
|
| 570 |
+
// Create Bonds
|
| 571 |
+
molecule.bonds?.forEach(bond => {
|
| 572 |
+
const start = new THREE.Vector3(...molecule.atoms[bond.from].position);
|
| 573 |
+
const end = new THREE.Vector3(...molecule.atoms[bond.to].position);
|
| 574 |
+
const path = new THREE.LineCurve3(start, end);
|
| 575 |
+
const geometry = new THREE.TubeGeometry(path, 1, 0.05, 8, false);
|
| 576 |
+
const material = new THREE.MeshStandardMaterial({ color: '#cccccc' });
|
| 577 |
+
const mesh = new THREE.Mesh(geometry, material);
|
| 578 |
+
moleculeGroup.add(mesh);
|
| 579 |
+
});
|
| 580 |
+
|
| 581 |
+
// Center the molecule
|
| 582 |
+
new THREE.Box3().setFromObject(moleculeGroup).getCenter(moleculeGroup.position).multiplyScalar(-1);
|
| 583 |
+
|
| 584 |
+
// Animation loop
|
| 585 |
+
let animationFrameId;
|
| 586 |
+
const animate = () => {
|
| 587 |
+
animationFrameId = requestAnimationFrame(animate);
|
| 588 |
+
if (isAnimating) {
|
| 589 |
+
moleculeGroup.rotation.y += 0.005;
|
| 590 |
+
moleculeGroup.rotation.x += 0.002;
|
| 591 |
+
}
|
| 592 |
+
renderer.render(scene, camera);
|
| 593 |
+
};
|
| 594 |
+
animate();
|
| 595 |
+
|
| 596 |
+
// Resize handler
|
| 597 |
+
const handleResize = () => {
|
| 598 |
+
if (!currentMount) return;
|
| 599 |
+
camera.aspect = currentMount.clientWidth / currentMount.clientHeight;
|
| 600 |
+
camera.updateProjectionMatrix();
|
| 601 |
+
renderer.setSize(currentMount.clientWidth, currentMount.clientHeight);
|
| 602 |
+
};
|
| 603 |
+
window.addEventListener('resize', handleResize);
|
| 604 |
+
|
| 605 |
+
// Cleanup
|
| 606 |
+
return () => {
|
| 607 |
+
window.removeEventListener('resize', handleResize);
|
| 608 |
+
if (currentMount.contains(renderer.domElement)) {
|
| 609 |
+
currentMount.removeChild(renderer.domElement);
|
| 610 |
+
}
|
| 611 |
+
cancelAnimationFrame(animationFrameId);
|
| 612 |
+
};
|
| 613 |
+
}, [molecule, isAnimating]);
|
| 614 |
+
|
| 615 |
+
return <div ref={mountRef} className="w-full h-full" />;
|
| 616 |
+
};
|
| 617 |
+
|
| 618 |
+
export default function MolecularViewer() {
|
| 619 |
+
const [selectedMolecule, setSelectedMolecule] = useState(MOLECULES[0]);
|
| 620 |
+
const [isAnimating, setIsAnimating] = useState(true);
|
| 621 |
+
const [searchTerm, setSearchTerm] = useState("");
|
| 622 |
+
const [selectedCategory, setSelectedCategory] = useState("all");
|
| 623 |
+
|
| 624 |
+
const filteredMolecules = MOLECULES.filter(mol => {
|
| 625 |
+
const matchesSearch = mol.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
| 626 |
+
mol.formula.toLowerCase().includes(searchTerm.toLowerCase());
|
| 627 |
+
const matchesCategory = selectedCategory === "all" || mol.category === selectedCategory;
|
| 628 |
+
return matchesSearch && matchesCategory;
|
| 629 |
+
});
|
| 630 |
+
|
| 631 |
+
return (
|
| 632 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-purple-50 p-4 md:p-8">
|
| 633 |
+
<div className="max-w-7xl mx-auto">
|
| 634 |
+
<div className="mb-8">
|
| 635 |
+
<div className="flex items-center gap-3 mb-4">
|
| 636 |
+
<div className="w-12 h-12 bg-gradient-to-br from-purple-600 to-pink-700 rounded-xl flex items-center justify-center">
|
| 637 |
+
<Atom className="w-6 h-6 text-white" />
|
| 638 |
+
</div>
|
| 639 |
+
<div>
|
| 640 |
+
<h1 className="text-3xl font-bold text-slate-900">3D Molecular Database</h1>
|
| 641 |
+
<p className="text-slate-600">Explore {MOLECULES.length} molecular structures in interactive 3D space</p>
|
| 642 |
+
</div>
|
| 643 |
+
</div>
|
| 644 |
+
</div>
|
| 645 |
+
|
| 646 |
+
<div className="grid lg:grid-cols-4 gap-8">
|
| 647 |
+
{/* Molecule Library */}
|
| 648 |
+
<div className="lg:col-span-1">
|
| 649 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 650 |
+
<CardHeader>
|
| 651 |
+
<CardTitle className="flex items-center gap-2 text-lg">
|
| 652 |
+
<Search className="w-5 h-5 text-purple-600" />
|
| 653 |
+
Molecule Library
|
| 654 |
+
</CardTitle>
|
| 655 |
+
</CardHeader>
|
| 656 |
+
<CardContent>
|
| 657 |
+
<div className="space-y-4">
|
| 658 |
+
<Input
|
| 659 |
+
placeholder="Search molecules..."
|
| 660 |
+
value={searchTerm}
|
| 661 |
+
onChange={(e) => setSearchTerm(e.target.value)}
|
| 662 |
+
className="bg-white"
|
| 663 |
+
/>
|
| 664 |
+
|
| 665 |
+
<div>
|
| 666 |
+
<label className="block text-sm font-medium text-slate-700 mb-2">Category</label>
|
| 667 |
+
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
| 668 |
+
<SelectTrigger className="bg-white">
|
| 669 |
+
<SelectValue />
|
| 670 |
+
</SelectTrigger>
|
| 671 |
+
<SelectContent>
|
| 672 |
+
{CATEGORIES.map((category) => (
|
| 673 |
+
<SelectItem key={category.value} value={category.value}>
|
| 674 |
+
{category.label} ({category.count})
|
| 675 |
+
</SelectItem>
|
| 676 |
+
))}
|
| 677 |
+
</SelectContent>
|
| 678 |
+
</Select>
|
| 679 |
+
</div>
|
| 680 |
+
|
| 681 |
+
<div className="space-y-2 max-h-96 overflow-y-auto pr-2">
|
| 682 |
+
{filteredMolecules.map((molecule, index) => (
|
| 683 |
+
<button
|
| 684 |
+
key={index}
|
| 685 |
+
onClick={() => setSelectedMolecule(molecule)}
|
| 686 |
+
className={`w-full text-left p-3 rounded-lg border transition-all duration-200 ${
|
| 687 |
+
selectedMolecule?.name === molecule.name
|
| 688 |
+
? 'border-purple-500 bg-purple-50'
|
| 689 |
+
: 'border-slate-200 hover:border-purple-300 hover:bg-purple-25'
|
| 690 |
+
}`}
|
| 691 |
+
>
|
| 692 |
+
<div className="font-medium text-slate-900 mb-1">
|
| 693 |
+
{molecule.name}
|
| 694 |
+
</div>
|
| 695 |
+
<div className="flex items-center justify-between">
|
| 696 |
+
<Badge variant="outline" className="text-xs">
|
| 697 |
+
{molecule.formula}
|
| 698 |
+
</Badge>
|
| 699 |
+
<Badge variant="secondary" className="text-xs capitalize">
|
| 700 |
+
{molecule.category}
|
| 701 |
+
</Badge>
|
| 702 |
+
</div>
|
| 703 |
+
</button>
|
| 704 |
+
))}
|
| 705 |
+
{filteredMolecules.length === 0 && (
|
| 706 |
+
<div className="text-center py-8 text-slate-500">
|
| 707 |
+
<Search className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
| 708 |
+
<p>No molecules found</p>
|
| 709 |
+
</div>
|
| 710 |
+
)}
|
| 711 |
+
</div>
|
| 712 |
+
</div>
|
| 713 |
+
</CardContent>
|
| 714 |
+
</Card>
|
| 715 |
+
</div>
|
| 716 |
+
|
| 717 |
+
{/* Main Viewer */}
|
| 718 |
+
<div className="lg:col-span-3">
|
| 719 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 720 |
+
<CardHeader>
|
| 721 |
+
<div className="flex items-center justify-between flex-wrap gap-4">
|
| 722 |
+
<CardTitle className="text-xl">
|
| 723 |
+
{selectedMolecule?.name}
|
| 724 |
+
</CardTitle>
|
| 725 |
+
<div className="flex items-center gap-2">
|
| 726 |
+
<Button
|
| 727 |
+
variant="outline"
|
| 728 |
+
size="icon"
|
| 729 |
+
onClick={() => setIsAnimating(!isAnimating)}
|
| 730 |
+
title={isAnimating ? "Pause Animation" : "Play Animation"}
|
| 731 |
+
>
|
| 732 |
+
{isAnimating ? (
|
| 733 |
+
<Pause className="w-4 h-4" />
|
| 734 |
+
) : (
|
| 735 |
+
<Play className="w-4 h-4" />
|
| 736 |
+
)}
|
| 737 |
+
</Button>
|
| 738 |
+
</div>
|
| 739 |
+
</div>
|
| 740 |
+
</CardHeader>
|
| 741 |
+
<CardContent>
|
| 742 |
+
<Tabs defaultValue="viewer" className="w-full">
|
| 743 |
+
<TabsList className="grid w-full grid-cols-2 mb-6">
|
| 744 |
+
<TabsTrigger value="viewer">3D Viewer</TabsTrigger>
|
| 745 |
+
<TabsTrigger value="info">Molecule Info</TabsTrigger>
|
| 746 |
+
</TabsList>
|
| 747 |
+
|
| 748 |
+
<TabsContent value="viewer" className="space-y-0">
|
| 749 |
+
<div className="h-96 w-full bg-gradient-to-b from-slate-900 to-slate-800 rounded-xl overflow-hidden relative">
|
| 750 |
+
<ThreeCanvas molecule={selectedMolecule} isAnimating={isAnimating} />
|
| 751 |
+
|
| 752 |
+
<div className="absolute top-4 right-4">
|
| 753 |
+
<Badge className="bg-purple-600 text-white">
|
| 754 |
+
{selectedMolecule?.formula}
|
| 755 |
+
</Badge>
|
| 756 |
+
</div>
|
| 757 |
+
</div>
|
| 758 |
+
</TabsContent>
|
| 759 |
+
|
| 760 |
+
<TabsContent value="info" className="space-y-6">
|
| 761 |
+
<div className="grid md:grid-cols-2 gap-6">
|
| 762 |
+
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-200">
|
| 763 |
+
<CardContent className="p-6">
|
| 764 |
+
<div className="flex items-center gap-2 mb-3">
|
| 765 |
+
<Info className="w-5 h-5 text-blue-600" />
|
| 766 |
+
<h3 className="font-semibold text-blue-900">Molecular Formula</h3>
|
| 767 |
+
</div>
|
| 768 |
+
<p className="text-2xl font-bold text-blue-800 font-mono">
|
| 769 |
+
{selectedMolecule?.formula}
|
| 770 |
+
</p>
|
| 771 |
+
</CardContent>
|
| 772 |
+
</Card>
|
| 773 |
+
|
| 774 |
+
<Card className="bg-gradient-to-br from-emerald-50 to-teal-50 border-emerald-200">
|
| 775 |
+
<CardContent className="p-6">
|
| 776 |
+
<div className="flex items-center gap-2 mb-3">
|
| 777 |
+
<Atom className="w-5 h-5 text-emerald-600" />
|
| 778 |
+
<h3 className="font-semibold text-emerald-900">Atom Count</h3>
|
| 779 |
+
</div>
|
| 780 |
+
<p className="text-2xl font-bold text-emerald-800">
|
| 781 |
+
{selectedMolecule?.atoms.length} atoms
|
| 782 |
+
</p>
|
| 783 |
+
</CardContent>
|
| 784 |
+
</Card>
|
| 785 |
+
</div>
|
| 786 |
+
|
| 787 |
+
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-purple-200">
|
| 788 |
+
<CardContent className="p-6">
|
| 789 |
+
<div className="flex items-center gap-2 mb-3">
|
| 790 |
+
<Filter className="w-5 h-5 text-purple-600" />
|
| 791 |
+
<h3 className="font-semibold text-purple-900">Category</h3>
|
| 792 |
+
</div>
|
| 793 |
+
<Badge className="capitalize bg-purple-600 text-white text-lg px-3 py-1">
|
| 794 |
+
{selectedMolecule?.category}
|
| 795 |
+
</Badge>
|
| 796 |
+
</CardContent>
|
| 797 |
+
</Card>
|
| 798 |
+
|
| 799 |
+
<Card className="bg-slate-50 border-slate-200">
|
| 800 |
+
<CardHeader>
|
| 801 |
+
<CardTitle className="text-lg">Description</CardTitle>
|
| 802 |
+
</CardHeader>
|
| 803 |
+
<CardContent>
|
| 804 |
+
<p className="text-slate-700 leading-relaxed">
|
| 805 |
+
{selectedMolecule?.description}
|
| 806 |
+
</p>
|
| 807 |
+
</CardContent>
|
| 808 |
+
</Card>
|
| 809 |
+
|
| 810 |
+
{selectedMolecule?.atoms && (
|
| 811 |
+
<Card className="bg-slate-50 border-slate-200">
|
| 812 |
+
<CardHeader>
|
| 813 |
+
<CardTitle className="text-lg">Atomic Composition</CardTitle>
|
| 814 |
+
</CardHeader>
|
| 815 |
+
<CardContent>
|
| 816 |
+
<div className="space-y-3">
|
| 817 |
+
{Object.entries(
|
| 818 |
+
selectedMolecule.atoms.reduce((acc, atom) => {
|
| 819 |
+
acc[atom.element] = (acc[atom.element] || 0) + 1;
|
| 820 |
+
return acc;
|
| 821 |
+
}, {})
|
| 822 |
+
).map(([element, count]) => (
|
| 823 |
+
<div key={element} className="flex items-center gap-3">
|
| 824 |
+
<div
|
| 825 |
+
className="w-8 h-8 rounded-full border-2 border-slate-300 flex items-center justify-center text-xs font-bold"
|
| 826 |
+
style={{
|
| 827 |
+
backgroundColor: selectedMolecule.atoms.find(a => a.element === element)?.color,
|
| 828 |
+
color: element === 'H' ? '#000' : '#fff'
|
| 829 |
+
}}
|
| 830 |
+
>
|
| 831 |
+
{element}
|
| 832 |
+
</div>
|
| 833 |
+
<span className="font-medium text-slate-900">{element}</span>
|
| 834 |
+
<span className="text-slate-600">× {count}</span>
|
| 835 |
+
</div>
|
| 836 |
+
))}
|
| 837 |
+
</div>
|
| 838 |
+
</CardContent>
|
| 839 |
+
</Card>
|
| 840 |
+
)}
|
| 841 |
+
</TabsContent>
|
| 842 |
+
</Tabs>
|
| 843 |
+
</CardContent>
|
| 844 |
+
</Card>
|
| 845 |
+
</div>
|
| 846 |
+
</div>
|
| 847 |
+
</div>
|
| 848 |
+
</div>
|
| 849 |
+
);
|
| 850 |
+
}
|
Pages/Items/Prctice.txt
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useState, useEffect } from "react";
|
| 3 |
+
import { Problem } from "@/entities/Problem";
|
| 4 |
+
import { StudySession } from "@/entities/StudySession";
|
| 5 |
+
import { User } from "@/entities/User";
|
| 6 |
+
import { InvokeLLM } from "@/integrations/Core";
|
| 7 |
+
import { Button } from "@/components/ui/button";
|
| 8 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 9 |
+
import { Badge } from "@/components/ui/badge";
|
| 10 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 11 |
+
import { Textarea } from "@/components/ui/textarea";
|
| 12 |
+
import { Alert, AlertDescription } from "@/components/ui/alert";
|
| 13 |
+
import {
|
| 14 |
+
Brain,
|
| 15 |
+
Lightbulb,
|
| 16 |
+
CheckCircle,
|
| 17 |
+
XCircle,
|
| 18 |
+
RefreshCw,
|
| 19 |
+
Sparkles,
|
| 20 |
+
Clock,
|
| 21 |
+
Target,
|
| 22 |
+
ChevronRight,
|
| 23 |
+
Atom,
|
| 24 |
+
BookOpen
|
| 25 |
+
} from "lucide-react";
|
| 26 |
+
|
| 27 |
+
const TOPICS = [
|
| 28 |
+
{ value: "organic_chemistry", label: "Organic Chemistry" },
|
| 29 |
+
{ value: "inorganic_chemistry", label: "Inorganic Chemistry" },
|
| 30 |
+
{ value: "physical_chemistry", label: "Physical Chemistry" },
|
| 31 |
+
{ value: "analytical_chemistry", label: "Analytical Chemistry" },
|
| 32 |
+
{ value: "biochemistry", label: "Biochemistry" },
|
| 33 |
+
{ value: "quantum_chemistry", label: "Quantum Chemistry" },
|
| 34 |
+
{ value: "materials_science", label: "Materials Science" }
|
| 35 |
+
];
|
| 36 |
+
|
| 37 |
+
const DIFFICULTIES = [
|
| 38 |
+
{ value: "beginner", label: "Beginner", color: "bg-green-100 text-green-800" },
|
| 39 |
+
{ value: "intermediate", label: "Intermediate", color: "bg-yellow-100 text-yellow-800" },
|
| 40 |
+
{ value: "advanced", label: "Advanced", color: "bg-red-100 text-red-800" }
|
| 41 |
+
];
|
| 42 |
+
|
| 43 |
+
export default function Practice() {
|
| 44 |
+
const [user, setUser] = useState(null);
|
| 45 |
+
const [currentProblem, setCurrentProblem] = useState(null);
|
| 46 |
+
const [userAnswer, setUserAnswer] = useState("");
|
| 47 |
+
const [showSolution, setShowSolution] = useState(false);
|
| 48 |
+
const [showHints, setShowHints] = useState(false);
|
| 49 |
+
const [hintIndex, setHintIndex] = useState(0);
|
| 50 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 51 |
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 52 |
+
const [sessionStartTime, setSessionStartTime] = useState(null);
|
| 53 |
+
const [selectedTopic, setSelectedTopic] = useState("");
|
| 54 |
+
const [selectedDifficulty, setSelectedDifficulty] = useState("");
|
| 55 |
+
const [score, setScore] = useState(null);
|
| 56 |
+
|
| 57 |
+
useEffect(() => {
|
| 58 |
+
loadUser();
|
| 59 |
+
}, []);
|
| 60 |
+
|
| 61 |
+
const loadUser = async () => {
|
| 62 |
+
try {
|
| 63 |
+
const userData = await User.me();
|
| 64 |
+
setUser(userData);
|
| 65 |
+
setSelectedDifficulty(userData.learning_level || "beginner");
|
| 66 |
+
} catch (error) {
|
| 67 |
+
await User.login();
|
| 68 |
+
}
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
const generateProblem = async () => {
|
| 72 |
+
if (!selectedTopic || !selectedDifficulty) return;
|
| 73 |
+
|
| 74 |
+
setIsGenerating(true);
|
| 75 |
+
setCurrentProblem(null);
|
| 76 |
+
setUserAnswer("");
|
| 77 |
+
setShowSolution(false);
|
| 78 |
+
setShowHints(false);
|
| 79 |
+
setHintIndex(0);
|
| 80 |
+
setScore(null);
|
| 81 |
+
setSessionStartTime(Date.now());
|
| 82 |
+
|
| 83 |
+
try {
|
| 84 |
+
const topicName = TOPICS.find(t => t.value === selectedTopic)?.label || selectedTopic;
|
| 85 |
+
|
| 86 |
+
const prompt = `Generate a ${selectedDifficulty} level chemistry problem in ${topicName}.
|
| 87 |
+
|
| 88 |
+
The problem should be educational, engaging, and appropriate for a student at the ${selectedDifficulty} level.
|
| 89 |
+
|
| 90 |
+
Include:
|
| 91 |
+
- A clear, specific problem statement
|
| 92 |
+
- Step-by-step solution with explanations
|
| 93 |
+
- 3 progressive hints to help students
|
| 94 |
+
- Key concepts being tested
|
| 95 |
+
- Molecular formula if applicable
|
| 96 |
+
- Brief molecular structure description if relevant
|
| 97 |
+
|
| 98 |
+
Make it practical and relatable to real-world chemistry applications.`;
|
| 99 |
+
|
| 100 |
+
const result = await InvokeLLM({
|
| 101 |
+
prompt,
|
| 102 |
+
response_json_schema: {
|
| 103 |
+
type: "object",
|
| 104 |
+
properties: {
|
| 105 |
+
title: { type: "string" },
|
| 106 |
+
question: { type: "string" },
|
| 107 |
+
solution: { type: "string" },
|
| 108 |
+
molecular_formula: { type: "string" },
|
| 109 |
+
molecular_structure: { type: "string" },
|
| 110 |
+
concepts: { type: "array", items: { type: "string" } },
|
| 111 |
+
hints: { type: "array", items: { type: "string" } }
|
| 112 |
+
},
|
| 113 |
+
required: ["title", "question", "solution", "concepts", "hints"]
|
| 114 |
+
}
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
const problemData = {
|
| 118 |
+
...result,
|
| 119 |
+
topic: selectedTopic,
|
| 120 |
+
difficulty: selectedDifficulty
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
const savedProblem = await Problem.create(problemData);
|
| 124 |
+
setCurrentProblem(savedProblem);
|
| 125 |
+
|
| 126 |
+
} catch (error) {
|
| 127 |
+
console.error("Error generating problem:", error);
|
| 128 |
+
}
|
| 129 |
+
setIsGenerating(false);
|
| 130 |
+
};
|
| 131 |
+
|
| 132 |
+
const submitAnswer = async () => {
|
| 133 |
+
if (!currentProblem || !userAnswer.trim()) return;
|
| 134 |
+
|
| 135 |
+
setIsSubmitting(true);
|
| 136 |
+
const timeSpent = sessionStartTime ? Math.round((Date.now() - sessionStartTime) / 1000 / 60) : 0;
|
| 137 |
+
|
| 138 |
+
try {
|
| 139 |
+
// Use AI to evaluate the answer
|
| 140 |
+
const evaluation = await InvokeLLM({
|
| 141 |
+
prompt: `Evaluate this student's answer to a chemistry problem:
|
| 142 |
+
|
| 143 |
+
Problem: ${currentProblem.question}
|
| 144 |
+
Correct Solution: ${currentProblem.solution}
|
| 145 |
+
Student Answer: ${userAnswer}
|
| 146 |
+
|
| 147 |
+
Provide a score from 0-100 and brief feedback on their understanding.`,
|
| 148 |
+
response_json_schema: {
|
| 149 |
+
type: "object",
|
| 150 |
+
properties: {
|
| 151 |
+
score: { type: "number", minimum: 0, maximum: 100 },
|
| 152 |
+
feedback: { type: "string" },
|
| 153 |
+
is_correct: { type: "boolean" }
|
| 154 |
+
},
|
| 155 |
+
required: ["score", "feedback", "is_correct"]
|
| 156 |
+
}
|
| 157 |
+
});
|
| 158 |
+
|
| 159 |
+
setScore(evaluation);
|
| 160 |
+
|
| 161 |
+
// Save study session
|
| 162 |
+
await StudySession.create({
|
| 163 |
+
user_email: user.email,
|
| 164 |
+
problem_id: currentProblem.id,
|
| 165 |
+
completed: evaluation.is_correct,
|
| 166 |
+
time_spent: timeSpent,
|
| 167 |
+
hints_used: hintIndex,
|
| 168 |
+
score: evaluation.score,
|
| 169 |
+
session_date: new Date().toISOString().split('T')[0]
|
| 170 |
+
});
|
| 171 |
+
|
| 172 |
+
// Update user stats if correct
|
| 173 |
+
if (evaluation.is_correct) {
|
| 174 |
+
await User.updateMyUserData({
|
| 175 |
+
total_problems_solved: (user.total_problems_solved || 0) + 1
|
| 176 |
+
});
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
setShowSolution(true);
|
| 180 |
+
|
| 181 |
+
} catch (error) {
|
| 182 |
+
console.error("Error submitting answer:", error);
|
| 183 |
+
}
|
| 184 |
+
setIsSubmitting(false);
|
| 185 |
+
};
|
| 186 |
+
|
| 187 |
+
const nextHint = () => {
|
| 188 |
+
if (hintIndex < (currentProblem?.hints?.length || 0) - 1) {
|
| 189 |
+
setHintIndex(hintIndex + 1);
|
| 190 |
+
}
|
| 191 |
+
setShowHints(true);
|
| 192 |
+
};
|
| 193 |
+
|
| 194 |
+
return (
|
| 195 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-4 md:p-8">
|
| 196 |
+
<div className="max-w-4xl mx-auto">
|
| 197 |
+
<div className="mb-8">
|
| 198 |
+
<div className="flex items-center gap-3 mb-4">
|
| 199 |
+
<div className="w-12 h-12 bg-gradient-to-br from-blue-600 to-indigo-700 rounded-xl flex items-center justify-center">
|
| 200 |
+
<Brain className="w-6 h-6 text-white" />
|
| 201 |
+
</div>
|
| 202 |
+
<div>
|
| 203 |
+
<h1 className="text-3xl font-bold text-slate-900">AI Practice Problems</h1>
|
| 204 |
+
<p className="text-slate-600">Personalized chemistry problems generated just for you</p>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
{/* Problem Generator */}
|
| 210 |
+
<Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm mb-8">
|
| 211 |
+
<CardHeader>
|
| 212 |
+
<CardTitle className="flex items-center gap-2">
|
| 213 |
+
<Sparkles className="w-5 h-5 text-amber-500" />
|
| 214 |
+
Generate New Problem
|
| 215 |
+
</CardTitle>
|
| 216 |
+
</CardHeader>
|
| 217 |
+
<CardContent>
|
| 218 |
+
<div className="grid md:grid-cols-2 gap-4 mb-6">
|
| 219 |
+
<div>
|
| 220 |
+
<label className="block text-sm font-medium text-slate-700 mb-2">Topic</label>
|
| 221 |
+
<Select value={selectedTopic} onValueChange={setSelectedTopic}>
|
| 222 |
+
<SelectTrigger className="bg-white">
|
| 223 |
+
<SelectValue placeholder="Choose a chemistry topic" />
|
| 224 |
+
</SelectTrigger>
|
| 225 |
+
<SelectContent>
|
| 226 |
+
{TOPICS.map((topic) => (
|
| 227 |
+
<SelectItem key={topic.value} value={topic.value}>
|
| 228 |
+
{topic.label}
|
| 229 |
+
</SelectItem>
|
| 230 |
+
))}
|
| 231 |
+
</SelectContent>
|
| 232 |
+
</Select>
|
| 233 |
+
</div>
|
| 234 |
+
<div>
|
| 235 |
+
<label className="block text-sm font-medium text-slate-700 mb-2">Difficulty</label>
|
| 236 |
+
<Select value={selectedDifficulty} onValueChange={setSelectedDifficulty}>
|
| 237 |
+
<SelectTrigger className="bg-white">
|
| 238 |
+
<SelectValue placeholder="Select difficulty" />
|
| 239 |
+
</SelectTrigger>
|
| 240 |
+
<SelectContent>
|
| 241 |
+
{DIFFICULTIES.map((diff) => (
|
| 242 |
+
<SelectItem key={diff.value} value={diff.value}>
|
| 243 |
+
{diff.label}
|
| 244 |
+
</SelectItem>
|
| 245 |
+
))}
|
| 246 |
+
</SelectContent>
|
| 247 |
+
</Select>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
<Button
|
| 251 |
+
onClick={generateProblem}
|
| 252 |
+
disabled={!selectedTopic || !selectedDifficulty || isGenerating}
|
| 253 |
+
className="w-full bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 font-semibold py-3"
|
| 254 |
+
>
|
| 255 |
+
{isGenerating ? (
|
| 256 |
+
<>
|
| 257 |
+
<RefreshCw className="w-5 h-5 mr-2 animate-spin" />
|
| 258 |
+
Generating Problem...
|
| 259 |
+
</>
|
| 260 |
+
) : (
|
| 261 |
+
<>
|
| 262 |
+
<Brain className="w-5 h-5 mr-2" />
|
| 263 |
+
Generate Problem
|
| 264 |
+
</>
|
| 265 |
+
)}
|
| 266 |
+
</Button>
|
| 267 |
+
</CardContent>
|
| 268 |
+
</Card>
|
| 269 |
+
|
| 270 |
+
{/* Current Problem */}
|
| 271 |
+
{currentProblem && (
|
| 272 |
+
<div className="space-y-6">
|
| 273 |
+
<Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm">
|
| 274 |
+
<CardHeader>
|
| 275 |
+
<div className="flex items-center justify-between flex-wrap gap-4">
|
| 276 |
+
<CardTitle className="text-xl">{currentProblem.title}</CardTitle>
|
| 277 |
+
<div className="flex gap-2">
|
| 278 |
+
<Badge className={DIFFICULTIES.find(d => d.value === currentProblem.difficulty)?.color}>
|
| 279 |
+
{currentProblem.difficulty}
|
| 280 |
+
</Badge>
|
| 281 |
+
<Badge variant="outline">
|
| 282 |
+
{TOPICS.find(t => t.value === currentProblem.topic)?.label}
|
| 283 |
+
</Badge>
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
</CardHeader>
|
| 287 |
+
<CardContent>
|
| 288 |
+
<div className="prose max-w-none mb-6">
|
| 289 |
+
<p className="text-lg leading-relaxed text-slate-700">{currentProblem.question}</p>
|
| 290 |
+
</div>
|
| 291 |
+
|
| 292 |
+
{currentProblem.molecular_formula && (
|
| 293 |
+
<div className="bg-slate-50 rounded-lg p-4 mb-6">
|
| 294 |
+
<div className="flex items-center gap-2 mb-2">
|
| 295 |
+
<Atom className="w-5 h-5 text-blue-600" />
|
| 296 |
+
<span className="font-medium text-slate-700">Molecular Formula:</span>
|
| 297 |
+
</div>
|
| 298 |
+
<code className="text-lg font-mono text-blue-700 bg-white px-3 py-1 rounded">
|
| 299 |
+
{currentProblem.molecular_formula}
|
| 300 |
+
</code>
|
| 301 |
+
</div>
|
| 302 |
+
)}
|
| 303 |
+
|
| 304 |
+
{currentProblem.concepts && (
|
| 305 |
+
<div className="mb-6">
|
| 306 |
+
<div className="flex items-center gap-2 mb-3">
|
| 307 |
+
<BookOpen className="w-5 h-5 text-purple-600" />
|
| 308 |
+
<span className="font-medium text-slate-700">Key Concepts:</span>
|
| 309 |
+
</div>
|
| 310 |
+
<div className="flex flex-wrap gap-2">
|
| 311 |
+
{currentProblem.concepts.map((concept, index) => (
|
| 312 |
+
<Badge key={index} variant="secondary" className="bg-purple-100 text-purple-800">
|
| 313 |
+
{concept}
|
| 314 |
+
</Badge>
|
| 315 |
+
))}
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
)}
|
| 319 |
+
|
| 320 |
+
{!showSolution && (
|
| 321 |
+
<div className="space-y-4">
|
| 322 |
+
<div>
|
| 323 |
+
<label className="block text-sm font-medium text-slate-700 mb-2">
|
| 324 |
+
Your Answer:
|
| 325 |
+
</label>
|
| 326 |
+
<Textarea
|
| 327 |
+
value={userAnswer}
|
| 328 |
+
onChange={(e) => setUserAnswer(e.target.value)}
|
| 329 |
+
placeholder="Write your solution step by step..."
|
| 330 |
+
className="min-h-32 bg-white"
|
| 331 |
+
/>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
<div className="flex gap-3 flex-wrap">
|
| 335 |
+
<Button
|
| 336 |
+
onClick={submitAnswer}
|
| 337 |
+
disabled={!userAnswer.trim() || isSubmitting}
|
| 338 |
+
className="bg-emerald-600 hover:bg-emerald-700"
|
| 339 |
+
>
|
| 340 |
+
{isSubmitting ? (
|
| 341 |
+
<>
|
| 342 |
+
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
| 343 |
+
Evaluating...
|
| 344 |
+
</>
|
| 345 |
+
) : (
|
| 346 |
+
<>
|
| 347 |
+
<CheckCircle className="w-4 h-4 mr-2" />
|
| 348 |
+
Submit Answer
|
| 349 |
+
</>
|
| 350 |
+
)}
|
| 351 |
+
</Button>
|
| 352 |
+
|
| 353 |
+
{currentProblem.hints && hintIndex < currentProblem.hints.length && (
|
| 354 |
+
<Button onClick={nextHint} variant="outline">
|
| 355 |
+
<Lightbulb className="w-4 h-4 mr-2" />
|
| 356 |
+
Get Hint ({hintIndex + 1}/{currentProblem.hints.length})
|
| 357 |
+
</Button>
|
| 358 |
+
)}
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
)}
|
| 362 |
+
|
| 363 |
+
{/* Hints */}
|
| 364 |
+
{showHints && currentProblem.hints && (
|
| 365 |
+
<Card className="bg-amber-50 border-amber-200">
|
| 366 |
+
<CardContent className="p-4">
|
| 367 |
+
<div className="flex items-start gap-3">
|
| 368 |
+
<Lightbulb className="w-5 h-5 text-amber-600 mt-0.5" />
|
| 369 |
+
<div>
|
| 370 |
+
<p className="font-medium text-amber-900 mb-2">Hint {hintIndex + 1}:</p>
|
| 371 |
+
<p className="text-amber-800">{currentProblem.hints[hintIndex]}</p>
|
| 372 |
+
</div>
|
| 373 |
+
</div>
|
| 374 |
+
</CardContent>
|
| 375 |
+
</Card>
|
| 376 |
+
)}
|
| 377 |
+
|
| 378 |
+
{/* Results */}
|
| 379 |
+
{score && showSolution && (
|
| 380 |
+
<div className="space-y-4 mt-6">
|
| 381 |
+
<Alert className={score.is_correct ? "border-emerald-200 bg-emerald-50" : "border-amber-200 bg-amber-50"}>
|
| 382 |
+
<div className="flex items-center gap-2">
|
| 383 |
+
{score.is_correct ? (
|
| 384 |
+
<CheckCircle className="w-5 h-5 text-emerald-600" />
|
| 385 |
+
) : (
|
| 386 |
+
<Target className="w-5 h-5 text-amber-600" />
|
| 387 |
+
)}
|
| 388 |
+
<div>
|
| 389 |
+
<h4 className={`font-semibold ${score.is_correct ? 'text-emerald-900' : 'text-amber-900'}`}>
|
| 390 |
+
Score: {score.score}/100
|
| 391 |
+
</h4>
|
| 392 |
+
<AlertDescription className={score.is_correct ? 'text-emerald-800' : 'text-amber-800'}>
|
| 393 |
+
{score.feedback}
|
| 394 |
+
</AlertDescription>
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
</Alert>
|
| 398 |
+
|
| 399 |
+
<Card className="bg-slate-50 border-slate-200">
|
| 400 |
+
<CardHeader>
|
| 401 |
+
<CardTitle className="text-lg flex items-center gap-2">
|
| 402 |
+
<BookOpen className="w-5 h-5 text-blue-600" />
|
| 403 |
+
Complete Solution
|
| 404 |
+
</CardTitle>
|
| 405 |
+
</CardHeader>
|
| 406 |
+
<CardContent>
|
| 407 |
+
<div className="prose max-w-none text-slate-700">
|
| 408 |
+
{currentProblem.solution.split('\n').map((line, index) => (
|
| 409 |
+
<p key={index} className="mb-2">{line}</p>
|
| 410 |
+
))}
|
| 411 |
+
</div>
|
| 412 |
+
</CardContent>
|
| 413 |
+
</Card>
|
| 414 |
+
</div>
|
| 415 |
+
)}
|
| 416 |
+
</CardContent>
|
| 417 |
+
</Card>
|
| 418 |
+
</div>
|
| 419 |
+
)}
|
| 420 |
+
|
| 421 |
+
{/* Empty State */}
|
| 422 |
+
{!currentProblem && !isGenerating && (
|
| 423 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 424 |
+
<CardContent className="text-center py-12">
|
| 425 |
+
<Brain className="w-16 h-16 mx-auto text-slate-400 mb-4" />
|
| 426 |
+
<h3 className="text-xl font-semibold text-slate-900 mb-2">Ready to Practice?</h3>
|
| 427 |
+
<p className="text-slate-600 mb-6">
|
| 428 |
+
Select a topic and difficulty level above to generate your first AI-powered chemistry problem!
|
| 429 |
+
</p>
|
| 430 |
+
</CardContent>
|
| 431 |
+
</Card>
|
| 432 |
+
)}
|
| 433 |
+
</div>
|
| 434 |
+
</div>
|
| 435 |
+
);
|
| 436 |
+
}
|
Pages/Items/Study_Material.txt
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useState, useEffect } from "react";
|
| 3 |
+
import { User } from "@/entities/User";
|
| 4 |
+
import { InvokeLLM } from "@/integrations/Core";
|
| 5 |
+
import { Button } from "@/components/ui/button";
|
| 6 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 7 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 8 |
+
import { Badge } from "@/components/ui/badge";
|
| 9 |
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 10 |
+
import { Alert, AlertDescription } from "@/components/ui/alert";
|
| 11 |
+
import {
|
| 12 |
+
BookOpen,
|
| 13 |
+
FileText,
|
| 14 |
+
Lightbulb,
|
| 15 |
+
RefreshCw,
|
| 16 |
+
Download,
|
| 17 |
+
Sparkles,
|
| 18 |
+
GraduationCap,
|
| 19 |
+
Target,
|
| 20 |
+
Brain,
|
| 21 |
+
CheckCircle
|
| 22 |
+
} from "lucide-react";
|
| 23 |
+
|
| 24 |
+
const TOPICS = [
|
| 25 |
+
{ value: "organic_chemistry", label: "Organic Chemistry", icon: "🧪", color: "bg-green-100 text-green-800" },
|
| 26 |
+
{ value: "inorganic_chemistry", label: "Inorganic Chemistry", icon: "⚛️", color: "bg-blue-100 text-blue-800" },
|
| 27 |
+
{ value: "physical_chemistry", label: "Physical Chemistry", icon: "🔬", color: "bg-purple-100 text-purple-800" },
|
| 28 |
+
{ value: "analytical_chemistry", label: "Analytical Chemistry", icon: "📊", color: "bg-orange-100 text-orange-800" },
|
| 29 |
+
{ value: "biochemistry", label: "Biochemistry", icon: "🧬", color: "bg-pink-100 text-pink-800" },
|
| 30 |
+
{ value: "quantum_chemistry", label: "Quantum Chemistry", icon: "⚡", color: "bg-indigo-100 text-indigo-800" },
|
| 31 |
+
{ value: "materials_science", label: "Materials Science", icon: "🏗️", color: "bg-cyan-100 text-cyan-800" }
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
+
const MATERIAL_TYPES = [
|
| 35 |
+
{ value: "study_guide", label: "Study Guide", icon: BookOpen, description: "Comprehensive topic overview" },
|
| 36 |
+
{ value: "flashcards", label: "Flashcards", icon: Brain, description: "Key terms and definitions" },
|
| 37 |
+
{ value: "practice_problems", label: "Practice Problems", icon: Target, description: "Problems with solutions" },
|
| 38 |
+
{ value: "concept_map", label: "Concept Map", icon: Lightbulb, description: "Visual concept connections" }
|
| 39 |
+
];
|
| 40 |
+
|
| 41 |
+
export default function StudyMaterials() {
|
| 42 |
+
const [user, setUser] = useState(null);
|
| 43 |
+
const [selectedTopic, setSelectedTopic] = useState("");
|
| 44 |
+
const [selectedType, setSelectedType] = useState("");
|
| 45 |
+
const [generatedMaterial, setGeneratedMaterial] = useState(null);
|
| 46 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 47 |
+
|
| 48 |
+
useEffect(() => {
|
| 49 |
+
loadUser();
|
| 50 |
+
}, []);
|
| 51 |
+
|
| 52 |
+
const loadUser = async () => {
|
| 53 |
+
try {
|
| 54 |
+
const userData = await User.me();
|
| 55 |
+
setUser(userData);
|
| 56 |
+
} catch (error) {
|
| 57 |
+
await User.login();
|
| 58 |
+
}
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
const generateMaterial = async () => {
|
| 62 |
+
if (!selectedTopic || !selectedType) return;
|
| 63 |
+
|
| 64 |
+
setIsGenerating(true);
|
| 65 |
+
setGeneratedMaterial(null);
|
| 66 |
+
|
| 67 |
+
try {
|
| 68 |
+
const topic = TOPICS.find(t => t.value === selectedTopic);
|
| 69 |
+
const materialType = MATERIAL_TYPES.find(t => t.value === selectedType);
|
| 70 |
+
|
| 71 |
+
let prompt = "";
|
| 72 |
+
let schema = {};
|
| 73 |
+
|
| 74 |
+
switch (selectedType) {
|
| 75 |
+
case "study_guide":
|
| 76 |
+
prompt = `Create a comprehensive study guide for ${topic.label} suitable for a ${user?.learning_level || 'beginner'} level student. Include:
|
| 77 |
+
- Overview and importance
|
| 78 |
+
- Key concepts with clear explanations
|
| 79 |
+
- Important formulas or reactions
|
| 80 |
+
- Real-world applications
|
| 81 |
+
- Common misconceptions
|
| 82 |
+
- Study tips and memory aids`;
|
| 83 |
+
|
| 84 |
+
schema = {
|
| 85 |
+
type: "object",
|
| 86 |
+
properties: {
|
| 87 |
+
title: { type: "string" },
|
| 88 |
+
overview: { type: "string" },
|
| 89 |
+
key_concepts: {
|
| 90 |
+
type: "array",
|
| 91 |
+
items: {
|
| 92 |
+
type: "object",
|
| 93 |
+
properties: {
|
| 94 |
+
concept: { type: "string" },
|
| 95 |
+
explanation: { type: "string" }
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
formulas: { type: "array", items: { type: "string" } },
|
| 100 |
+
applications: { type: "array", items: { type: "string" } },
|
| 101 |
+
misconceptions: { type: "array", items: { type: "string" } },
|
| 102 |
+
study_tips: { type: "array", items: { type: "string" } }
|
| 103 |
+
}
|
| 104 |
+
};
|
| 105 |
+
break;
|
| 106 |
+
|
| 107 |
+
case "flashcards":
|
| 108 |
+
prompt = `Create 15-20 flashcards for ${topic.label} at ${user?.learning_level || 'beginner'} level. Each flashcard should have:
|
| 109 |
+
- A clear, concise question or term
|
| 110 |
+
- A comprehensive answer or definition
|
| 111 |
+
- Include key formulas, reactions, or concepts`;
|
| 112 |
+
|
| 113 |
+
schema = {
|
| 114 |
+
type: "object",
|
| 115 |
+
properties: {
|
| 116 |
+
title: { type: "string" },
|
| 117 |
+
cards: {
|
| 118 |
+
type: "array",
|
| 119 |
+
items: {
|
| 120 |
+
type: "object",
|
| 121 |
+
properties: {
|
| 122 |
+
front: { type: "string" },
|
| 123 |
+
back: { type: "string" }
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
};
|
| 129 |
+
break;
|
| 130 |
+
|
| 131 |
+
case "practice_problems":
|
| 132 |
+
prompt = `Create 8-10 practice problems for ${topic.label} at ${user?.learning_level || 'beginner'} level. Include:
|
| 133 |
+
- Varied difficulty within the level
|
| 134 |
+
- Step-by-step solutions
|
| 135 |
+
- Brief explanations of key concepts used`;
|
| 136 |
+
|
| 137 |
+
schema = {
|
| 138 |
+
type: "object",
|
| 139 |
+
properties: {
|
| 140 |
+
title: { type: "string" },
|
| 141 |
+
problems: {
|
| 142 |
+
type: "array",
|
| 143 |
+
items: {
|
| 144 |
+
type: "object",
|
| 145 |
+
properties: {
|
| 146 |
+
question: { type: "string" },
|
| 147 |
+
solution: { type: "string" },
|
| 148 |
+
concepts_used: { type: "array", items: { type: "string" } }
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
};
|
| 154 |
+
break;
|
| 155 |
+
|
| 156 |
+
case "concept_map":
|
| 157 |
+
prompt = `Create a concept map structure for ${topic.label} at ${user?.learning_level || 'beginner'} level showing:
|
| 158 |
+
- Main concepts and their relationships
|
| 159 |
+
- Hierarchical organization
|
| 160 |
+
- Key connections between ideas
|
| 161 |
+
- Brief descriptions for each concept`;
|
| 162 |
+
|
| 163 |
+
schema = {
|
| 164 |
+
type: "object",
|
| 165 |
+
properties: {
|
| 166 |
+
title: { type: "string" },
|
| 167 |
+
main_concept: { type: "string" },
|
| 168 |
+
concepts: {
|
| 169 |
+
type: "array",
|
| 170 |
+
items: {
|
| 171 |
+
type: "object",
|
| 172 |
+
properties: {
|
| 173 |
+
name: { type: "string" },
|
| 174 |
+
description: { type: "string" },
|
| 175 |
+
level: { type: "number" },
|
| 176 |
+
connections: { type: "array", items: { type: "string" } }
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
};
|
| 182 |
+
break;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
const result = await InvokeLLM({
|
| 186 |
+
prompt,
|
| 187 |
+
response_json_schema: schema
|
| 188 |
+
});
|
| 189 |
+
|
| 190 |
+
setGeneratedMaterial({
|
| 191 |
+
type: selectedType,
|
| 192 |
+
topic: selectedTopic,
|
| 193 |
+
data: result
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
} catch (error) {
|
| 197 |
+
console.error("Error generating material:", error);
|
| 198 |
+
}
|
| 199 |
+
setIsGenerating(false);
|
| 200 |
+
};
|
| 201 |
+
|
| 202 |
+
const downloadMaterial = () => {
|
| 203 |
+
if (!generatedMaterial) return;
|
| 204 |
+
|
| 205 |
+
let content = "";
|
| 206 |
+
const { data } = generatedMaterial;
|
| 207 |
+
|
| 208 |
+
switch (generatedMaterial.type) {
|
| 209 |
+
case "study_guide":
|
| 210 |
+
content = `${data.title}\n\n`;
|
| 211 |
+
content += `Overview:\n${data.overview}\n\n`;
|
| 212 |
+
if (data.key_concepts) {
|
| 213 |
+
content += "Key Concepts:\n";
|
| 214 |
+
data.key_concepts.forEach(concept => {
|
| 215 |
+
content += `- ${concept.concept}: ${concept.explanation}\n`;
|
| 216 |
+
});
|
| 217 |
+
content += "\n";
|
| 218 |
+
}
|
| 219 |
+
if (data.formulas && data.formulas.length > 0) {
|
| 220 |
+
content += "Important Formulas/Reactions:\n";
|
| 221 |
+
data.formulas.forEach(formula => {
|
| 222 |
+
content += `- ${formula}\n`;
|
| 223 |
+
});
|
| 224 |
+
content += "\n";
|
| 225 |
+
}
|
| 226 |
+
if (data.applications && data.applications.length > 0) {
|
| 227 |
+
content += "Real-world Applications:\n";
|
| 228 |
+
data.applications.forEach(app => {
|
| 229 |
+
content += `- ${app}\n`;
|
| 230 |
+
});
|
| 231 |
+
content += "\n";
|
| 232 |
+
}
|
| 233 |
+
if (data.misconceptions && data.misconceptions.length > 0) {
|
| 234 |
+
content += "Common Misconceptions:\n";
|
| 235 |
+
data.misconceptions.forEach(misconception => {
|
| 236 |
+
content += `- ${misconception}\n`;
|
| 237 |
+
});
|
| 238 |
+
content += "\n";
|
| 239 |
+
}
|
| 240 |
+
if (data.study_tips && data.study_tips.length > 0) {
|
| 241 |
+
content += "Study Tips:\n";
|
| 242 |
+
data.study_tips.forEach(tip => {
|
| 243 |
+
content += `- ${tip}\n`;
|
| 244 |
+
});
|
| 245 |
+
content += "\n";
|
| 246 |
+
}
|
| 247 |
+
break;
|
| 248 |
+
case "flashcards":
|
| 249 |
+
content = `${data.title}\n\nFlashcards:\n\n`;
|
| 250 |
+
data.cards?.forEach((card, index) => {
|
| 251 |
+
content += `Card ${index + 1}:\nQ: ${card.front}\nA: ${card.back}\n\n`;
|
| 252 |
+
});
|
| 253 |
+
break;
|
| 254 |
+
case "practice_problems":
|
| 255 |
+
content = `${data.title}\n\nPractice Problems:\n\n`;
|
| 256 |
+
data.problems?.forEach((problem, index) => {
|
| 257 |
+
content += `Problem ${index + 1}:\nQuestion: ${problem.question}\nSolution: ${problem.solution}\nConcepts Used: ${problem.concepts_used?.join(', ')}\n\n`;
|
| 258 |
+
});
|
| 259 |
+
break;
|
| 260 |
+
case "concept_map":
|
| 261 |
+
content = `${data.title}\n\nMain Concept: ${data.main_concept}\n\n`;
|
| 262 |
+
content += "Concepts:\n";
|
| 263 |
+
data.concepts?.forEach(concept => {
|
| 264 |
+
content += `- Name: ${concept.name}\n Description: ${concept.description}\n Level: ${concept.level}\n Connections: ${concept.connections?.join(', ')}\n\n`;
|
| 265 |
+
});
|
| 266 |
+
break;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
const blob = new Blob([content], { type: 'text/plain' });
|
| 270 |
+
const url = URL.createObjectURL(blob);
|
| 271 |
+
const a = document.createElement('a');
|
| 272 |
+
a.href = url;
|
| 273 |
+
a.download = `${data.title.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.]/g, '')}.txt`; // Sanitize filename
|
| 274 |
+
document.body.appendChild(a);
|
| 275 |
+
a.click();
|
| 276 |
+
document.body.removeChild(a);
|
| 277 |
+
URL.revokeObjectURL(url);
|
| 278 |
+
};
|
| 279 |
+
|
| 280 |
+
const renderMaterial = () => {
|
| 281 |
+
if (!generatedMaterial) return null;
|
| 282 |
+
|
| 283 |
+
const { data, type } = generatedMaterial;
|
| 284 |
+
|
| 285 |
+
switch (type) {
|
| 286 |
+
case "study_guide":
|
| 287 |
+
return (
|
| 288 |
+
<div className="space-y-6">
|
| 289 |
+
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-200">
|
| 290 |
+
<CardHeader>
|
| 291 |
+
<CardTitle className="text-2xl">{data.title}</CardTitle>
|
| 292 |
+
</CardHeader>
|
| 293 |
+
<CardContent>
|
| 294 |
+
<p className="text-lg text-slate-700 leading-relaxed">{data.overview}</p>
|
| 295 |
+
</CardContent>
|
| 296 |
+
</Card>
|
| 297 |
+
|
| 298 |
+
{data.key_concepts && data.key_concepts.length > 0 && (
|
| 299 |
+
<Card>
|
| 300 |
+
<CardHeader>
|
| 301 |
+
<CardTitle className="flex items-center gap-2">
|
| 302 |
+
<Lightbulb className="w-5 h-5 text-amber-500" />
|
| 303 |
+
Key Concepts
|
| 304 |
+
</CardTitle>
|
| 305 |
+
</CardHeader>
|
| 306 |
+
<CardContent>
|
| 307 |
+
<div className="space-y-4">
|
| 308 |
+
{data.key_concepts.map((concept, index) => (
|
| 309 |
+
<div key={index} className="p-4 bg-slate-50 rounded-lg">
|
| 310 |
+
<h4 className="font-semibold text-slate-900 mb-2">{concept.concept}</h4>
|
| 311 |
+
<p className="text-slate-700">{concept.explanation}</p>
|
| 312 |
+
</div>
|
| 313 |
+
))}
|
| 314 |
+
</div>
|
| 315 |
+
</CardContent>
|
| 316 |
+
</Card>
|
| 317 |
+
)}
|
| 318 |
+
|
| 319 |
+
{data.formulas && data.formulas.length > 0 && (
|
| 320 |
+
<Card>
|
| 321 |
+
<CardHeader>
|
| 322 |
+
<CardTitle className="flex items-center gap-2">
|
| 323 |
+
<FileText className="w-5 h-5 text-red-500" />
|
| 324 |
+
Important Formulas/Reactions
|
| 325 |
+
</CardTitle>
|
| 326 |
+
</CardHeader>
|
| 327 |
+
<CardContent>
|
| 328 |
+
<ul className="list-disc list-inside space-y-2">
|
| 329 |
+
{data.formulas.map((formula, index) => (
|
| 330 |
+
<li key={index} className="text-slate-700">{formula}</li>
|
| 331 |
+
))}
|
| 332 |
+
</ul>
|
| 333 |
+
</CardContent>
|
| 334 |
+
</Card>
|
| 335 |
+
)}
|
| 336 |
+
|
| 337 |
+
{data.applications && data.applications.length > 0 && (
|
| 338 |
+
<Card>
|
| 339 |
+
<CardHeader>
|
| 340 |
+
<CardTitle className="flex items-center gap-2">
|
| 341 |
+
<Target className="w-5 h-5 text-orange-500" />
|
| 342 |
+
Real-world Applications
|
| 343 |
+
</CardTitle>
|
| 344 |
+
</CardHeader>
|
| 345 |
+
<CardContent>
|
| 346 |
+
<ul className="list-disc list-inside space-y-2">
|
| 347 |
+
{data.applications.map((app, index) => (
|
| 348 |
+
<li key={index} className="text-slate-700">{app}</li>
|
| 349 |
+
))}
|
| 350 |
+
</ul>
|
| 351 |
+
</CardContent>
|
| 352 |
+
</Card>
|
| 353 |
+
)}
|
| 354 |
+
|
| 355 |
+
{data.misconceptions && data.misconceptions.length > 0 && (
|
| 356 |
+
<Card>
|
| 357 |
+
<CardHeader>
|
| 358 |
+
<CardTitle className="flex items-center gap-2">
|
| 359 |
+
<Lightbulb className="w-5 h-5 text-purple-500" />
|
| 360 |
+
Common Misconceptions
|
| 361 |
+
</CardTitle>
|
| 362 |
+
</CardHeader>
|
| 363 |
+
<CardContent>
|
| 364 |
+
<ul className="list-disc list-inside space-y-2">
|
| 365 |
+
{data.misconceptions.map((misconception, index) => (
|
| 366 |
+
<li key={index} className="text-slate-700">{misconception}</li>
|
| 367 |
+
))}
|
| 368 |
+
</ul>
|
| 369 |
+
</CardContent>
|
| 370 |
+
</Card>
|
| 371 |
+
)}
|
| 372 |
+
|
| 373 |
+
{data.study_tips && data.study_tips.length > 0 && (
|
| 374 |
+
<Card>
|
| 375 |
+
<CardHeader>
|
| 376 |
+
<CardTitle className="flex items-center gap-2">
|
| 377 |
+
<GraduationCap className="w-5 h-5 text-emerald-500" />
|
| 378 |
+
Study Tips
|
| 379 |
+
</CardTitle>
|
| 380 |
+
</CardHeader>
|
| 381 |
+
<CardContent>
|
| 382 |
+
<ul className="space-y-2">
|
| 383 |
+
{data.study_tips.map((tip, index) => (
|
| 384 |
+
<li key={index} className="flex items-start gap-3">
|
| 385 |
+
<CheckCircle className="w-5 h-5 text-emerald-500 mt-0.5 flex-shrink-0" />
|
| 386 |
+
<span className="text-slate-700">{tip}</span>
|
| 387 |
+
</li>
|
| 388 |
+
))}
|
| 389 |
+
</ul>
|
| 390 |
+
</CardContent>
|
| 391 |
+
</Card>
|
| 392 |
+
)}
|
| 393 |
+
</div>
|
| 394 |
+
);
|
| 395 |
+
|
| 396 |
+
case "flashcards":
|
| 397 |
+
return (
|
| 398 |
+
<div className="space-y-6">
|
| 399 |
+
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-purple-200">
|
| 400 |
+
<CardHeader>
|
| 401 |
+
<CardTitle className="text-2xl">{data.title}</CardTitle>
|
| 402 |
+
<p className="text-slate-600">{data.cards?.length} flashcards</p>
|
| 403 |
+
</CardHeader>
|
| 404 |
+
</Card>
|
| 405 |
+
|
| 406 |
+
<div className="grid md:grid-cols-2 gap-4">
|
| 407 |
+
{data.cards?.map((card, index) => (
|
| 408 |
+
<Card key={index} className="hover:shadow-lg transition-shadow">
|
| 409 |
+
<CardContent className="p-6">
|
| 410 |
+
<div className="text-center">
|
| 411 |
+
<Badge className="mb-4">Card {index + 1}</Badge>
|
| 412 |
+
<div className="space-y-4">
|
| 413 |
+
<div>
|
| 414 |
+
<h4 className="font-medium text-slate-600 mb-2">Question:</h4>
|
| 415 |
+
<p className="font-semibold text-slate-900">{card.front}</p>
|
| 416 |
+
</div>
|
| 417 |
+
<div className="border-t pt-4">
|
| 418 |
+
<h4 className="font-medium text-slate-600 mb-2">Answer:</h4>
|
| 419 |
+
<p className="text-slate-800">{card.back}</p>
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
</CardContent>
|
| 424 |
+
</Card>
|
| 425 |
+
))}
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
);
|
| 429 |
+
|
| 430 |
+
case "practice_problems":
|
| 431 |
+
return (
|
| 432 |
+
<div className="space-y-6">
|
| 433 |
+
<Card className="bg-gradient-to-br from-teal-50 to-emerald-50 border-teal-200">
|
| 434 |
+
<CardHeader>
|
| 435 |
+
<CardTitle className="text-2xl">{data.title}</CardTitle>
|
| 436 |
+
<p className="text-slate-600">{data.problems?.length} practice problems</p>
|
| 437 |
+
</CardHeader>
|
| 438 |
+
</Card>
|
| 439 |
+
|
| 440 |
+
<div className="space-y-4">
|
| 441 |
+
{data.problems?.map((problem, index) => (
|
| 442 |
+
<Card key={index} className="hover:shadow-lg transition-shadow">
|
| 443 |
+
<CardContent className="p-6">
|
| 444 |
+
<div className="space-y-4">
|
| 445 |
+
<div>
|
| 446 |
+
<h4 className="font-medium text-slate-600 mb-2">Problem {index + 1}:</h4>
|
| 447 |
+
<p className="font-semibold text-slate-900">{problem.question}</p>
|
| 448 |
+
</div>
|
| 449 |
+
<div className="border-t pt-4">
|
| 450 |
+
<h4 className="font-medium text-slate-600 mb-2">Solution:</h4>
|
| 451 |
+
<p className="text-slate-800 whitespace-pre-wrap">{problem.solution}</p>
|
| 452 |
+
</div>
|
| 453 |
+
{problem.concepts_used && problem.concepts_used.length > 0 && (
|
| 454 |
+
<div className="border-t pt-4">
|
| 455 |
+
<h4 className="font-medium text-slate-600 mb-2">Concepts Used:</h4>
|
| 456 |
+
<div className="flex flex-wrap gap-2">
|
| 457 |
+
{problem.concepts_used.map((concept, idx) => (
|
| 458 |
+
<Badge key={idx} variant="outline" className="bg-blue-50 text-blue-700">{concept}</Badge>
|
| 459 |
+
))}
|
| 460 |
+
</div>
|
| 461 |
+
</div>
|
| 462 |
+
)}
|
| 463 |
+
</div>
|
| 464 |
+
</CardContent>
|
| 465 |
+
</Card>
|
| 466 |
+
))}
|
| 467 |
+
</div>
|
| 468 |
+
</div>
|
| 469 |
+
);
|
| 470 |
+
|
| 471 |
+
case "concept_map":
|
| 472 |
+
return (
|
| 473 |
+
<div className="space-y-6">
|
| 474 |
+
<Card className="bg-gradient-to-br from-yellow-50 to-orange-50 border-yellow-200">
|
| 475 |
+
<CardHeader>
|
| 476 |
+
<CardTitle className="text-2xl">{data.title}</CardTitle>
|
| 477 |
+
<p className="text-slate-600">Main Concept: <span className="font-medium">{data.main_concept}</span></p>
|
| 478 |
+
</CardHeader>
|
| 479 |
+
</Card>
|
| 480 |
+
|
| 481 |
+
{data.concepts && data.concepts.length > 0 && (
|
| 482 |
+
<Card>
|
| 483 |
+
<CardHeader>
|
| 484 |
+
<CardTitle className="flex items-center gap-2">
|
| 485 |
+
<Lightbulb className="w-5 h-5 text-amber-500" />
|
| 486 |
+
Related Concepts
|
| 487 |
+
</CardTitle>
|
| 488 |
+
</CardHeader>
|
| 489 |
+
<CardContent>
|
| 490 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
| 491 |
+
{data.concepts.map((concept, index) => (
|
| 492 |
+
<Card key={index} className="p-4 bg-slate-50 rounded-lg shadow-sm">
|
| 493 |
+
<h4 className="font-semibold text-slate-900 mb-2">{concept.name}</h4>
|
| 494 |
+
<p className="text-sm text-slate-700 mb-2">{concept.description}</p>
|
| 495 |
+
{concept.connections && concept.connections.length > 0 && (
|
| 496 |
+
<div className="mt-2">
|
| 497 |
+
<span className="font-medium text-xs text-slate-600">Connections: </span>
|
| 498 |
+
<div className="flex flex-wrap gap-1">
|
| 499 |
+
{concept.connections.map((conn, idx) => (
|
| 500 |
+
<Badge key={idx} variant="secondary" className="text-xs">{conn}</Badge>
|
| 501 |
+
))}
|
| 502 |
+
</div>
|
| 503 |
+
</div>
|
| 504 |
+
)}
|
| 505 |
+
</Card>
|
| 506 |
+
))}
|
| 507 |
+
</div>
|
| 508 |
+
</CardContent>
|
| 509 |
+
</Card>
|
| 510 |
+
)}
|
| 511 |
+
</div>
|
| 512 |
+
);
|
| 513 |
+
|
| 514 |
+
default:
|
| 515 |
+
return (
|
| 516 |
+
<Alert>
|
| 517 |
+
<AlertDescription>
|
| 518 |
+
Material type not yet implemented for display.
|
| 519 |
+
</AlertDescription>
|
| 520 |
+
</Alert>
|
| 521 |
+
);
|
| 522 |
+
}
|
| 523 |
+
};
|
| 524 |
+
|
| 525 |
+
return (
|
| 526 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-emerald-50 p-4 md:p-8">
|
| 527 |
+
<div className="max-w-6xl mx-auto">
|
| 528 |
+
<div className="mb-8">
|
| 529 |
+
<div className="flex items-center gap-3 mb-4">
|
| 530 |
+
<div className="w-12 h-12 bg-gradient-to-br from-emerald-600 to-teal-700 rounded-xl flex items-center justify-center">
|
| 531 |
+
<BookOpen className="w-6 h-6 text-white" />
|
| 532 |
+
</div>
|
| 533 |
+
<div>
|
| 534 |
+
<h1 className="text-3xl font-bold text-slate-900">AI Study Materials</h1>
|
| 535 |
+
<p className="text-slate-600">Generate personalized learning resources with AI</p>
|
| 536 |
+
</div>
|
| 537 |
+
</div>
|
| 538 |
+
</div>
|
| 539 |
+
|
| 540 |
+
{/* Material Generator */}
|
| 541 |
+
<Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm mb-8">
|
| 542 |
+
<CardHeader>
|
| 543 |
+
<CardTitle className="flex items-center gap-2">
|
| 544 |
+
<Sparkles className="w-5 h-5 text-amber-500" />
|
| 545 |
+
Generate Study Material
|
| 546 |
+
</CardTitle>
|
| 547 |
+
</CardHeader>
|
| 548 |
+
<CardContent>
|
| 549 |
+
<div className="grid md:grid-cols-2 gap-6 mb-6">
|
| 550 |
+
<div>
|
| 551 |
+
<label className="block text-sm font-medium text-slate-700 mb-3">Chemistry Topic</label>
|
| 552 |
+
<div className="grid grid-cols-2 gap-2">
|
| 553 |
+
{TOPICS.map((topic) => (
|
| 554 |
+
<button
|
| 555 |
+
key={topic.value}
|
| 556 |
+
onClick={() => setSelectedTopic(topic.value)}
|
| 557 |
+
className={`p-3 text-left rounded-lg border transition-all duration-200 ${
|
| 558 |
+
selectedTopic === topic.value
|
| 559 |
+
? 'border-emerald-500 bg-emerald-50'
|
| 560 |
+
: 'border-slate-200 hover:border-emerald-300'
|
| 561 |
+
}`}
|
| 562 |
+
>
|
| 563 |
+
<div className="text-lg mb-1">{topic.icon}</div>
|
| 564 |
+
<div className="text-sm font-medium text-slate-900">{topic.label}</div>
|
| 565 |
+
</button>
|
| 566 |
+
))}
|
| 567 |
+
</div>
|
| 568 |
+
</div>
|
| 569 |
+
|
| 570 |
+
<div>
|
| 571 |
+
<label className="block text-sm font-medium text-slate-700 mb-3">Material Type</label>
|
| 572 |
+
<div className="space-y-2">
|
| 573 |
+
{MATERIAL_TYPES.map((type) => (
|
| 574 |
+
<button
|
| 575 |
+
key={type.value}
|
| 576 |
+
onClick={() => setSelectedType(type.value)}
|
| 577 |
+
className={`w-full p-4 text-left rounded-lg border transition-all duration-200 ${
|
| 578 |
+
selectedType === type.value
|
| 579 |
+
? 'border-emerald-500 bg-emerald-50'
|
| 580 |
+
: 'border-slate-200 hover:border-emerald-300'
|
| 581 |
+
}`}
|
| 582 |
+
>
|
| 583 |
+
<div className="flex items-center gap-3">
|
| 584 |
+
<type.icon className="w-5 h-5 text-emerald-600" />
|
| 585 |
+
<div>
|
| 586 |
+
<div className="font-medium text-slate-900">{type.label}</div>
|
| 587 |
+
<div className="text-sm text-slate-500">{type.description}</div>
|
| 588 |
+
</div>
|
| 589 |
+
</div>
|
| 590 |
+
</button>
|
| 591 |
+
))}
|
| 592 |
+
</div>
|
| 593 |
+
</div>
|
| 594 |
+
</div>
|
| 595 |
+
|
| 596 |
+
<Button
|
| 597 |
+
onClick={generateMaterial}
|
| 598 |
+
disabled={!selectedTopic || !selectedType || isGenerating}
|
| 599 |
+
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"
|
| 600 |
+
>
|
| 601 |
+
{isGenerating ? (
|
| 602 |
+
<>
|
| 603 |
+
<RefreshCw className="w-5 h-5 mr-2 animate-spin" />
|
| 604 |
+
Generating Material...
|
| 605 |
+
</>
|
| 606 |
+
) : (
|
| 607 |
+
<>
|
| 608 |
+
<Brain className="w-5 h-5 mr-2" />
|
| 609 |
+
Generate Study Material
|
| 610 |
+
</>
|
| 611 |
+
)}
|
| 612 |
+
</Button>
|
| 613 |
+
</CardContent>
|
| 614 |
+
</Card>
|
| 615 |
+
|
| 616 |
+
{/* Generated Material */}
|
| 617 |
+
{generatedMaterial && (
|
| 618 |
+
<Card className="border-0 shadow-xl bg-white/90 backdrop-blur-sm">
|
| 619 |
+
<CardHeader>
|
| 620 |
+
<div className="flex items-center justify-between flex-wrap gap-4">
|
| 621 |
+
<div>
|
| 622 |
+
<CardTitle className="text-xl">Generated Material</CardTitle>
|
| 623 |
+
<div className="flex gap-2 mt-2">
|
| 624 |
+
<Badge className={TOPICS.find(t => t.value === generatedMaterial.topic)?.color}>
|
| 625 |
+
{TOPICS.find(t => t.value === generatedMaterial.topic)?.label}
|
| 626 |
+
</Badge>
|
| 627 |
+
<Badge variant="outline">
|
| 628 |
+
{MATERIAL_TYPES.find(t => t.value === generatedMaterial.type)?.label}
|
| 629 |
+
</Badge>
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
<Button onClick={downloadMaterial} variant="outline">
|
| 633 |
+
<Download className="w-4 h-4 mr-2" />
|
| 634 |
+
Download
|
| 635 |
+
</Button>
|
| 636 |
+
</div>
|
| 637 |
+
</CardHeader>
|
| 638 |
+
<CardContent>
|
| 639 |
+
{renderMaterial()}
|
| 640 |
+
</CardContent>
|
| 641 |
+
</Card>
|
| 642 |
+
)}
|
| 643 |
+
|
| 644 |
+
{/* Empty State */}
|
| 645 |
+
{!generatedMaterial && !isGenerating && (
|
| 646 |
+
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm">
|
| 647 |
+
<CardContent className="text-center py-12">
|
| 648 |
+
<BookOpen className="w-16 h-16 mx-auto text-slate-400 mb-4" />
|
| 649 |
+
<h3 className="text-xl font-semibold text-slate-900 mb-2">Ready to Study?</h3>
|
| 650 |
+
<p className="text-slate-600 mb-6">
|
| 651 |
+
Select a chemistry topic and material type above to generate personalized study materials!
|
| 652 |
+
</p>
|
| 653 |
+
</CardContent>
|
| 654 |
+
</Card>
|
| 655 |
+
)}
|
| 656 |
+
</div>
|
| 657 |
+
</div>
|
| 658 |
+
);
|
| 659 |
+
}
|