Spaces:
Sleeping
Sleeping
Mahdi Naser Moghadasi
commited on
Commit
·
6cff14b
1
Parent(s):
9ffab0c
Refactor app.py into modular architecture
Browse files- Created config.py for configuration and constants
- Created database.py for database operations
- Created email_service.py for email functionality
- Created ai_services.py for AI/ML related functions
- Created content_generators.py for lesson plan and quiz generation
- Created ui_components.py for Gradio interface components
- Created utils.py for utility functions
- Created app_modular.py as new main entry point
- Updated generate_image_with_huggingface with improved error handling and multiple model fallbacks
- Improved code organization and maintainability
- ai_services.py +280 -0
- app_modular.py +37 -0
- config.py +65 -0
- content_generators.py +643 -0
- database.py +143 -0
- email_service.py +101 -0
- ui_components.py +577 -0
- utils.py +254 -0
ai_services.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AI services module for BrightMind AI
|
| 3 |
+
Handles all AI/ML related functionality including Hugging Face API calls and image generation
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import requests
|
| 7 |
+
import base64
|
| 8 |
+
from config import HF_TOKEN, HUGGINGFACE_HEADERS
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def call_hugging_face_api(prompt):
|
| 12 |
+
"""Call Hugging Face API for Albert chatbot"""
|
| 13 |
+
if not HF_TOKEN:
|
| 14 |
+
return None
|
| 15 |
+
|
| 16 |
+
headers = {
|
| 17 |
+
"Authorization": f"Bearer {HF_TOKEN}",
|
| 18 |
+
"Content-Type": "application/json"
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
data = {
|
| 22 |
+
"model": "deepseek-ai/DeepSeek-V3-0324",
|
| 23 |
+
"messages": [
|
| 24 |
+
{
|
| 25 |
+
"role": "user",
|
| 26 |
+
"content": prompt
|
| 27 |
+
}
|
| 28 |
+
],
|
| 29 |
+
"max_tokens": 200,
|
| 30 |
+
"temperature": 0.7
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
response = requests.post(
|
| 35 |
+
"https://router.huggingface.co/v1/chat/completions",
|
| 36 |
+
headers=headers,
|
| 37 |
+
json=data,
|
| 38 |
+
timeout=30
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
if response.status_code == 200:
|
| 42 |
+
result = response.json()
|
| 43 |
+
return result['choices'][0]['message']['content']
|
| 44 |
+
else:
|
| 45 |
+
print(f"❌ Albert API error: {response.status_code}")
|
| 46 |
+
return None
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"❌ Albert API error: {str(e)}")
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def call_hugging_face_api_content(prompt):
|
| 53 |
+
"""Call Hugging Face API for content generation with more tokens"""
|
| 54 |
+
if not HF_TOKEN:
|
| 55 |
+
return None
|
| 56 |
+
|
| 57 |
+
headers = {
|
| 58 |
+
"Authorization": f"Bearer {HF_TOKEN}",
|
| 59 |
+
"Content-Type": "application/json"
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
data = {
|
| 63 |
+
"model": "deepseek-ai/DeepSeek-V3-0324",
|
| 64 |
+
"messages": [
|
| 65 |
+
{
|
| 66 |
+
"role": "user",
|
| 67 |
+
"content": prompt
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"max_tokens": 1000, # More tokens for content generation
|
| 71 |
+
"temperature": 0.7
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
print(f"🌐 Making API call for content generation...")
|
| 76 |
+
print(f"🔗 Endpoint: https://router.huggingface.co/v1/chat/completions")
|
| 77 |
+
print(f"🔑 Token available: {HF_TOKEN is not None}")
|
| 78 |
+
print(f"📝 Prompt length: {len(prompt)} characters")
|
| 79 |
+
|
| 80 |
+
response = requests.post(
|
| 81 |
+
"https://router.huggingface.co/v1/chat/completions",
|
| 82 |
+
headers=headers,
|
| 83 |
+
json=data,
|
| 84 |
+
timeout=60 # Longer timeout for content generation
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
print(f"📊 Response status: {response.status_code}")
|
| 88 |
+
|
| 89 |
+
if response.status_code == 200:
|
| 90 |
+
result = response.json()
|
| 91 |
+
content = result['choices'][0]['message']['content']
|
| 92 |
+
print(f"✅ Content generation successful! Length: {len(content)} characters")
|
| 93 |
+
return content
|
| 94 |
+
else:
|
| 95 |
+
print(f"❌ Content API error: {response.status_code}")
|
| 96 |
+
print(f"❌ Response: {response.text}")
|
| 97 |
+
return None
|
| 98 |
+
except Exception as e:
|
| 99 |
+
print(f"❌ Content API error: {str(e)}")
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def generate_image_with_huggingface(prompt, topic, content_type):
|
| 104 |
+
"""Generate image using only Hugging Face Inference API models"""
|
| 105 |
+
try:
|
| 106 |
+
if not HF_TOKEN:
|
| 107 |
+
print("No HF_TOKEN available for image generation")
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
+
headers = {
|
| 111 |
+
"Authorization": f"Bearer {HF_TOKEN}",
|
| 112 |
+
"Content-Type": "application/json"
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
# Models available on Hugging Face Inference API
|
| 116 |
+
models = [
|
| 117 |
+
"runwayml/stable-diffusion-v1-5",
|
| 118 |
+
"CompVis/stable-diffusion-v1-4",
|
| 119 |
+
"stabilityai/stable-diffusion-2-1",
|
| 120 |
+
"prompthero/openjourney",
|
| 121 |
+
"dreamlike-art/dreamlike-diffusion-1.0"
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
# Simple prompt optimized for educational content
|
| 125 |
+
clean_prompt = f"educational diagram of {topic}, simple illustration, clean design, white background"
|
| 126 |
+
|
| 127 |
+
print(f"Generating image for: {topic}")
|
| 128 |
+
print(f"Using prompt: {clean_prompt}")
|
| 129 |
+
|
| 130 |
+
for model_name in models:
|
| 131 |
+
try:
|
| 132 |
+
print(f"Trying model: {model_name}")
|
| 133 |
+
|
| 134 |
+
# Use the correct API format for HF Inference API
|
| 135 |
+
payload = {"inputs": clean_prompt}
|
| 136 |
+
|
| 137 |
+
response = requests.post(
|
| 138 |
+
f"https://api-inference.huggingface.co/models/{model_name}",
|
| 139 |
+
headers=headers,
|
| 140 |
+
json=payload,
|
| 141 |
+
timeout=60
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
print(f"Response status: {response.status_code}")
|
| 145 |
+
|
| 146 |
+
if response.status_code == 200:
|
| 147 |
+
content_length = len(response.content)
|
| 148 |
+
print(f"Content length: {content_length} bytes")
|
| 149 |
+
|
| 150 |
+
# Check if we got actual image data (not error JSON)
|
| 151 |
+
if content_length > 5000:
|
| 152 |
+
try:
|
| 153 |
+
# Try to decode as JSON first to check for errors
|
| 154 |
+
json_response = response.json()
|
| 155 |
+
if "error" in json_response:
|
| 156 |
+
print(f"API error: {json_response['error']}")
|
| 157 |
+
continue
|
| 158 |
+
except:
|
| 159 |
+
# If it's not JSON, it should be binary image data
|
| 160 |
+
import base64
|
| 161 |
+
image_base64 = base64.b64encode(response.content).decode('utf-8')
|
| 162 |
+
print(f"Successfully generated image with {model_name}")
|
| 163 |
+
return f"data:image/png;base64,{image_base64}"
|
| 164 |
+
else:
|
| 165 |
+
print("Response too small, likely an error")
|
| 166 |
+
|
| 167 |
+
elif response.status_code == 503:
|
| 168 |
+
# Model is loading - this is common on first request
|
| 169 |
+
try:
|
| 170 |
+
error_info = response.json()
|
| 171 |
+
if "estimated_time" in error_info:
|
| 172 |
+
print(f"Model loading, estimated time: {error_info['estimated_time']} seconds")
|
| 173 |
+
else:
|
| 174 |
+
print("Model is currently loading")
|
| 175 |
+
except:
|
| 176 |
+
print("Model unavailable (503)")
|
| 177 |
+
continue
|
| 178 |
+
|
| 179 |
+
elif response.status_code == 401:
|
| 180 |
+
print("Authentication failed - invalid HF_TOKEN")
|
| 181 |
+
break
|
| 182 |
+
|
| 183 |
+
else:
|
| 184 |
+
print(f"HTTP {response.status_code}: {response.text[:200]}")
|
| 185 |
+
|
| 186 |
+
except requests.exceptions.Timeout:
|
| 187 |
+
print(f"Timeout with model {model_name}")
|
| 188 |
+
continue
|
| 189 |
+
except Exception as model_error:
|
| 190 |
+
print(f"Error with model {model_name}: {str(model_error)}")
|
| 191 |
+
continue
|
| 192 |
+
|
| 193 |
+
print("All models failed or unavailable")
|
| 194 |
+
return None
|
| 195 |
+
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(f"Critical error in image generation: {str(e)}")
|
| 198 |
+
return None
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def get_educational_image_url(topic, description, content_type):
|
| 202 |
+
"""Get educational image URL using Hugging Face generation first, then fallbacks"""
|
| 203 |
+
try:
|
| 204 |
+
# Create a detailed prompt for image generation
|
| 205 |
+
image_prompt = f"Educational illustration of {topic}, {description}, {content_type}, clean diagram, colorful, professional, suitable for middle school students"
|
| 206 |
+
|
| 207 |
+
print(f"🎨 Generating image with prompt: {image_prompt}")
|
| 208 |
+
|
| 209 |
+
# Try to generate image with Hugging Face first
|
| 210 |
+
generated_image = generate_image_with_huggingface(image_prompt, topic, content_type)
|
| 211 |
+
|
| 212 |
+
if generated_image:
|
| 213 |
+
print(f"✅ Generated image successfully")
|
| 214 |
+
return generated_image
|
| 215 |
+
|
| 216 |
+
# Fallback to reliable sources if generation fails
|
| 217 |
+
print("🔄 Image generation failed, using fallback sources")
|
| 218 |
+
|
| 219 |
+
educational_sources = [
|
| 220 |
+
# 1. Lorem Picsum (reliable placeholder)
|
| 221 |
+
f"https://picsum.photos/800/600?random={hash(topic) % 1000}",
|
| 222 |
+
|
| 223 |
+
# 2. Placeholder with educational styling
|
| 224 |
+
f"https://via.placeholder.com/800x600/667eea/ffffff?text={topic.replace(' ', '+')}+Educational+Content",
|
| 225 |
+
|
| 226 |
+
# 3. Educational diagram generator
|
| 227 |
+
f"https://via.placeholder.com/800x600/4f46e5/ffffff?text={content_type.replace(' ', '+')}+{topic.replace(' ', '+')}",
|
| 228 |
+
]
|
| 229 |
+
|
| 230 |
+
# Try each fallback source
|
| 231 |
+
for i, url in enumerate(educational_sources):
|
| 232 |
+
try:
|
| 233 |
+
print(f"🔍 Trying fallback source {i+1}: {url}")
|
| 234 |
+
response = requests.head(url, timeout=5)
|
| 235 |
+
if response.status_code == 200:
|
| 236 |
+
print(f"✅ Found working fallback image: {url}")
|
| 237 |
+
return url
|
| 238 |
+
except:
|
| 239 |
+
continue
|
| 240 |
+
|
| 241 |
+
# Final fallback
|
| 242 |
+
return f"https://via.placeholder.com/800x600/667eea/ffffff?text={topic.replace(' ', '+')}+{content_type.replace(' ', '+')}"
|
| 243 |
+
|
| 244 |
+
except Exception as e:
|
| 245 |
+
print(f"❌ Image URL generation error: {str(e)}")
|
| 246 |
+
return f"https://via.placeholder.com/800x600/667eea/ffffff?text={topic.replace(' ', '+')}+{content_type.replace(' ', '+')}"
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def generate_real_image(description, topic, content_type):
|
| 250 |
+
"""Generate actual image for educational content"""
|
| 251 |
+
try:
|
| 252 |
+
# Try to generate image with Hugging Face
|
| 253 |
+
image_data = generate_image_with_huggingface(description, topic, content_type)
|
| 254 |
+
|
| 255 |
+
if image_data:
|
| 256 |
+
return f'\n<img src="{image_data}" alt="{description}" style="max-width: 100%; height: auto; border-radius: 8px; margin: 20px 0;">\n'
|
| 257 |
+
else:
|
| 258 |
+
# Fallback to text placeholder
|
| 259 |
+
return f"\n**[📸 Image: {description}]**\n*Visual content would appear here*\n"
|
| 260 |
+
|
| 261 |
+
except Exception as e:
|
| 262 |
+
return f"\n**[📸 Image: {description}]**\n*Visual content would appear here*\n"
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def generate_image_placeholder(description, topic, content_type):
|
| 266 |
+
"""Generate image placeholder with description for educational content (fallback)"""
|
| 267 |
+
# Create a structured image placeholder that can be replaced with real images
|
| 268 |
+
image_placeholder = f"""
|
| 269 |
+
<div style="border: 2px dashed #667eea; padding: 20px; margin: 20px 0; text-align: center; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 12px;">
|
| 270 |
+
<div style="font-size: 48px; margin-bottom: 10px;">🖼️</div>
|
| 271 |
+
<h3 style="color: #667eea; margin: 10px 0;">📸 Image Placeholder</h3>
|
| 272 |
+
<p style="color: #4a5568; margin: 10px 0;"><strong>Description:</strong> {description}</p>
|
| 273 |
+
<p style="color: #718096; font-size: 14px; margin: 5px 0;"><strong>Topic:</strong> {topic}</p>
|
| 274 |
+
<p style="color: #718096; font-size: 14px; margin: 5px 0;"><strong>Content Type:</strong> {content_type}</p>
|
| 275 |
+
<div style="background: #667eea; color: white; padding: 8px 16px; border-radius: 6px; display: inline-block; margin-top: 10px; font-size: 12px;">
|
| 276 |
+
💡 This image would enhance the learning experience
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
"""
|
| 280 |
+
return image_placeholder
|
app_modular.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
BrightMind AI - Modular Educational Assistant
|
| 3 |
+
Main application file using modular architecture
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from config import debug_token_status
|
| 8 |
+
from database import init_database
|
| 9 |
+
from ui_components import create_main_interface
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
"""Main application entry point"""
|
| 14 |
+
|
| 15 |
+
# Debug token status
|
| 16 |
+
debug_token_status()
|
| 17 |
+
|
| 18 |
+
# Initialize database
|
| 19 |
+
print("🔧 Initializing database...")
|
| 20 |
+
init_database()
|
| 21 |
+
|
| 22 |
+
# Create and launch the interface
|
| 23 |
+
print("🚀 Starting BrightMind AI...")
|
| 24 |
+
interface = create_main_interface()
|
| 25 |
+
|
| 26 |
+
# Launch the application
|
| 27 |
+
interface.launch(
|
| 28 |
+
server_name="0.0.0.0",
|
| 29 |
+
server_port=7860,
|
| 30 |
+
share=False,
|
| 31 |
+
debug=True,
|
| 32 |
+
show_error=True
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
if __name__ == "__main__":
|
| 37 |
+
main()
|
config.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration module for BrightMind AI
|
| 3 |
+
Contains all configuration constants and environment variables
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# Get Hugging Face token from environment variable
|
| 9 |
+
HF_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
|
| 10 |
+
|
| 11 |
+
# Database configuration
|
| 12 |
+
DATABASE_FILE = "feedback.db"
|
| 13 |
+
|
| 14 |
+
# Email configuration (private)
|
| 15 |
+
FEEDBACK_EMAIL = "mahdi@brightmind-ai.com"
|
| 16 |
+
SMTP_SERVER = "smtp.gmail.com"
|
| 17 |
+
SMTP_PORT = 587
|
| 18 |
+
SMTP_USERNAME = os.getenv("SMTP_USERNAME", "")
|
| 19 |
+
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
|
| 20 |
+
|
| 21 |
+
# Debug token detection
|
| 22 |
+
def debug_token_status():
|
| 23 |
+
"""Print debug information about token status"""
|
| 24 |
+
print(f"🔍 Token detection debug:")
|
| 25 |
+
print(f" - HF_TOKEN exists: {HF_TOKEN is not None}")
|
| 26 |
+
print(f" - HF_TOKEN length: {len(HF_TOKEN) if HF_TOKEN else 0}")
|
| 27 |
+
print(f" - HF_TOKEN starts with hf_: {HF_TOKEN.startswith('hf_') if HF_TOKEN else False}")
|
| 28 |
+
print(f" - SMTP configured: {SMTP_USERNAME != '' and SMTP_PASSWORD != ''}")
|
| 29 |
+
|
| 30 |
+
# Hugging Face API configuration
|
| 31 |
+
HUGGINGFACE_API_URL = "https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium"
|
| 32 |
+
HUGGINGFACE_HEADERS = {
|
| 33 |
+
"Authorization": f"Bearer {HF_TOKEN}" if HF_TOKEN else None
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# Content generation settings
|
| 37 |
+
DEFAULT_DIFFICULTY = "intermediate"
|
| 38 |
+
DEFAULT_QUESTION_COUNT = 5
|
| 39 |
+
DEFAULT_DURATION = 60
|
| 40 |
+
|
| 41 |
+
# UI Configuration
|
| 42 |
+
PROGRESS_HTML = """
|
| 43 |
+
<div class="progress-container">
|
| 44 |
+
<div class="generation-status">🤖 Generating your lesson plan...</div>
|
| 45 |
+
<div class="progress-bar">
|
| 46 |
+
<div class="progress-fill"></div>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="generation-status">Analyzing requirements and creating content...</div>
|
| 49 |
+
</div>
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
COMPLETION_HTML = """
|
| 53 |
+
<div class="progress-container">
|
| 54 |
+
<div class="generation-status">✅ Lesson Plan Generated Successfully!</div>
|
| 55 |
+
</div>
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
FALLBACK_HTML = """
|
| 59 |
+
<div class="progress-container">
|
| 60 |
+
<div class="generation-status">🔄 Using educational algorithms...</div>
|
| 61 |
+
<div class="progress-bar">
|
| 62 |
+
<div class="progress-fill"></div>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
"""
|
content_generators.py
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Content generators module for BrightMind AI
|
| 3 |
+
Handles lesson plan and quiz generation functionality
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from config import HF_TOKEN, PROGRESS_HTML, COMPLETION_HTML, FALLBACK_HTML
|
| 7 |
+
from ai_services import call_hugging_face_api_content
|
| 8 |
+
from utils import typewriter_effect, process_content_with_images, apply_content_styling
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def generate_lesson_plan_with_progress(topic, subject, grade_level, duration, difficulty="intermediate"):
|
| 12 |
+
"""Generate a lesson plan with progress updates"""
|
| 13 |
+
|
| 14 |
+
# Show progress bar
|
| 15 |
+
yield PROGRESS_HTML, ""
|
| 16 |
+
|
| 17 |
+
print(f"🚀 Starting lesson plan generation for: {topic}")
|
| 18 |
+
print(f"📚 Subject: {subject}, Grade: {grade_level}, Duration: {duration}min")
|
| 19 |
+
|
| 20 |
+
# Try Hugging Face API first if token is available
|
| 21 |
+
if HF_TOKEN:
|
| 22 |
+
print("🤖 Token found! Attempting Hugging Face API call...")
|
| 23 |
+
try:
|
| 24 |
+
prompt = f"""Create a comprehensive lesson plan for:
|
| 25 |
+
Topic: {topic}
|
| 26 |
+
Subject: {subject}
|
| 27 |
+
Grade Level: {grade_level}
|
| 28 |
+
Duration: {duration} minutes
|
| 29 |
+
Difficulty: {difficulty}
|
| 30 |
+
|
| 31 |
+
Include:
|
| 32 |
+
1. Learning objectives
|
| 33 |
+
2. Activities with time allocations
|
| 34 |
+
3. Materials needed
|
| 35 |
+
4. Assessment methods
|
| 36 |
+
5. Differentiation strategies
|
| 37 |
+
6. Educational standards
|
| 38 |
+
7. Real-world connections
|
| 39 |
+
8. Extension activities
|
| 40 |
+
|
| 41 |
+
Make it practical, engaging, and ready for classroom use."""
|
| 42 |
+
|
| 43 |
+
result = call_hugging_face_api_content(prompt)
|
| 44 |
+
|
| 45 |
+
if result:
|
| 46 |
+
print("✅ Successfully generated with Hugging Face API")
|
| 47 |
+
|
| 48 |
+
# Show completion and return result
|
| 49 |
+
yield COMPLETION_HTML, result
|
| 50 |
+
else:
|
| 51 |
+
print("❌ API call returned no content, falling back to algorithms")
|
| 52 |
+
raise Exception("No content returned from API")
|
| 53 |
+
|
| 54 |
+
except Exception as e:
|
| 55 |
+
print(f"❌ Hugging Face API failed: {e}")
|
| 56 |
+
print("🔄 Falling back to educational algorithms...")
|
| 57 |
+
|
| 58 |
+
# Show fallback progress
|
| 59 |
+
yield FALLBACK_HTML, ""
|
| 60 |
+
|
| 61 |
+
result = generate_with_algorithms(topic, subject, grade_level, duration, difficulty)
|
| 62 |
+
print("✅ Generated with educational algorithms")
|
| 63 |
+
|
| 64 |
+
# Show completion
|
| 65 |
+
yield COMPLETION_HTML, result
|
| 66 |
+
else:
|
| 67 |
+
print("⚠️ No Hugging Face token found, using educational algorithms...")
|
| 68 |
+
|
| 69 |
+
# Show progress for algorithms
|
| 70 |
+
yield FALLBACK_HTML, ""
|
| 71 |
+
|
| 72 |
+
result = generate_with_algorithms(topic, subject, grade_level, duration, difficulty)
|
| 73 |
+
print("✅ Generated with educational algorithms")
|
| 74 |
+
|
| 75 |
+
# Show completion
|
| 76 |
+
yield COMPLETION_HTML, result
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def generate_lesson_plan(topic, subject, grade_level, duration, difficulty="intermediate"):
|
| 80 |
+
"""Generate a lesson plan using Hugging Face API or fallback algorithms"""
|
| 81 |
+
|
| 82 |
+
print(f"🚀 Starting lesson plan generation for: {topic}")
|
| 83 |
+
print(f"📚 Subject: {subject}, Grade: {grade_level}, Duration: {duration}min")
|
| 84 |
+
|
| 85 |
+
# Try Hugging Face API first if token is available
|
| 86 |
+
if HF_TOKEN:
|
| 87 |
+
print("🤖 Token found! Attempting Hugging Face API call...")
|
| 88 |
+
try:
|
| 89 |
+
result = generate_with_hugging_face(topic, subject, grade_level, duration, difficulty)
|
| 90 |
+
print("✅ Successfully generated with Hugging Face API")
|
| 91 |
+
return result
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"❌ Hugging Face API failed: {e}")
|
| 94 |
+
print("🔄 Falling back to educational algorithms...")
|
| 95 |
+
else:
|
| 96 |
+
print("⚠️ No Hugging Face token found, using educational algorithms...")
|
| 97 |
+
|
| 98 |
+
# Fallback to educational algorithms
|
| 99 |
+
result = generate_with_algorithms(topic, subject, grade_level, duration, difficulty)
|
| 100 |
+
print("✅ Generated with educational algorithms")
|
| 101 |
+
return result
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def generate_with_hugging_face(topic, subject, grade_level, duration, difficulty):
|
| 105 |
+
"""Generate lesson plan using Hugging Face API"""
|
| 106 |
+
prompt = f"""Create a comprehensive lesson plan for:
|
| 107 |
+
Topic: {topic}
|
| 108 |
+
Subject: {subject}
|
| 109 |
+
Grade Level: {grade_level}
|
| 110 |
+
Duration: {duration} minutes
|
| 111 |
+
Difficulty: {difficulty}
|
| 112 |
+
|
| 113 |
+
Include:
|
| 114 |
+
1. Learning objectives
|
| 115 |
+
2. Activities with time allocations
|
| 116 |
+
3. Materials needed
|
| 117 |
+
4. Assessment methods
|
| 118 |
+
5. Differentiation strategies
|
| 119 |
+
6. Educational standards
|
| 120 |
+
7. Real-world connections
|
| 121 |
+
8. Extension activities
|
| 122 |
+
|
| 123 |
+
Make it practical, engaging, and ready for classroom use."""
|
| 124 |
+
|
| 125 |
+
result = call_hugging_face_api_content(prompt)
|
| 126 |
+
|
| 127 |
+
if result:
|
| 128 |
+
return format_lesson_plan(result, topic, subject, grade_level, duration, True)
|
| 129 |
+
else:
|
| 130 |
+
raise Exception("No content returned from API")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def generate_with_algorithms(topic, subject, grade_level, duration, difficulty):
|
| 134 |
+
"""Generate lesson plan using educational algorithms"""
|
| 135 |
+
|
| 136 |
+
# Generate lesson plan components
|
| 137 |
+
objectives = generate_objectives(topic, grade_level)
|
| 138 |
+
activities = generate_activities(topic, duration, grade_level)
|
| 139 |
+
materials = generate_materials(subject, grade_level)
|
| 140 |
+
assessment = generate_assessment(grade_level)
|
| 141 |
+
differentiation = generate_differentiation()
|
| 142 |
+
|
| 143 |
+
# Create structured lesson plan
|
| 144 |
+
lesson_plan = {
|
| 145 |
+
"topic": topic,
|
| 146 |
+
"subject": subject,
|
| 147 |
+
"grade_level": grade_level,
|
| 148 |
+
"duration": duration,
|
| 149 |
+
"difficulty": difficulty,
|
| 150 |
+
"objectives": objectives,
|
| 151 |
+
"activities": activities,
|
| 152 |
+
"materials": materials,
|
| 153 |
+
"assessment": assessment,
|
| 154 |
+
"differentiation": differentiation
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
return format_lesson_plan_from_dict(lesson_plan)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def generate_objectives(topic, grade):
|
| 161 |
+
"""Generate learning objectives for a topic and grade level"""
|
| 162 |
+
objectives = [
|
| 163 |
+
f"Students will understand the key concepts of {topic}",
|
| 164 |
+
f"Students will be able to explain {topic} in their own words",
|
| 165 |
+
f"Students will apply knowledge of {topic} to solve problems"
|
| 166 |
+
]
|
| 167 |
+
|
| 168 |
+
if grade in ["6th", "7th", "8th"]:
|
| 169 |
+
objectives.append(f"Students will analyze the relationship between {topic} and real-world applications")
|
| 170 |
+
elif grade in ["9th", "10th", "11th", "12th"]:
|
| 171 |
+
objectives.append(f"Students will evaluate different perspectives on {topic}")
|
| 172 |
+
objectives.append(f"Students will create original work demonstrating understanding of {topic}")
|
| 173 |
+
|
| 174 |
+
return objectives
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def generate_activities(topic, duration, grade):
|
| 178 |
+
"""Generate activities for a lesson plan"""
|
| 179 |
+
activities = []
|
| 180 |
+
|
| 181 |
+
# Opening activity (10% of duration)
|
| 182 |
+
opening_time = max(5, duration // 10)
|
| 183 |
+
activities.append({
|
| 184 |
+
"name": f"Introduction to {topic}",
|
| 185 |
+
"duration": f"{opening_time} minutes",
|
| 186 |
+
"description": f"Engage students with a thought-provoking question about {topic}"
|
| 187 |
+
})
|
| 188 |
+
|
| 189 |
+
# Main activities (70% of duration)
|
| 190 |
+
main_time = int(duration * 0.7)
|
| 191 |
+
if main_time > 20:
|
| 192 |
+
activities.append({
|
| 193 |
+
"name": f"Explore {topic}",
|
| 194 |
+
"duration": f"{main_time // 2} minutes",
|
| 195 |
+
"description": f"Hands-on exploration of {topic} concepts"
|
| 196 |
+
})
|
| 197 |
+
activities.append({
|
| 198 |
+
"name": f"Practice with {topic}",
|
| 199 |
+
"duration": f"{main_time - main_time // 2} minutes",
|
| 200 |
+
"description": f"Guided practice and application of {topic}"
|
| 201 |
+
})
|
| 202 |
+
else:
|
| 203 |
+
activities.append({
|
| 204 |
+
"name": f"Learn about {topic}",
|
| 205 |
+
"duration": f"{main_time} minutes",
|
| 206 |
+
"description": f"Interactive learning about {topic}"
|
| 207 |
+
})
|
| 208 |
+
|
| 209 |
+
# Closing activity (20% of duration)
|
| 210 |
+
closing_time = max(5, duration - opening_time - main_time)
|
| 211 |
+
activities.append({
|
| 212 |
+
"name": f"Reflect on {topic}",
|
| 213 |
+
"duration": f"{closing_time} minutes",
|
| 214 |
+
"description": f"Students share what they learned about {topic}"
|
| 215 |
+
})
|
| 216 |
+
|
| 217 |
+
return activities
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def generate_materials(subject, grade):
|
| 221 |
+
"""Generate materials list for a lesson"""
|
| 222 |
+
base_materials = [
|
| 223 |
+
"Whiteboard and markers",
|
| 224 |
+
"Student notebooks",
|
| 225 |
+
"Pencils and erasers"
|
| 226 |
+
]
|
| 227 |
+
|
| 228 |
+
if subject.lower() in ["science", "biology", "chemistry", "physics"]:
|
| 229 |
+
base_materials.extend([
|
| 230 |
+
"Safety goggles",
|
| 231 |
+
"Lab materials (as needed)",
|
| 232 |
+
"Scientific calculator"
|
| 233 |
+
])
|
| 234 |
+
elif subject.lower() in ["math", "mathematics", "algebra", "geometry"]:
|
| 235 |
+
base_materials.extend([
|
| 236 |
+
"Graph paper",
|
| 237 |
+
"Ruler and compass",
|
| 238 |
+
"Calculator"
|
| 239 |
+
])
|
| 240 |
+
elif subject.lower() in ["english", "language arts", "literature"]:
|
| 241 |
+
base_materials.extend([
|
| 242 |
+
"Texts or reading materials",
|
| 243 |
+
"Dictionary",
|
| 244 |
+
"Highlighters"
|
| 245 |
+
])
|
| 246 |
+
|
| 247 |
+
return base_materials
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def generate_assessment(grade):
|
| 251 |
+
"""Generate assessment methods for a lesson"""
|
| 252 |
+
assessments = [
|
| 253 |
+
"Exit ticket with key questions",
|
| 254 |
+
"Class participation and discussion",
|
| 255 |
+
"Quick quiz on main concepts"
|
| 256 |
+
]
|
| 257 |
+
|
| 258 |
+
if grade in ["9th", "10th", "11th", "12th"]:
|
| 259 |
+
assessments.append("Short written reflection")
|
| 260 |
+
assessments.append("Peer review activity")
|
| 261 |
+
|
| 262 |
+
return assessments
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def generate_differentiation():
|
| 266 |
+
"""Generate differentiation strategies"""
|
| 267 |
+
return [
|
| 268 |
+
"Provide visual aids for visual learners",
|
| 269 |
+
"Offer hands-on activities for kinesthetic learners",
|
| 270 |
+
"Use audio explanations for auditory learners",
|
| 271 |
+
"Provide additional support for struggling students",
|
| 272 |
+
"Offer extension activities for advanced students"
|
| 273 |
+
]
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def format_lesson_plan(content, topic, subject, grade_level, duration, ai_generated):
|
| 277 |
+
"""Format lesson plan content with proper styling"""
|
| 278 |
+
|
| 279 |
+
# Process content with images and math
|
| 280 |
+
processed_content = process_content_with_images(content, topic, "lesson plan")
|
| 281 |
+
|
| 282 |
+
# Apply content styling
|
| 283 |
+
styled_content = apply_content_styling(processed_content, "lesson plan")
|
| 284 |
+
|
| 285 |
+
# Add header information
|
| 286 |
+
header = f"""# 📚 Lesson Plan: {topic}
|
| 287 |
+
|
| 288 |
+
**Subject:** {subject}
|
| 289 |
+
**Grade Level:** {grade_level}
|
| 290 |
+
**Duration:** {duration} minutes
|
| 291 |
+
**Generated by:** {'AI Assistant' if ai_generated else 'Educational Algorithms'}
|
| 292 |
+
|
| 293 |
+
---
|
| 294 |
+
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
return header + styled_content
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def format_lesson_plan_from_dict(lesson_plan):
|
| 301 |
+
"""Format lesson plan from dictionary structure"""
|
| 302 |
+
|
| 303 |
+
content = f"""# 📚 Lesson Plan: {lesson_plan['topic']}
|
| 304 |
+
|
| 305 |
+
**Subject:** {lesson_plan['subject']}
|
| 306 |
+
**Grade Level:** {lesson_plan['grade_level']}
|
| 307 |
+
**Duration:** {lesson_plan['duration']} minutes
|
| 308 |
+
**Difficulty:** {lesson_plan['difficulty']}
|
| 309 |
+
**Generated by:** Educational Algorithms
|
| 310 |
+
|
| 311 |
+
---
|
| 312 |
+
|
| 313 |
+
## 🎯 Learning Objectives
|
| 314 |
+
|
| 315 |
+
"""
|
| 316 |
+
|
| 317 |
+
for i, objective in enumerate(lesson_plan['objectives'], 1):
|
| 318 |
+
content += f"{i}. {objective}\n"
|
| 319 |
+
|
| 320 |
+
content += "\n## 📋 Activities\n\n"
|
| 321 |
+
|
| 322 |
+
for activity in lesson_plan['activities']:
|
| 323 |
+
content += f"### {activity['name']} ({activity['duration']})\n"
|
| 324 |
+
content += f"{activity['description']}\n\n"
|
| 325 |
+
|
| 326 |
+
content += "## 📦 Materials Needed\n\n"
|
| 327 |
+
for material in lesson_plan['materials']:
|
| 328 |
+
content += f"- {material}\n"
|
| 329 |
+
|
| 330 |
+
content += "\n## 📊 Assessment Methods\n\n"
|
| 331 |
+
for assessment in lesson_plan['assessment']:
|
| 332 |
+
content += f"- {assessment}\n"
|
| 333 |
+
|
| 334 |
+
content += "\n## 🎨 Differentiation Strategies\n\n"
|
| 335 |
+
for strategy in lesson_plan['differentiation']:
|
| 336 |
+
content += f"- {strategy}\n"
|
| 337 |
+
|
| 338 |
+
return content
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def generate_quiz_with_progress(topic, subject, grade_level, question_count, question_types):
|
| 342 |
+
"""Generate quiz with progress updates"""
|
| 343 |
+
|
| 344 |
+
# Show progress bar
|
| 345 |
+
yield PROGRESS_HTML, ""
|
| 346 |
+
|
| 347 |
+
print(f"🚀 Starting quiz generation for: {topic}")
|
| 348 |
+
print(f"📚 Subject: {subject}, Grade: {grade_level}, Questions: {question_count}")
|
| 349 |
+
|
| 350 |
+
# Try Hugging Face API first if token is available
|
| 351 |
+
if HF_TOKEN:
|
| 352 |
+
print("🤖 Token found! Attempting Hugging Face API call...")
|
| 353 |
+
try:
|
| 354 |
+
result = generate_quiz_with_hugging_face(topic, subject, grade_level, question_count, question_types)
|
| 355 |
+
print("✅ Successfully generated with Hugging Face API")
|
| 356 |
+
|
| 357 |
+
# Show completion and return result
|
| 358 |
+
yield COMPLETION_HTML, result
|
| 359 |
+
except Exception as e:
|
| 360 |
+
print(f"❌ Hugging Face API failed: {e}")
|
| 361 |
+
print("🔄 Falling back to educational algorithms...")
|
| 362 |
+
|
| 363 |
+
# Show fallback progress
|
| 364 |
+
yield FALLBACK_HTML, ""
|
| 365 |
+
|
| 366 |
+
result = generate_quiz_with_algorithms(topic, subject, grade_level, question_count, question_types)
|
| 367 |
+
print("✅ Generated with educational algorithms")
|
| 368 |
+
|
| 369 |
+
# Show completion
|
| 370 |
+
yield COMPLETION_HTML, result
|
| 371 |
+
else:
|
| 372 |
+
print("⚠️ No Hugging Face token found, using educational algorithms...")
|
| 373 |
+
|
| 374 |
+
# Show progress for algorithms
|
| 375 |
+
yield FALLBACK_HTML, ""
|
| 376 |
+
|
| 377 |
+
result = generate_quiz_with_algorithms(topic, subject, grade_level, question_count, question_types)
|
| 378 |
+
print("✅ Generated with educational algorithms")
|
| 379 |
+
|
| 380 |
+
# Show completion
|
| 381 |
+
yield COMPLETION_HTML, result
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def generate_quiz(topic, subject, grade_level, question_count, question_types):
|
| 385 |
+
"""Generate quiz using Hugging Face API or fallback algorithms"""
|
| 386 |
+
|
| 387 |
+
print(f"🚀 Starting quiz generation for: {topic}")
|
| 388 |
+
print(f"📚 Subject: {subject}, Grade: {grade_level}, Questions: {question_count}")
|
| 389 |
+
|
| 390 |
+
# Try Hugging Face API first if token is available
|
| 391 |
+
if HF_TOKEN:
|
| 392 |
+
print("🤖 Token found! Attempting Hugging Face API call...")
|
| 393 |
+
try:
|
| 394 |
+
result = generate_quiz_with_hugging_face(topic, subject, grade_level, question_count, question_types)
|
| 395 |
+
print("✅ Successfully generated with Hugging Face API")
|
| 396 |
+
return result
|
| 397 |
+
except Exception as e:
|
| 398 |
+
print(f"❌ Hugging Face API failed: {e}")
|
| 399 |
+
print("🔄 Falling back to educational algorithms...")
|
| 400 |
+
else:
|
| 401 |
+
print("⚠️ No Hugging Face token found, using educational algorithms...")
|
| 402 |
+
|
| 403 |
+
# Fallback to educational algorithms
|
| 404 |
+
result = generate_quiz_with_algorithms(topic, subject, grade_level, question_count, question_types)
|
| 405 |
+
print("✅ Generated with educational algorithms")
|
| 406 |
+
return result
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
def generate_quiz_with_hugging_face(topic, subject, grade_level, question_count, question_types):
|
| 410 |
+
"""Generate quiz using Hugging Face API"""
|
| 411 |
+
prompt = f"""Create a {question_count}-question quiz about {topic} for {grade_level} grade {subject} students.
|
| 412 |
+
|
| 413 |
+
Question types to include: {', '.join(question_types)}
|
| 414 |
+
|
| 415 |
+
Make the questions:
|
| 416 |
+
- Age-appropriate for {grade_level} grade
|
| 417 |
+
- Clear and unambiguous
|
| 418 |
+
- Cover different aspects of {topic}
|
| 419 |
+
- Include a mix of difficulty levels
|
| 420 |
+
- Provide clear answer choices for multiple choice questions
|
| 421 |
+
|
| 422 |
+
Format each question with:
|
| 423 |
+
1. Question number
|
| 424 |
+
2. Question text
|
| 425 |
+
3. Answer choices (for multiple choice)
|
| 426 |
+
4. Correct answer
|
| 427 |
+
5. Brief explanation
|
| 428 |
+
|
| 429 |
+
Topic: {topic}
|
| 430 |
+
Subject: {subject}
|
| 431 |
+
Grade: {grade_level}
|
| 432 |
+
Number of questions: {question_count}"""
|
| 433 |
+
|
| 434 |
+
result = call_hugging_face_api_content(prompt)
|
| 435 |
+
|
| 436 |
+
if result:
|
| 437 |
+
return format_quiz(result, topic, subject, grade_level, question_count, True)
|
| 438 |
+
else:
|
| 439 |
+
raise Exception("No content returned from API")
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def generate_quiz_with_algorithms(topic, subject, grade_level, question_count, question_types):
|
| 443 |
+
"""Generate quiz using educational algorithms"""
|
| 444 |
+
|
| 445 |
+
questions = []
|
| 446 |
+
|
| 447 |
+
for i in range(question_count):
|
| 448 |
+
# Select question type (cycle through available types)
|
| 449 |
+
question_type = question_types[i % len(question_types)]
|
| 450 |
+
|
| 451 |
+
question = generate_question_by_type(question_type, topic, subject, grade_level, i + 1)
|
| 452 |
+
questions.append(question)
|
| 453 |
+
|
| 454 |
+
# Create quiz structure
|
| 455 |
+
quiz = {
|
| 456 |
+
"topic": topic,
|
| 457 |
+
"subject": subject,
|
| 458 |
+
"grade_level": grade_level,
|
| 459 |
+
"question_count": question_count,
|
| 460 |
+
"questions": questions
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
return format_quiz_from_dict(quiz)
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
def generate_question_by_type(question_type, topic, subject, grade, question_number):
|
| 467 |
+
"""Generate a question of specific type"""
|
| 468 |
+
|
| 469 |
+
if question_type == "Multiple Choice":
|
| 470 |
+
return generate_multiple_choice_question(topic, subject, grade, question_number)
|
| 471 |
+
elif question_type == "True/False":
|
| 472 |
+
return generate_true_false_question(topic, subject, grade, question_number)
|
| 473 |
+
elif question_type == "Short Answer":
|
| 474 |
+
return generate_short_answer_question(topic, subject, grade, question_number)
|
| 475 |
+
elif question_type == "Fill in the Blank":
|
| 476 |
+
return generate_fill_in_blank_question(topic, subject, grade, question_number)
|
| 477 |
+
else:
|
| 478 |
+
return generate_multiple_choice_question(topic, subject, grade, question_number)
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def generate_multiple_choice_question(topic, subject, grade, question_number):
|
| 482 |
+
"""Generate a multiple choice question"""
|
| 483 |
+
|
| 484 |
+
questions = [
|
| 485 |
+
{
|
| 486 |
+
"question": f"What is the main concept of {topic}?",
|
| 487 |
+
"options": [
|
| 488 |
+
f"The primary idea behind {topic}",
|
| 489 |
+
f"A secondary aspect of {topic}",
|
| 490 |
+
f"An unrelated concept to {topic}",
|
| 491 |
+
f"A complex theory about {topic}"
|
| 492 |
+
],
|
| 493 |
+
"correct": 0,
|
| 494 |
+
"explanation": f"The main concept of {topic} is the primary idea that students need to understand."
|
| 495 |
+
},
|
| 496 |
+
{
|
| 497 |
+
"question": f"Which of the following best describes {topic}?",
|
| 498 |
+
"options": [
|
| 499 |
+
f"A simple explanation of {topic}",
|
| 500 |
+
f"A complex theory about {topic}",
|
| 501 |
+
f"An advanced concept in {topic}",
|
| 502 |
+
f"A basic principle of {topic}"
|
| 503 |
+
],
|
| 504 |
+
"correct": 3,
|
| 505 |
+
"explanation": f"{topic} is best described as a basic principle that students should learn."
|
| 506 |
+
}
|
| 507 |
+
]
|
| 508 |
+
|
| 509 |
+
selected = questions[question_number % len(questions)]
|
| 510 |
+
return {
|
| 511 |
+
"type": "Multiple Choice",
|
| 512 |
+
"question": selected["question"],
|
| 513 |
+
"options": selected["options"],
|
| 514 |
+
"correct_answer": selected["options"][selected["correct"]],
|
| 515 |
+
"explanation": selected["explanation"]
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def generate_true_false_question(topic, subject, grade, question_number):
|
| 520 |
+
"""Generate a true/false question"""
|
| 521 |
+
|
| 522 |
+
questions = [
|
| 523 |
+
{
|
| 524 |
+
"question": f"{topic} is an important concept in {subject}.",
|
| 525 |
+
"answer": True,
|
| 526 |
+
"explanation": f"Yes, {topic} is indeed an important concept that students need to understand in {subject}."
|
| 527 |
+
},
|
| 528 |
+
{
|
| 529 |
+
"question": f"Understanding {topic} requires advanced knowledge.",
|
| 530 |
+
"answer": False,
|
| 531 |
+
"explanation": f"While {topic} can be complex, it can be understood at the {grade} grade level with proper instruction."
|
| 532 |
+
}
|
| 533 |
+
]
|
| 534 |
+
|
| 535 |
+
selected = questions[question_number % len(questions)]
|
| 536 |
+
return {
|
| 537 |
+
"type": "True/False",
|
| 538 |
+
"question": selected["question"],
|
| 539 |
+
"correct_answer": "True" if selected["answer"] else "False",
|
| 540 |
+
"explanation": selected["explanation"]
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
def generate_short_answer_question(topic, subject, grade, question_number):
|
| 545 |
+
"""Generate a short answer question"""
|
| 546 |
+
|
| 547 |
+
questions = [
|
| 548 |
+
{
|
| 549 |
+
"question": f"Explain {topic} in your own words.",
|
| 550 |
+
"explanation": f"Students should provide a clear explanation of {topic} using their own understanding."
|
| 551 |
+
},
|
| 552 |
+
{
|
| 553 |
+
"question": f"What are the key components of {topic}?",
|
| 554 |
+
"explanation": f"Students should identify and explain the main parts or elements that make up {topic}."
|
| 555 |
+
}
|
| 556 |
+
]
|
| 557 |
+
|
| 558 |
+
selected = questions[question_number % len(questions)]
|
| 559 |
+
return {
|
| 560 |
+
"type": "Short Answer",
|
| 561 |
+
"question": selected["question"],
|
| 562 |
+
"correct_answer": "Open-ended response",
|
| 563 |
+
"explanation": selected["explanation"]
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
def generate_fill_in_blank_question(topic, subject, grade, question_number):
|
| 568 |
+
"""Generate a fill in the blank question"""
|
| 569 |
+
|
| 570 |
+
questions = [
|
| 571 |
+
{
|
| 572 |
+
"question": f"_______ is the main concept we learned about {topic}.",
|
| 573 |
+
"answer": topic,
|
| 574 |
+
"explanation": f"The blank should be filled with '{topic}' as it's the main concept being studied."
|
| 575 |
+
},
|
| 576 |
+
{
|
| 577 |
+
"question": f"In {subject}, we study _______ to understand {topic}.",
|
| 578 |
+
"answer": "concepts",
|
| 579 |
+
"explanation": f"The blank should be filled with 'concepts' as we study various concepts to understand {topic}."
|
| 580 |
+
}
|
| 581 |
+
]
|
| 582 |
+
|
| 583 |
+
selected = questions[question_number % len(questions)]
|
| 584 |
+
return {
|
| 585 |
+
"type": "Fill in the Blank",
|
| 586 |
+
"question": selected["question"],
|
| 587 |
+
"correct_answer": selected["answer"],
|
| 588 |
+
"explanation": selected["explanation"]
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def format_quiz(content, topic, subject, grade_level, question_count, ai_generated):
|
| 593 |
+
"""Format quiz content with proper styling"""
|
| 594 |
+
|
| 595 |
+
# Process content with images and math
|
| 596 |
+
processed_content = process_content_with_images(content, topic, "quiz")
|
| 597 |
+
|
| 598 |
+
# Apply content styling
|
| 599 |
+
styled_content = apply_content_styling(processed_content, "quiz")
|
| 600 |
+
|
| 601 |
+
# Add header information
|
| 602 |
+
header = f"""# 📝 Quiz: {topic}
|
| 603 |
+
|
| 604 |
+
**Subject:** {subject}
|
| 605 |
+
**Grade Level:** {grade_level}
|
| 606 |
+
**Number of Questions:** {question_count}
|
| 607 |
+
**Generated by:** {'AI Assistant' if ai_generated else 'Educational Algorithms'}
|
| 608 |
+
|
| 609 |
+
---
|
| 610 |
+
|
| 611 |
+
"""
|
| 612 |
+
|
| 613 |
+
return header + styled_content
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def format_quiz_from_dict(quiz):
|
| 617 |
+
"""Format quiz from dictionary structure"""
|
| 618 |
+
|
| 619 |
+
content = f"""# 📝 Quiz: {quiz['topic']}
|
| 620 |
+
|
| 621 |
+
**Subject:** {quiz['subject']}
|
| 622 |
+
**Grade Level:** {quiz['grade_level']}
|
| 623 |
+
**Number of Questions:** {quiz['question_count']}
|
| 624 |
+
**Generated by:** Educational Algorithms
|
| 625 |
+
|
| 626 |
+
---
|
| 627 |
+
|
| 628 |
+
"""
|
| 629 |
+
|
| 630 |
+
for i, question in enumerate(quiz['questions'], 1):
|
| 631 |
+
content += f"## Question {i} ({question['type']})\n\n"
|
| 632 |
+
content += f"{question['question']}\n\n"
|
| 633 |
+
|
| 634 |
+
if question['type'] == "Multiple Choice":
|
| 635 |
+
for j, option in enumerate(question['options'], 1):
|
| 636 |
+
content += f"{j}. {option}\n"
|
| 637 |
+
content += "\n"
|
| 638 |
+
|
| 639 |
+
content += f"**Answer:** {question['correct_answer']}\n\n"
|
| 640 |
+
content += f"**Explanation:** {question['explanation']}\n\n"
|
| 641 |
+
content += "---\n\n"
|
| 642 |
+
|
| 643 |
+
return content
|
database.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database module for BrightMind AI
|
| 3 |
+
Handles all database operations including feedback storage and retrieval
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sqlite3
|
| 7 |
+
from datetime import datetime
|
| 8 |
+
from config import DATABASE_FILE
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def init_database():
|
| 12 |
+
"""Initialize the SQLite database"""
|
| 13 |
+
try:
|
| 14 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 15 |
+
cursor = conn.cursor()
|
| 16 |
+
|
| 17 |
+
# Create feedback table
|
| 18 |
+
cursor.execute('''
|
| 19 |
+
CREATE TABLE IF NOT EXISTS feedback (
|
| 20 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 21 |
+
timestamp TEXT NOT NULL,
|
| 22 |
+
feedback_type TEXT NOT NULL,
|
| 23 |
+
rating INTEGER NOT NULL,
|
| 24 |
+
comments TEXT NOT NULL,
|
| 25 |
+
user_email TEXT,
|
| 26 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 27 |
+
)
|
| 28 |
+
''')
|
| 29 |
+
|
| 30 |
+
conn.commit()
|
| 31 |
+
conn.close()
|
| 32 |
+
print("✅ Database initialized successfully")
|
| 33 |
+
return True
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"❌ Database initialization error: {e}")
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def save_feedback_to_db(feedback_type, rating, comments, user_email=""):
|
| 40 |
+
"""Save feedback to SQLite database"""
|
| 41 |
+
try:
|
| 42 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 43 |
+
cursor = conn.cursor()
|
| 44 |
+
|
| 45 |
+
# Convert feedback_type list to string
|
| 46 |
+
feedback_type_str = ', '.join(feedback_type) if isinstance(feedback_type, list) else str(feedback_type)
|
| 47 |
+
|
| 48 |
+
cursor.execute('''
|
| 49 |
+
INSERT INTO feedback (timestamp, feedback_type, rating, comments, user_email)
|
| 50 |
+
VALUES (?, ?, ?, ?, ?)
|
| 51 |
+
''', (
|
| 52 |
+
datetime.now().isoformat(),
|
| 53 |
+
feedback_type_str,
|
| 54 |
+
rating,
|
| 55 |
+
comments,
|
| 56 |
+
user_email
|
| 57 |
+
))
|
| 58 |
+
|
| 59 |
+
conn.commit()
|
| 60 |
+
conn.close()
|
| 61 |
+
print("✅ Feedback saved to database successfully")
|
| 62 |
+
return True
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"❌ Database save error: {e}")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_feedback_stats():
|
| 69 |
+
"""Get feedback statistics for admin"""
|
| 70 |
+
try:
|
| 71 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 72 |
+
cursor = conn.cursor()
|
| 73 |
+
|
| 74 |
+
# Get total feedback count
|
| 75 |
+
cursor.execute('SELECT COUNT(*) FROM feedback')
|
| 76 |
+
total_feedback = cursor.fetchone()[0]
|
| 77 |
+
|
| 78 |
+
# Get average rating
|
| 79 |
+
cursor.execute('SELECT AVG(rating) FROM feedback')
|
| 80 |
+
avg_rating = cursor.fetchone()[0]
|
| 81 |
+
avg_rating = round(avg_rating, 2) if avg_rating else 0
|
| 82 |
+
|
| 83 |
+
# Get recent feedback (last 5)
|
| 84 |
+
cursor.execute('''
|
| 85 |
+
SELECT timestamp, feedback_type, rating, comments, user_email
|
| 86 |
+
FROM feedback
|
| 87 |
+
ORDER BY created_at DESC
|
| 88 |
+
LIMIT 5
|
| 89 |
+
''')
|
| 90 |
+
recent_feedback = cursor.fetchall()
|
| 91 |
+
|
| 92 |
+
conn.close()
|
| 93 |
+
|
| 94 |
+
return {
|
| 95 |
+
'total_feedback': total_feedback,
|
| 96 |
+
'avg_rating': avg_rating,
|
| 97 |
+
'recent_feedback': recent_feedback
|
| 98 |
+
}
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"❌ Database stats error: {e}")
|
| 101 |
+
return {
|
| 102 |
+
'total_feedback': 0,
|
| 103 |
+
'avg_rating': 0,
|
| 104 |
+
'recent_feedback': []
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def view_feedback_admin():
|
| 109 |
+
"""Admin function to view all feedback"""
|
| 110 |
+
try:
|
| 111 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 112 |
+
cursor = conn.cursor()
|
| 113 |
+
|
| 114 |
+
cursor.execute('''
|
| 115 |
+
SELECT id, timestamp, feedback_type, rating, comments, user_email, created_at
|
| 116 |
+
FROM feedback
|
| 117 |
+
ORDER BY created_at DESC
|
| 118 |
+
''')
|
| 119 |
+
|
| 120 |
+
feedback_data = cursor.fetchall()
|
| 121 |
+
conn.close()
|
| 122 |
+
|
| 123 |
+
if not feedback_data:
|
| 124 |
+
return "No feedback found in database."
|
| 125 |
+
|
| 126 |
+
# Format feedback data
|
| 127 |
+
result = "# 📊 Feedback Database\n\n"
|
| 128 |
+
result += f"**Total Feedback Entries:** {len(feedback_data)}\n\n"
|
| 129 |
+
|
| 130 |
+
for entry in feedback_data:
|
| 131 |
+
id_val, timestamp, feedback_type, rating, comments, user_email, created_at = entry
|
| 132 |
+
result += f"## Entry #{id_val}\n"
|
| 133 |
+
result += f"**Date:** {timestamp}\n"
|
| 134 |
+
result += f"**Type:** {feedback_type}\n"
|
| 135 |
+
result += f"**Rating:** {rating}/5 ⭐\n"
|
| 136 |
+
result += f"**Comments:** {comments}\n"
|
| 137 |
+
result += f"**Email:** {user_email if user_email else 'Not provided'}\n"
|
| 138 |
+
result += f"**Created:** {created_at}\n"
|
| 139 |
+
result += "---\n\n"
|
| 140 |
+
|
| 141 |
+
return result
|
| 142 |
+
except Exception as e:
|
| 143 |
+
return f"❌ Error accessing database: {str(e)}"
|
email_service.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Email service module for BrightMind AI
|
| 3 |
+
Handles email functionality including feedback notifications
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import smtplib
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from email.mime.text import MIMEText
|
| 10 |
+
from email.mime.multipart import MIMEMultipart
|
| 11 |
+
from config import FEEDBACK_EMAIL, SMTP_SERVER, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD
|
| 12 |
+
from database import save_feedback_to_db
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def send_feedback_email(feedback_type, rating, comments, user_email=""):
|
| 16 |
+
"""Send feedback email to admin"""
|
| 17 |
+
|
| 18 |
+
if not SMTP_USERNAME or not SMTP_PASSWORD:
|
| 19 |
+
print("⚠️ SMTP credentials not configured, storing feedback locally")
|
| 20 |
+
# Store feedback locally if no email configured
|
| 21 |
+
feedback_data = {
|
| 22 |
+
"timestamp": datetime.now().isoformat(),
|
| 23 |
+
"feedback_type": feedback_type,
|
| 24 |
+
"rating": rating,
|
| 25 |
+
"comments": comments,
|
| 26 |
+
"user_email": user_email
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# Save to local file
|
| 30 |
+
try:
|
| 31 |
+
with open("feedback_log.json", "a") as f:
|
| 32 |
+
f.write(json.dumps(feedback_data) + "\n")
|
| 33 |
+
return "✅ Feedback saved successfully! (Email not configured)"
|
| 34 |
+
except Exception as e:
|
| 35 |
+
return f"❌ Error saving feedback: {str(e)}"
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
# Create message
|
| 39 |
+
msg = MIMEMultipart()
|
| 40 |
+
msg['From'] = SMTP_USERNAME
|
| 41 |
+
msg['To'] = FEEDBACK_EMAIL
|
| 42 |
+
msg['Subject'] = f"BrightMind AI Feedback - {feedback_type}"
|
| 43 |
+
|
| 44 |
+
# Create email body
|
| 45 |
+
body = f"""
|
| 46 |
+
New Feedback Received from BrightMind AI Platform
|
| 47 |
+
|
| 48 |
+
Feedback Type: {', '.join(feedback_type) if isinstance(feedback_type, list) else feedback_type}
|
| 49 |
+
Rating: {rating}/5
|
| 50 |
+
User Email: {user_email if user_email else 'Not provided'}
|
| 51 |
+
Comments: {comments}
|
| 52 |
+
|
| 53 |
+
Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
This feedback was automatically sent from the BrightMind AI platform.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
msg.attach(MIMEText(body, 'plain'))
|
| 60 |
+
|
| 61 |
+
# Send email
|
| 62 |
+
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
|
| 63 |
+
server.starttls()
|
| 64 |
+
server.login(SMTP_USERNAME, SMTP_PASSWORD)
|
| 65 |
+
text = msg.as_string()
|
| 66 |
+
server.sendmail(SMTP_USERNAME, FEEDBACK_EMAIL, text)
|
| 67 |
+
server.quit()
|
| 68 |
+
|
| 69 |
+
print(f"✅ Feedback email sent successfully to {FEEDBACK_EMAIL}")
|
| 70 |
+
return "✅ Thank you! Your feedback has been sent successfully."
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"❌ Error sending feedback email: {str(e)}")
|
| 74 |
+
return f"❌ Error sending feedback: {str(e)}"
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def submit_feedback(feedback_type, rating, comments, user_email):
|
| 78 |
+
"""Handle feedback submission"""
|
| 79 |
+
|
| 80 |
+
if not feedback_type:
|
| 81 |
+
return "❌ Please select at least one feedback type."
|
| 82 |
+
|
| 83 |
+
if not rating:
|
| 84 |
+
return "❌ Please provide a rating."
|
| 85 |
+
|
| 86 |
+
if not comments.strip():
|
| 87 |
+
return "❌ Please provide your comments."
|
| 88 |
+
|
| 89 |
+
# Save to database first
|
| 90 |
+
db_success = save_feedback_to_db(feedback_type, rating, comments, user_email)
|
| 91 |
+
|
| 92 |
+
if not db_success:
|
| 93 |
+
return "❌ Error saving feedback to database. Please try again."
|
| 94 |
+
|
| 95 |
+
# Try to send email (optional)
|
| 96 |
+
email_result = send_feedback_email(feedback_type, rating, comments, user_email)
|
| 97 |
+
|
| 98 |
+
if "✅" in email_result:
|
| 99 |
+
return "✅ Thank you! Your feedback has been saved and sent successfully."
|
| 100 |
+
else:
|
| 101 |
+
return "✅ Thank you! Your feedback has been saved successfully. (Email notification not available)"
|
ui_components.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
UI components module for BrightMind AI
|
| 3 |
+
Contains Gradio interface components and styling
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from config import debug_token_status
|
| 8 |
+
from content_generators import (
|
| 9 |
+
generate_lesson_plan_with_progress,
|
| 10 |
+
generate_quiz_with_progress,
|
| 11 |
+
generate_content_with_progress
|
| 12 |
+
)
|
| 13 |
+
from email_service import submit_feedback
|
| 14 |
+
from database import get_feedback_stats, view_feedback_admin
|
| 15 |
+
from ai_services import generate_albert_response
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def create_main_interface():
|
| 19 |
+
"""Create the main Gradio interface"""
|
| 20 |
+
|
| 21 |
+
# Debug token status
|
| 22 |
+
debug_token_status()
|
| 23 |
+
|
| 24 |
+
# Custom CSS for modern styling
|
| 25 |
+
custom_css = """
|
| 26 |
+
.modern-card {
|
| 27 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 28 |
+
border-radius: 20px;
|
| 29 |
+
padding: 30px;
|
| 30 |
+
margin: 20px 0;
|
| 31 |
+
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
| 32 |
+
color: white;
|
| 33 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.modern-card:hover {
|
| 37 |
+
transform: translateY(-5px);
|
| 38 |
+
box-shadow: 0 15px 40px rgba(0,0,0,0.2);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.slide-in-left {
|
| 42 |
+
animation: slideInLeft 0.6s ease-out;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.slide-in-right {
|
| 46 |
+
animation: slideInRight 0.6s ease-out;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
@keyframes slideInLeft {
|
| 50 |
+
from { transform: translateX(-100px); opacity: 0; }
|
| 51 |
+
to { transform: translateX(0); opacity: 1; }
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
@keyframes slideInRight {
|
| 55 |
+
from { transform: translateX(100px); opacity: 0; }
|
| 56 |
+
to { transform: translateX(0); opacity: 1; }
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.progress-container {
|
| 60 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
| 61 |
+
border-radius: 15px;
|
| 62 |
+
padding: 20px;
|
| 63 |
+
margin: 20px 0;
|
| 64 |
+
text-align: center;
|
| 65 |
+
border: 2px solid #667eea;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.progress-bar {
|
| 69 |
+
width: 100%;
|
| 70 |
+
height: 8px;
|
| 71 |
+
background: #e9ecef;
|
| 72 |
+
border-radius: 4px;
|
| 73 |
+
overflow: hidden;
|
| 74 |
+
margin: 15px 0;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.progress-fill {
|
| 78 |
+
height: 100%;
|
| 79 |
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
| 80 |
+
border-radius: 4px;
|
| 81 |
+
animation: progress 2s ease-in-out infinite;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
@keyframes progress {
|
| 85 |
+
0% { width: 0%; }
|
| 86 |
+
50% { width: 70%; }
|
| 87 |
+
100% { width: 100%; }
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.generation-status {
|
| 91 |
+
font-weight: 600;
|
| 92 |
+
color: #667eea;
|
| 93 |
+
margin: 10px 0;
|
| 94 |
+
font-size: 1.1rem;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.typewriter-container {
|
| 98 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
| 99 |
+
border-radius: 15px;
|
| 100 |
+
padding: 20px;
|
| 101 |
+
margin: 20px 0;
|
| 102 |
+
border-left: 4px solid #667eea;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.typewriter-header {
|
| 106 |
+
display: flex;
|
| 107 |
+
align-items: center;
|
| 108 |
+
margin-bottom: 15px;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.book-icon {
|
| 112 |
+
font-size: 24px;
|
| 113 |
+
margin-right: 10px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.typing-dots span {
|
| 117 |
+
animation: typing 1.4s infinite;
|
| 118 |
+
margin: 0 2px;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
| 122 |
+
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
| 123 |
+
|
| 124 |
+
@keyframes typing {
|
| 125 |
+
0%, 60%, 100% { opacity: 0; }
|
| 126 |
+
30% { opacity: 1; }
|
| 127 |
+
}
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
with gr.Blocks(css=custom_css, title="BrightMind AI - Educational Assistant") as interface:
|
| 131 |
+
|
| 132 |
+
# Header
|
| 133 |
+
gr.HTML("""
|
| 134 |
+
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 20px; margin-bottom: 30px;">
|
| 135 |
+
<h1 style="font-size: 3rem; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">🧠 BrightMind AI</h1>
|
| 136 |
+
<p style="font-size: 1.3rem; margin: 10px 0; opacity: 0.9;">Your Intelligent Educational Assistant</p>
|
| 137 |
+
<p style="font-size: 1rem; margin: 0; opacity: 0.8;">Generate lesson plans, quizzes, and educational content with AI</p>
|
| 138 |
+
</div>
|
| 139 |
+
""")
|
| 140 |
+
|
| 141 |
+
# Main tabs
|
| 142 |
+
with gr.Tabs():
|
| 143 |
+
|
| 144 |
+
# Albert Chat Tab
|
| 145 |
+
with gr.TabItem("🤖 Albert Chat"):
|
| 146 |
+
create_albert_chat_tab()
|
| 147 |
+
|
| 148 |
+
# Lesson Plan Generator Tab
|
| 149 |
+
with gr.TabItem("📚 Lesson Plan Generator"):
|
| 150 |
+
create_lesson_plan_tab()
|
| 151 |
+
|
| 152 |
+
# Quiz Generator Tab
|
| 153 |
+
with gr.TabItem("📝 Quiz Generator"):
|
| 154 |
+
create_quiz_tab()
|
| 155 |
+
|
| 156 |
+
# Content Generator Tab
|
| 157 |
+
with gr.TabItem("📄 Content Generator"):
|
| 158 |
+
create_content_tab()
|
| 159 |
+
|
| 160 |
+
# Feedback Tab
|
| 161 |
+
with gr.TabItem("💬 Feedback"):
|
| 162 |
+
create_feedback_tab()
|
| 163 |
+
|
| 164 |
+
# Admin Tab
|
| 165 |
+
with gr.TabItem("⚙️ Admin"):
|
| 166 |
+
create_admin_tab()
|
| 167 |
+
|
| 168 |
+
return interface
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def create_albert_chat_tab():
|
| 172 |
+
"""Create the Albert chat interface"""
|
| 173 |
+
|
| 174 |
+
with gr.Row():
|
| 175 |
+
with gr.Column(scale=1):
|
| 176 |
+
gr.HTML("""
|
| 177 |
+
<div class="modern-card slide-in-left">
|
| 178 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">🤖 Albert Chat</h2>
|
| 179 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">Chat with Albert, your AI learning buddy!</p>
|
| 180 |
+
""")
|
| 181 |
+
|
| 182 |
+
user_name = gr.Textbox(
|
| 183 |
+
label="👤 Your Name",
|
| 184 |
+
placeholder="Enter your name to start chatting",
|
| 185 |
+
value=""
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
user_age_group = gr.Dropdown(
|
| 189 |
+
choices=["Elementary (K-5)", "Middle School (6-8)", "High School (9-12)", "College", "Adult"],
|
| 190 |
+
label="🎓 Age Group",
|
| 191 |
+
value="Middle School (6-8)",
|
| 192 |
+
interactive=True
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
gr.HTML("</div>")
|
| 196 |
+
|
| 197 |
+
with gr.Column(scale=2):
|
| 198 |
+
gr.HTML("""
|
| 199 |
+
<div class="modern-card slide-in-right">
|
| 200 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">💬 Chat with Albert</h3>
|
| 201 |
+
""")
|
| 202 |
+
|
| 203 |
+
chat_display = gr.Chatbot(
|
| 204 |
+
label="",
|
| 205 |
+
height=400,
|
| 206 |
+
show_label=False,
|
| 207 |
+
container=True,
|
| 208 |
+
bubble_full_width=False
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
chat_input = gr.Textbox(
|
| 212 |
+
label="Type your message here...",
|
| 213 |
+
placeholder="Ask Albert anything about learning!",
|
| 214 |
+
lines=2
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
with gr.Row():
|
| 218 |
+
send_btn = gr.Button("Send", variant="primary")
|
| 219 |
+
clear_btn = gr.Button("Clear Chat", variant="secondary")
|
| 220 |
+
|
| 221 |
+
context_display = gr.HTML("", visible=False)
|
| 222 |
+
typing_indicator = gr.HTML("", visible=False)
|
| 223 |
+
|
| 224 |
+
gr.HTML("</div>")
|
| 225 |
+
|
| 226 |
+
# Event handlers
|
| 227 |
+
def send_message_with_typing(message, name, age_group, history):
|
| 228 |
+
"""Send message with typing effect"""
|
| 229 |
+
if not message.strip():
|
| 230 |
+
return history, "", "", ""
|
| 231 |
+
|
| 232 |
+
if history is None:
|
| 233 |
+
history = []
|
| 234 |
+
|
| 235 |
+
history.append([f"{name}: {message}", None])
|
| 236 |
+
|
| 237 |
+
# Generate Albert's response
|
| 238 |
+
albert_response = generate_albert_response(message, name, age_group, history)
|
| 239 |
+
history[-1][1] = albert_response
|
| 240 |
+
|
| 241 |
+
return history, "", "", ""
|
| 242 |
+
|
| 243 |
+
def clear_chat():
|
| 244 |
+
return [], "", "", ""
|
| 245 |
+
|
| 246 |
+
def initialize_chat(name, age_group):
|
| 247 |
+
if not name.strip():
|
| 248 |
+
return [], "", ""
|
| 249 |
+
|
| 250 |
+
welcome_message = f"Hi {name}! 🧠✨ I'm Albert, your learning buddy! What would you like to know about today? 😊🚀"
|
| 251 |
+
return [[None, welcome_message]], "", ""
|
| 252 |
+
|
| 253 |
+
# Connect event handlers
|
| 254 |
+
send_btn.click(
|
| 255 |
+
fn=send_message_with_typing,
|
| 256 |
+
inputs=[chat_input, user_name, user_age_group, chat_display],
|
| 257 |
+
outputs=[chat_display, chat_input, context_display, typing_indicator]
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
clear_btn.click(
|
| 261 |
+
fn=clear_chat,
|
| 262 |
+
outputs=[chat_display, context_display, context_display, typing_indicator]
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
user_name.change(
|
| 266 |
+
fn=initialize_chat,
|
| 267 |
+
inputs=[user_name, user_age_group],
|
| 268 |
+
outputs=[chat_display, context_display, context_display]
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def create_lesson_plan_tab():
|
| 273 |
+
"""Create the lesson plan generator interface"""
|
| 274 |
+
|
| 275 |
+
with gr.Row():
|
| 276 |
+
with gr.Column():
|
| 277 |
+
gr.HTML("""
|
| 278 |
+
<div class="modern-card slide-in-left">
|
| 279 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">📚 Lesson Plan Generator</h2>
|
| 280 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">Create comprehensive lesson plans with AI assistance</p>
|
| 281 |
+
""")
|
| 282 |
+
|
| 283 |
+
topic = gr.Textbox(label="📝 Topic", placeholder="e.g., Photosynthesis, World War II, Algebra")
|
| 284 |
+
subject = gr.Dropdown(
|
| 285 |
+
choices=["Science", "Mathematics", "History", "English Language Arts", "Geography", "Art"],
|
| 286 |
+
label="📚 Subject",
|
| 287 |
+
value="Science",
|
| 288 |
+
interactive=True
|
| 289 |
+
)
|
| 290 |
+
grade_level = gr.Dropdown(
|
| 291 |
+
choices=["K-2", "3-5", "6-8", "9-12"],
|
| 292 |
+
label="🎓 Grade Level",
|
| 293 |
+
value="6-8",
|
| 294 |
+
interactive=True
|
| 295 |
+
)
|
| 296 |
+
duration = gr.Slider(
|
| 297 |
+
minimum=30,
|
| 298 |
+
maximum=180,
|
| 299 |
+
step=15,
|
| 300 |
+
value=60,
|
| 301 |
+
label="⏱️ Duration (minutes)"
|
| 302 |
+
)
|
| 303 |
+
difficulty = gr.Dropdown(
|
| 304 |
+
choices=["Beginner", "Intermediate", "Advanced"],
|
| 305 |
+
label="🎯 Difficulty",
|
| 306 |
+
value="Intermediate",
|
| 307 |
+
interactive=True
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
generate_lesson_btn = gr.Button("🚀 Generate Lesson Plan", variant="primary")
|
| 311 |
+
|
| 312 |
+
gr.HTML("</div>")
|
| 313 |
+
|
| 314 |
+
with gr.Column():
|
| 315 |
+
gr.HTML("""
|
| 316 |
+
<div class="modern-card slide-in-right">
|
| 317 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">📋 Generated Lesson Plan</h3>
|
| 318 |
+
""")
|
| 319 |
+
|
| 320 |
+
progress_display = gr.HTML("", visible=False)
|
| 321 |
+
lesson_plan_output = gr.Markdown(label="")
|
| 322 |
+
|
| 323 |
+
gr.HTML("</div>")
|
| 324 |
+
|
| 325 |
+
# Event handler
|
| 326 |
+
generate_lesson_btn.click(
|
| 327 |
+
fn=generate_lesson_plan_with_progress,
|
| 328 |
+
inputs=[topic, subject, grade_level, duration, difficulty],
|
| 329 |
+
outputs=[progress_display, lesson_plan_output]
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def create_quiz_tab():
|
| 334 |
+
"""Create the quiz generator interface"""
|
| 335 |
+
|
| 336 |
+
with gr.Row():
|
| 337 |
+
with gr.Column():
|
| 338 |
+
gr.HTML("""
|
| 339 |
+
<div class="modern-card slide-in-left">
|
| 340 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">📝 Quiz Generator</h2>
|
| 341 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">Create engaging quizzes with AI assistance</p>
|
| 342 |
+
""")
|
| 343 |
+
|
| 344 |
+
quiz_topic = gr.Textbox(label="📝 Topic", placeholder="e.g., Photosynthesis, World War II, Algebra")
|
| 345 |
+
quiz_subject = gr.Dropdown(
|
| 346 |
+
choices=["Science", "Mathematics", "History", "English Language Arts", "Geography", "Art"],
|
| 347 |
+
label="📚 Subject",
|
| 348 |
+
value="Science",
|
| 349 |
+
interactive=True
|
| 350 |
+
)
|
| 351 |
+
quiz_grade_level = gr.Dropdown(
|
| 352 |
+
choices=["K-2", "3-5", "6-8", "9-12"],
|
| 353 |
+
label="🎓 Grade Level",
|
| 354 |
+
value="6-8",
|
| 355 |
+
interactive=True
|
| 356 |
+
)
|
| 357 |
+
question_count = gr.Slider(
|
| 358 |
+
minimum=3,
|
| 359 |
+
maximum=20,
|
| 360 |
+
step=1,
|
| 361 |
+
value=5,
|
| 362 |
+
label="❓ Number of Questions"
|
| 363 |
+
)
|
| 364 |
+
question_types = gr.CheckboxGroup(
|
| 365 |
+
choices=["Multiple Choice", "True/False", "Short Answer", "Fill in the Blank"],
|
| 366 |
+
label="📋 Question Types",
|
| 367 |
+
value=["Multiple Choice", "True/False"]
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
generate_quiz_btn = gr.Button("🚀 Generate Quiz", variant="primary")
|
| 371 |
+
|
| 372 |
+
gr.HTML("</div>")
|
| 373 |
+
|
| 374 |
+
with gr.Column():
|
| 375 |
+
gr.HTML("""
|
| 376 |
+
<div class="modern-card slide-in-right">
|
| 377 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">📝 Generated Quiz</h3>
|
| 378 |
+
""")
|
| 379 |
+
|
| 380 |
+
quiz_progress_display = gr.HTML("", visible=False)
|
| 381 |
+
quiz_output = gr.Markdown(label="")
|
| 382 |
+
|
| 383 |
+
gr.HTML("</div>")
|
| 384 |
+
|
| 385 |
+
# Event handler
|
| 386 |
+
generate_quiz_btn.click(
|
| 387 |
+
fn=generate_quiz_with_progress,
|
| 388 |
+
inputs=[quiz_topic, quiz_subject, quiz_grade_level, question_count, question_types],
|
| 389 |
+
outputs=[quiz_progress_display, quiz_output]
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def create_content_tab():
|
| 394 |
+
"""Create the content generator interface"""
|
| 395 |
+
|
| 396 |
+
with gr.Row():
|
| 397 |
+
with gr.Column():
|
| 398 |
+
gr.HTML("""
|
| 399 |
+
<div class="modern-card slide-in-left">
|
| 400 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">📄 Content Generator</h2>
|
| 401 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">Generate educational content with AI assistance</p>
|
| 402 |
+
""")
|
| 403 |
+
|
| 404 |
+
content_topic = gr.Textbox(label="📝 Topic", placeholder="e.g., Photosynthesis, World War II, Algebra")
|
| 405 |
+
content_subject = gr.Dropdown(
|
| 406 |
+
choices=["Science", "Mathematics", "History", "English Language Arts", "Geography", "Art"],
|
| 407 |
+
label="📚 Subject",
|
| 408 |
+
value="Science",
|
| 409 |
+
interactive=True
|
| 410 |
+
)
|
| 411 |
+
content_grade_level = gr.Dropdown(
|
| 412 |
+
choices=["K-2", "3-5", "6-8", "9-12"],
|
| 413 |
+
label="🎓 Grade Level",
|
| 414 |
+
value="6-8",
|
| 415 |
+
interactive=True
|
| 416 |
+
)
|
| 417 |
+
content_type = gr.Dropdown(
|
| 418 |
+
choices=["Study Guide", "Worksheet", "Reading Material", "Summary", "Notes"],
|
| 419 |
+
label="📄 Content Type",
|
| 420 |
+
value="Study Guide",
|
| 421 |
+
interactive=True
|
| 422 |
+
)
|
| 423 |
+
content_length = gr.Dropdown(
|
| 424 |
+
choices=["Short (1-2 pages)", "Medium (3-5 pages)", "Long (6+ pages)"],
|
| 425 |
+
label="📏 Length",
|
| 426 |
+
value="Medium (3-5 pages)",
|
| 427 |
+
interactive=True
|
| 428 |
+
)
|
| 429 |
+
content_difficulty = gr.Dropdown(
|
| 430 |
+
choices=["Beginner", "Intermediate", "Advanced"],
|
| 431 |
+
label="🎯 Difficulty",
|
| 432 |
+
value="Intermediate",
|
| 433 |
+
interactive=True
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
generate_content_btn = gr.Button("🚀 Generate Content", variant="primary")
|
| 437 |
+
|
| 438 |
+
gr.HTML("</div>")
|
| 439 |
+
|
| 440 |
+
with gr.Column():
|
| 441 |
+
gr.HTML("""
|
| 442 |
+
<div class="modern-card slide-in-right">
|
| 443 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">📄 Generated Content</h3>
|
| 444 |
+
""")
|
| 445 |
+
|
| 446 |
+
content_progress_display = gr.HTML("", visible=False)
|
| 447 |
+
content_output = gr.Markdown(label="")
|
| 448 |
+
|
| 449 |
+
gr.HTML("</div>")
|
| 450 |
+
|
| 451 |
+
# Event handler
|
| 452 |
+
generate_content_btn.click(
|
| 453 |
+
fn=generate_content_with_progress,
|
| 454 |
+
inputs=[content_topic, content_subject, content_grade_level, content_difficulty, content_type, content_length],
|
| 455 |
+
outputs=[content_progress_display, content_output]
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def create_feedback_tab():
|
| 460 |
+
"""Create the feedback interface"""
|
| 461 |
+
|
| 462 |
+
with gr.Row():
|
| 463 |
+
with gr.Column():
|
| 464 |
+
gr.HTML("""
|
| 465 |
+
<div class="modern-card slide-in-left">
|
| 466 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">💬 Feedback</h2>
|
| 467 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">Help us improve BrightMind AI</p>
|
| 468 |
+
""")
|
| 469 |
+
|
| 470 |
+
feedback_type = gr.CheckboxGroup(
|
| 471 |
+
choices=["Lesson Plan Generator", "Quiz Generator", "Content Generator", "Albert Chat", "Overall Experience"],
|
| 472 |
+
label="📋 What would you like to provide feedback on?",
|
| 473 |
+
value=["Overall Experience"]
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
rating = gr.Radio(
|
| 477 |
+
choices=[1, 2, 3, 4, 5],
|
| 478 |
+
label="⭐ Rating (1-5 stars)",
|
| 479 |
+
value=5
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
comments = gr.Textbox(
|
| 483 |
+
label="💭 Comments",
|
| 484 |
+
placeholder="Please share your thoughts, suggestions, or any issues you encountered...",
|
| 485 |
+
lines=4
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
user_email = gr.Textbox(
|
| 489 |
+
label="📧 Email (optional)",
|
| 490 |
+
placeholder="your.email@example.com"
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
submit_feedback_btn = gr.Button("📤 Submit Feedback", variant="primary")
|
| 494 |
+
|
| 495 |
+
gr.HTML("</div>")
|
| 496 |
+
|
| 497 |
+
with gr.Column():
|
| 498 |
+
gr.HTML("""
|
| 499 |
+
<div class="modern-card slide-in-right">
|
| 500 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">📊 Feedback Status</h3>
|
| 501 |
+
""")
|
| 502 |
+
|
| 503 |
+
feedback_status = gr.Markdown("Your feedback helps us improve! Please fill out the form and click submit.")
|
| 504 |
+
|
| 505 |
+
gr.HTML("</div>")
|
| 506 |
+
|
| 507 |
+
# Event handler
|
| 508 |
+
submit_feedback_btn.click(
|
| 509 |
+
fn=submit_feedback,
|
| 510 |
+
inputs=[feedback_type, rating, comments, user_email],
|
| 511 |
+
outputs=[feedback_status]
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def create_admin_tab():
|
| 516 |
+
"""Create the admin interface"""
|
| 517 |
+
|
| 518 |
+
with gr.Row():
|
| 519 |
+
with gr.Column():
|
| 520 |
+
gr.HTML("""
|
| 521 |
+
<div class="modern-card slide-in-left">
|
| 522 |
+
<h2 style="color: white; margin-bottom: 20px; font-size: 1.8rem;">⚙️ Admin Panel</h2>
|
| 523 |
+
<p style="color: white; font-size: 1.1rem; margin-bottom: 20px;">View feedback statistics and system information</p>
|
| 524 |
+
""")
|
| 525 |
+
|
| 526 |
+
refresh_stats_btn = gr.Button("🔄 Refresh Statistics", variant="primary")
|
| 527 |
+
view_feedback_btn = gr.Button("📊 View All Feedback", variant="secondary")
|
| 528 |
+
|
| 529 |
+
gr.HTML("</div>")
|
| 530 |
+
|
| 531 |
+
with gr.Column():
|
| 532 |
+
gr.HTML("""
|
| 533 |
+
<div class="modern-card slide-in-right">
|
| 534 |
+
<h3 style="color: white; margin-bottom: 20px; font-size: 1.4rem;">📈 Statistics</h3>
|
| 535 |
+
""")
|
| 536 |
+
|
| 537 |
+
stats_display = gr.Markdown("Click 'Refresh Statistics' to view feedback data.")
|
| 538 |
+
feedback_display = gr.Markdown("Click 'View All Feedback' to see detailed feedback entries.")
|
| 539 |
+
|
| 540 |
+
gr.HTML("</div>")
|
| 541 |
+
|
| 542 |
+
# Event handlers
|
| 543 |
+
def refresh_stats():
|
| 544 |
+
stats = get_feedback_stats()
|
| 545 |
+
return f"""
|
| 546 |
+
## 📊 Feedback Statistics
|
| 547 |
+
|
| 548 |
+
**Total Feedback Entries:** {stats['total_feedback']}
|
| 549 |
+
**Average Rating:** {stats['avg_rating']}/5 ⭐
|
| 550 |
+
|
| 551 |
+
**Recent Feedback:**
|
| 552 |
+
{format_recent_feedback(stats['recent_feedback'])}
|
| 553 |
+
"""
|
| 554 |
+
|
| 555 |
+
def view_all_feedback():
|
| 556 |
+
return view_feedback_admin()
|
| 557 |
+
|
| 558 |
+
def format_recent_feedback(recent_feedback):
|
| 559 |
+
if not recent_feedback:
|
| 560 |
+
return "No recent feedback available."
|
| 561 |
+
|
| 562 |
+
formatted = ""
|
| 563 |
+
for entry in recent_feedback:
|
| 564 |
+
timestamp, feedback_type, rating, comments, user_email = entry
|
| 565 |
+
formatted += f"- **{timestamp}**: {rating}/5 ⭐ - {comments[:100]}{'...' if len(comments) > 100 else ''}\n"
|
| 566 |
+
|
| 567 |
+
return formatted
|
| 568 |
+
|
| 569 |
+
refresh_stats_btn.click(
|
| 570 |
+
fn=refresh_stats,
|
| 571 |
+
outputs=[stats_display]
|
| 572 |
+
)
|
| 573 |
+
|
| 574 |
+
view_feedback_btn.click(
|
| 575 |
+
fn=view_all_feedback,
|
| 576 |
+
outputs=[feedback_display]
|
| 577 |
+
)
|
utils.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Utility functions module for BrightMind AI
|
| 3 |
+
Contains helper functions for content processing, math conversion, and styling
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import re
|
| 7 |
+
from algebra_solver import AlgebraSolver
|
| 8 |
+
|
| 9 |
+
# Initialize algebra solver
|
| 10 |
+
algebra_solver = AlgebraSolver()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def convert_math_to_html(text):
|
| 14 |
+
"""Convert LaTeX-style math expressions to HTML"""
|
| 15 |
+
|
| 16 |
+
# Handle LaTeX math delimiters first
|
| 17 |
+
# Convert \[ ... \] to display math
|
| 18 |
+
text = re.sub(r'\\\[([^\]]+)\\\]', r'<div style="text-align: center; margin: 10px 0; padding: 10px; background: #f8f9fa; border-radius: 5px; font-family: \'Times New Roman\', serif; font-size: 1.1em;">\1</div>', text)
|
| 19 |
+
|
| 20 |
+
# Convert \( ... \) to inline math
|
| 21 |
+
text = re.sub(r'\\\(([^)]+)\\\)', r'<span style="font-family: \'Times New Roman\', serif; font-size: 1.05em; background: #f0f0f0; padding: 2px 4px; border-radius: 3px;">\1</span>', text)
|
| 22 |
+
|
| 23 |
+
# Handle fractions: \frac{a}{b}
|
| 24 |
+
text = re.sub(r'\\frac\{([^}]+)\}\{([^}]+)\}', r'<div style="display: inline-block; text-align: center; vertical-align: middle; margin: 0 5px;"><div style="border-bottom: 1px solid #333; padding-bottom: 2px; font-size: 0.9em;">\1</div><div style="padding-top: 2px; font-size: 0.9em;">\2</div></div>', text)
|
| 25 |
+
|
| 26 |
+
# Handle simple fractions like 120/2
|
| 27 |
+
text = re.sub(r'(\d+)/(\d+)', r'<div style="display: inline-block; text-align: center; vertical-align: middle; margin: 0 3px;"><div style="border-bottom: 1px solid #333; padding-bottom: 1px; font-size: 0.9em;">\1</div><div style="padding-top: 1px; font-size: 0.9em;">\2</div></div>', text)
|
| 28 |
+
|
| 29 |
+
# Handle superscripts: ^{text}
|
| 30 |
+
text = re.sub(r'\^\{([^}]+)\}', r'<sup>\1</sup>', text)
|
| 31 |
+
text = re.sub(r'\^(\w)', r'<sup>\1</sup>', text)
|
| 32 |
+
|
| 33 |
+
# Handle subscripts: _{text}
|
| 34 |
+
text = re.sub(r'_\{([^}]+)\}', r'<sub>\1</sub>', text)
|
| 35 |
+
text = re.sub(r'_(\w)', r'<sub>\1</sub>', text)
|
| 36 |
+
|
| 37 |
+
# Handle \text{} commands first (before other processing)
|
| 38 |
+
text = re.sub(r'\\text\{([^}]+)\}', r'\1', text)
|
| 39 |
+
|
| 40 |
+
# Handle \boxed{} commands
|
| 41 |
+
text = re.sub(r'\\boxed\{([^}]+)\}', r'<span style="border: 2px solid #333; padding: 2px 4px; background: #f0f0f0; border-radius: 3px;">\1</span>', text)
|
| 42 |
+
|
| 43 |
+
# Handle underscore patterns that are not subscripts (like \_\_\_)
|
| 44 |
+
text = re.sub(r'\\_+', r'<span style="border-bottom: 2px solid #333; min-width: 20px; display: inline-block;"> </span>', text)
|
| 45 |
+
|
| 46 |
+
# Re-process fractions after text commands are handled
|
| 47 |
+
text = re.sub(r'\\frac\{([^}]+)\}\{([^}]+)\}', r'<div style="display: inline-block; text-align: center; vertical-align: middle; margin: 0 5px;"><div style="border-bottom: 1px solid #333; padding-bottom: 2px; font-size: 0.9em;">\1</div><div style="padding-top: 2px; font-size: 0.9em;">\2</div></div>', text)
|
| 48 |
+
|
| 49 |
+
# Handle common math symbols
|
| 50 |
+
text = text.replace('\\times', '×')
|
| 51 |
+
text = text.replace('\\rightarrow', '→')
|
| 52 |
+
text = text.replace('\\leftarrow', '←')
|
| 53 |
+
text = text.replace('\\leq', '≤')
|
| 54 |
+
text = text.replace('\\geq', '≥')
|
| 55 |
+
text = text.replace('\\neq', '≠')
|
| 56 |
+
text = text.replace('\\approx', '≈')
|
| 57 |
+
text = text.replace('\\pm', '±')
|
| 58 |
+
text = text.replace('\\sqrt', '√')
|
| 59 |
+
text = text.replace('\\pi', 'π')
|
| 60 |
+
|
| 61 |
+
return text
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def solve_algebra_problem(problem_type, *args):
|
| 65 |
+
"""Solve algebra problems interactively"""
|
| 66 |
+
try:
|
| 67 |
+
if problem_type == "speed":
|
| 68 |
+
distance, time = args
|
| 69 |
+
result = algebra_solver.solve_speed_problem(float(distance), float(time))
|
| 70 |
+
elif problem_type == "volume":
|
| 71 |
+
mass, density = args
|
| 72 |
+
result = algebra_solver.solve_volume_problem(float(mass), float(density))
|
| 73 |
+
elif problem_type == "linear":
|
| 74 |
+
a, b, c = args
|
| 75 |
+
result = algebra_solver.solve_linear_equation(float(a), float(b), float(c))
|
| 76 |
+
elif problem_type == "quadratic":
|
| 77 |
+
h, coefficient = args
|
| 78 |
+
result = algebra_solver.solve_quadratic_equation(float(h), float(coefficient))
|
| 79 |
+
else:
|
| 80 |
+
return "❌ Unknown problem type"
|
| 81 |
+
|
| 82 |
+
if "error" in result:
|
| 83 |
+
return f"❌ Error: {result['error']}"
|
| 84 |
+
|
| 85 |
+
# Format the solution nicely
|
| 86 |
+
solution_text = f"✅ **{result['solution']}**\n\n"
|
| 87 |
+
if "steps" in result:
|
| 88 |
+
solution_text += "**Steps:**\n"
|
| 89 |
+
for i, step in enumerate(result["steps"], 1):
|
| 90 |
+
solution_text += f"{i}. {step}\n"
|
| 91 |
+
elif "formula" in result:
|
| 92 |
+
solution_text += f"**Formula:** {result['formula']}\n"
|
| 93 |
+
solution_text += f"**Calculation:** {result['calculation']}\n"
|
| 94 |
+
|
| 95 |
+
return solution_text
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
return f"❌ Error solving problem: {str(e)}"
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def apply_content_styling(content, content_type):
|
| 102 |
+
"""Apply simple and robust styling to educational content"""
|
| 103 |
+
|
| 104 |
+
# Clean up any malformed HTML first
|
| 105 |
+
content = re.sub(r'="[^"]*style="[^"]*"[^"]*"', '', content)
|
| 106 |
+
content = re.sub(r'="[^"]*color: [^"]*"[^"]*"', '', content)
|
| 107 |
+
content = re.sub(r'="[^"]*background: [^"]*"[^"]*"', '', content)
|
| 108 |
+
content = re.sub(r'="[^"]*border: [^"]*"[^"]*"', '', content)
|
| 109 |
+
content = re.sub(r'="[^"]*padding: [^"]*"[^"]*"', '', content)
|
| 110 |
+
content = re.sub(r'="[^"]*margin: [^"]*"[^"]*"', '', content)
|
| 111 |
+
|
| 112 |
+
# Fix broken image tags and malformed HTML
|
| 113 |
+
content = re.sub(r'<div style>.*?</div>', '', content)
|
| 114 |
+
content = re.sub(r'stylelazy">', 'loading="lazy">', content)
|
| 115 |
+
content = re.sub(r'<img[^>]*style[^>]*>', '', content)
|
| 116 |
+
|
| 117 |
+
# Fix malformed button attributes
|
| 118 |
+
content = re.sub(r'styleselectRating\(\d+\)"', '', content)
|
| 119 |
+
content = re.sub(r'styletext"', '', content)
|
| 120 |
+
content = re.sub(r'style="[^"]*"[^"]*style="[^"]*"', 'style="margin-right: 8px; transform: scale(1.2);"', content)
|
| 121 |
+
|
| 122 |
+
# Fix broken input attributes
|
| 123 |
+
content = re.sub(r'<input typeready-check"', '<input type="checkbox" id="ready-check"', content)
|
| 124 |
+
content = re.sub(r'styleSolve"', 'onclick="solveSpeed()"', content)
|
| 125 |
+
|
| 126 |
+
# Clean up any remaining malformed attributes
|
| 127 |
+
content = re.sub(r'="[^"]*"[^"]*"', '', content)
|
| 128 |
+
|
| 129 |
+
# Return content as-is for Markdown rendering
|
| 130 |
+
styled_content = content
|
| 131 |
+
|
| 132 |
+
# Apply Markdown-friendly styling
|
| 133 |
+
styled_content = re.sub(r'\[ANSWER: ([^\]]+)\]', r'**Answer:** \1', styled_content)
|
| 134 |
+
|
| 135 |
+
styled_content = re.sub(r'___+', r'_________________', styled_content)
|
| 136 |
+
|
| 137 |
+
styled_content = re.sub(r'\[ \]', r'☐', styled_content)
|
| 138 |
+
styled_content = re.sub(r'☑', r'☑', styled_content)
|
| 139 |
+
|
| 140 |
+
styled_content = re.sub(r'\[(\d+)\]', r'**\1**', styled_content)
|
| 141 |
+
|
| 142 |
+
return styled_content
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def process_content_with_images(content, topic, content_type):
|
| 146 |
+
"""Process generated content to include real images and multimedia elements"""
|
| 147 |
+
from ai_services import get_educational_image_url, generate_image_placeholder
|
| 148 |
+
|
| 149 |
+
# Replace image placeholders with actual images
|
| 150 |
+
# Convert LaTeX-style math expressions to HTML first
|
| 151 |
+
content = convert_math_to_html(content)
|
| 152 |
+
|
| 153 |
+
# Fix LaTeX math rendering by converting to HTML
|
| 154 |
+
def fix_latex_math(match):
|
| 155 |
+
latex_formula = match.group(1)
|
| 156 |
+
|
| 157 |
+
# Handle fractions first (most complex) - improved regex
|
| 158 |
+
def process_fraction(match):
|
| 159 |
+
numerator = match.group(1)
|
| 160 |
+
denominator = match.group(2)
|
| 161 |
+
return f'<div style="display: inline-block; text-align: center; vertical-align: middle; margin: 0 5px;"><div style="border-bottom: 1px solid #333; padding-bottom: 2px; font-size: 0.9em;">{numerator}</div><div style="padding-top: 2px; font-size: 0.9em;">{denominator}</div></div>'
|
| 162 |
+
|
| 163 |
+
# Process fractions: \frac{numerator}{denominator} - more robust regex
|
| 164 |
+
latex_formula = re.sub(r'\\frac\{([^{}]+)\}\{([^{}]+)\}', process_fraction, latex_formula)
|
| 165 |
+
|
| 166 |
+
# Handle text commands more carefully
|
| 167 |
+
latex_formula = latex_formula.replace('\\text{', '<span style="font-style: normal;">')
|
| 168 |
+
|
| 169 |
+
# Handle math symbols
|
| 170 |
+
latex_formula = latex_formula.replace('\\times', '×')
|
| 171 |
+
latex_formula = latex_formula.replace('\\rightarrow', '→')
|
| 172 |
+
latex_formula = latex_formula.replace('\\leftarrow', '←')
|
| 173 |
+
latex_formula = latex_formula.replace('\\leq', '≤')
|
| 174 |
+
latex_formula = latex_formula.replace('\\geq', '≥')
|
| 175 |
+
latex_formula = latex_formula.replace('\\neq', '≠')
|
| 176 |
+
latex_formula = latex_formula.replace('\\approx', '≈')
|
| 177 |
+
latex_formula = latex_formula.replace('\\pm', '±')
|
| 178 |
+
latex_formula = latex_formula.replace('\\sqrt{', '√(')
|
| 179 |
+
latex_formula = latex_formula.replace('\\pi', 'π')
|
| 180 |
+
latex_formula = latex_formula.replace('\\alpha', 'α')
|
| 181 |
+
latex_formula = latex_formula.replace('\\beta', 'β')
|
| 182 |
+
latex_formula = latex_formula.replace('\\gamma', 'γ')
|
| 183 |
+
latex_formula = latex_formula.replace('\\delta', 'δ')
|
| 184 |
+
latex_formula = latex_formula.replace('\\theta', 'θ')
|
| 185 |
+
latex_formula = latex_formula.replace('\\lambda', 'λ')
|
| 186 |
+
latex_formula = latex_formula.replace('\\mu', 'μ')
|
| 187 |
+
latex_formula = latex_formula.replace('\\sigma', 'σ')
|
| 188 |
+
latex_formula = latex_formula.replace('\\tau', 'τ')
|
| 189 |
+
latex_formula = latex_formula.replace('\\phi', 'φ')
|
| 190 |
+
latex_formula = latex_formula.replace('\\omega', 'ω')
|
| 191 |
+
|
| 192 |
+
# Handle superscripts and subscripts
|
| 193 |
+
latex_formula = re.sub(r'\^(\w+)', r'<sup>\1</sup>', latex_formula)
|
| 194 |
+
latex_formula = re.sub(r'_(\w+)', r'<sub>\1</sub>', latex_formula)
|
| 195 |
+
|
| 196 |
+
# Clean up any remaining unmatched braces
|
| 197 |
+
latex_formula = latex_formula.replace('{', '').replace('}', '')
|
| 198 |
+
|
| 199 |
+
return f'<div style="text-align: center; margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #667eea; font-family: \'Times New Roman\', serif; font-size: 1.1em;">{latex_formula}</div>'
|
| 200 |
+
|
| 201 |
+
# Find LaTeX math formulas and convert them (both [formula] and (formula) formats)
|
| 202 |
+
# Updated patterns to be more specific to LaTeX commands
|
| 203 |
+
latex_patterns = [
|
| 204 |
+
re.compile(r'\[([^\[\]]*\\[a-zA-Z]+[^\[\]]*)\]', re.MULTILINE), # [formula with LaTeX commands]
|
| 205 |
+
re.compile(r'\(([^()]*\\[a-zA-Z]+[^()]*)\)', re.MULTILINE) # (formula with LaTeX commands)
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
for pattern in latex_patterns:
|
| 209 |
+
content = pattern.sub(fix_latex_math, content)
|
| 210 |
+
|
| 211 |
+
# Find all IMAGE: description patterns (both [IMAGE: description] and IMAGE: description)
|
| 212 |
+
image_patterns = [
|
| 213 |
+
re.compile(r'\[IMAGE:\s*([^\]]+)\]', re.IGNORECASE),
|
| 214 |
+
re.compile(r'IMAGE:\s*([^\n]+)', re.IGNORECASE)
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
for pattern in image_patterns:
|
| 218 |
+
def replace_image(match):
|
| 219 |
+
description = match.group(1).strip()
|
| 220 |
+
print(f"🖼️ Processing image: {description}")
|
| 221 |
+
|
| 222 |
+
# Try to get a real image URL
|
| 223 |
+
image_url = get_educational_image_url(topic, description, content_type)
|
| 224 |
+
|
| 225 |
+
if image_url and not image_url.startswith('https://via.placeholder.com'):
|
| 226 |
+
# Real image found
|
| 227 |
+
return f'\n<img src="{image_url}" alt="{description}" style="max-width: 100%; height: auto; border-radius: 8px; margin: 20px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">\n'
|
| 228 |
+
else:
|
| 229 |
+
# Use placeholder
|
| 230 |
+
return generate_image_placeholder(description, topic, content_type)
|
| 231 |
+
|
| 232 |
+
content = pattern.sub(replace_image, content)
|
| 233 |
+
|
| 234 |
+
return content
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def typewriter_effect(text, status_message):
|
| 238 |
+
"""Create a typewriter effect for text display"""
|
| 239 |
+
import time
|
| 240 |
+
|
| 241 |
+
if not text:
|
| 242 |
+
return status_message
|
| 243 |
+
|
| 244 |
+
# Split text into words for smoother effect
|
| 245 |
+
words = text.split()
|
| 246 |
+
result = ""
|
| 247 |
+
|
| 248 |
+
for i, word in enumerate(words):
|
| 249 |
+
result += word + " "
|
| 250 |
+
if i % 10 == 0: # Yield every 10 words
|
| 251 |
+
yield result, status_message
|
| 252 |
+
time.sleep(0.05) # Small delay for effect
|
| 253 |
+
|
| 254 |
+
yield result, status_message
|