helpme / app.py
mikaelJ46's picture
Update app.py
b305764 verified
# ============================================================================
# PROFESSIONAL IGCSE TEACHER PLATFORM
# Multi-Subject Question Bank & Teaching Resource Generator
# ============================================================================
import warnings
warnings.filterwarnings('ignore')
import os
import json
import time
import re
from datetime import datetime, timedelta
from collections import defaultdict
from io import BytesIO
import base64
# PDF Processing
import fitz
import PyPDF2
import pdfplumber
# AI Models
import google.generativeai as genai
import cohere
from huggingface_hub import InferenceClient
# Gradio
import gradio as gr
# Document Generation
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
print("πŸŽ“ Initializing Professional Teacher Platform...")
# ============================================================================
# INITIALIZE ALL AI MODELS WITH FALLBACK CHAIN
# ============================================================================
# Gemini (Primary)
try:
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
gemini_model = genai.GenerativeModel('gemini-2.5-pro')
print("βœ“ Gemini AI initialized (PRIMARY)")
except Exception as e:
print(f"βœ— Gemini initialization failed: {e}")
gemini_model = None
# Cohere (Secondary)
try:
cohere_client = cohere.Client(os.getenv("COHERE_API_KEY"))
print("βœ“ Cohere initialized (SECONDARY)")
except Exception as e:
print(f"βœ— Cohere initialization failed: {e}")
cohere_client = None
# Z.ai (Tertiary)
try:
zai_client = InferenceClient(provider="novita", api_key=os.environ.get("HF_TOKEN"))
print("βœ“ Z.ai GLM-4.6 initialized (TERTIARY)")
except Exception as e:
print(f"βœ— Z.ai initialization failed: {e}")
zai_client = None
# MiniMax (Final Fallback)
try:
minimax_client = InferenceClient(provider="novita", api_key=os.environ.get("HF_TOKEN"))
print("βœ“ MiniMax initialized (FINAL FALLBACK)")
except Exception as e:
print(f"βœ— MiniMax initialization failed: {e}")
minimax_client = None
# ============================================================================
# UNIFIED AI FUNCTION WITH SMART FALLBACK
# ============================================================================
def ask_ai(prompt, temperature=0.7, max_retries=2):
"""Try models in order: Gemini β†’ Cohere β†’ Z.ai β†’ MiniMax"""
last_error = None
# Try Gemini first (Primary)
if gemini_model:
for attempt in range(max_retries):
try:
response = gemini_model.generate_content(
prompt,
generation_config=genai.types.GenerationConfig(temperature=temperature)
)
return response.text, "gemini"
except Exception as e:
last_error = e
if attempt < max_retries - 1:
time.sleep(1)
# Try Cohere (Secondary)
if cohere_client:
for attempt in range(max_retries):
try:
response = cohere_client.chat(
model="command-r-plus-08-2024",
message=prompt,
temperature=temperature
)
return response.text, "cohere"
except Exception as e:
last_error = e
if attempt < max_retries - 1:
time.sleep(1)
# Try Z.ai (Tertiary)
if zai_client:
for attempt in range(max_retries):
try:
completion = zai_client.chat.completions.create(
model="zai-org/GLM-4.6",
messages=[{"role": "user", "content": prompt}],
temperature=temperature
)
return completion.choices[0].message.content, "zai"
except Exception as e:
last_error = e
if attempt < max_retries - 1:
time.sleep(1)
# Try MiniMax (Final Fallback)
if minimax_client:
try:
completion = minimax_client.chat.completions.create(
model="MiniMaxAI/MiniMax-M2",
messages=[{"role": "user", "content": prompt}],
temperature=temperature
)
return completion.choices[0].message.content, "minimax"
except Exception as e:
last_error = e
# All failed
return f"Error: All AI services failed. Last error: {str(last_error)}", "error"
# ============================================================================
# IGCSE SUBJECT SYLLABUSES AND TOPICS
# ============================================================================
IGCSE_SUBJECTS = {
"Mathematics (0580)": {
"topics": [
"Number", "Algebra and Graphs", "Coordinate Geometry", "Geometry",
"Mensuration", "Trigonometry", "Vectors and Transformations",
"Probability", "Statistics"
],
"subtopics": {
"Number": ["Types of numbers", "Operations", "Powers and roots", "Fractions and decimals", "Percentages", "Ratio and proportion", "Standard form"],
"Algebra and Graphs": ["Equations", "Inequalities", "Sequences", "Functions", "Quadratic equations", "Simultaneous equations"],
"Trigonometry": ["Sine, cosine, tangent", "Pythagoras theorem", "Sine and cosine rules", "3D problems"]
}
},
"Physics (0625)": {
"topics": [
"General Physics", "Thermal Physics", "Properties of Waves",
"Electricity and Magnetism", "Atomic Physics"
],
"subtopics": {
"General Physics": ["Length and time", "Motion", "Mass and weight", "Density", "Forces", "Momentum", "Energy", "Pressure"],
"Thermal Physics": ["Temperature", "Thermal properties", "Transfer of thermal energy"],
"Electricity and Magnetism": ["Simple circuits", "Current", "Voltage", "Resistance", "Electrical energy", "Magnetism", "Electromagnetism"]
}
},
"Chemistry (0620)": {
"topics": [
"Particulate Nature of Matter", "Experimental Techniques", "Atoms, Elements and Compounds",
"Stoichiometry", "Electricity and Chemistry", "Chemical Energetics",
"Chemical Reactions", "Acids, Bases and Salts", "The Periodic Table",
"Metals", "Air and Water", "Organic Chemistry"
],
"subtopics": {
"Atoms, Elements and Compounds": ["Atomic structure", "Ions", "Ionic bonding", "Covalent bonding", "Metallic bonding"],
"Chemical Reactions": ["Physical and chemical changes", "Rate of reaction", "Reversible reactions", "Redox reactions"]
}
},
"Biology (0610)": {
"topics": [
"Characteristics of Living Organisms", "Organisation of the Organism",
"Movement in and out of Cells", "Biological Molecules", "Enzymes",
"Plant Nutrition", "Human Nutrition", "Transport in Plants",
"Transport in Humans", "Diseases and Immunity", "Gas Exchange",
"Respiration", "Excretion", "Coordination and Response",
"Drugs", "Reproduction", "Inheritance", "Selection and Evolution",
"Organisms and Environment", "Human Influences on Ecosystems"
],
"subtopics": {
"Organisation of the Organism": ["Cell structure", "Levels of organisation", "Size of specimens"],
"Transport in Humans": ["Circulatory system", "Heart", "Blood vessels", "Blood"]
}
},
"ICT (0417)": {
"topics": [
"Types of Computer Systems", "Hardware and Software", "Storage Devices",
"Networks", "The Effects of ICT", "ICT Applications", "Systems Life Cycle",
"Safety and Security", "Audience", "Communication", "File Management",
"Images", "Layout", "Styles", "Proofing", "Charts and Graphs",
"Databases", "Presentations", "Data Manipulation", "Spreadsheets",
"Website Authoring", "Programming"
],
"subtopics": {
"Hardware and Software": ["Input devices", "Output devices", "CPU", "Memory", "Operating systems"],
"Networks": ["LAN and WAN", "Network hardware", "Internet", "Security"]
}
},
"Physical Education (0413)": {
"topics": [
"Skeletal System", "Muscular System", "Cardiovascular System",
"Respiratory System", "Energy Systems", "Components of Fitness",
"Principles of Training", "Training Methods", "Nutrition",
"Skill Acquisition", "Psychology of Sport"
],
"subtopics": {
"Skeletal System": ["Types of bones", "Types of joints", "Joint movements"],
"Energy Systems": ["ATP-PC system", "Lactic acid system", "Aerobic system"]
}
}
}
# ============================================================================
# GLOBAL STORAGE
# ============================================================================
papers_storage = []
questions_index = []
pdf_content_storage = {}
# ============================================================================
# PDF PROCESSING FUNCTIONS
# ============================================================================
def extract_text_from_pdf(pdf_file):
"""Extract text from PDF using multiple methods"""
if pdf_file is None:
return ""
try:
# Try PyMuPDF first
doc = fitz.open(pdf_file.name)
text = ""
for page in doc:
text += page.get_text() + "\n"
doc.close()
if len(text.strip()) < 100:
# Fallback to PyPDF2
with open(pdf_file.name, 'rb') as f:
pdf_reader = PyPDF2.PdfReader(f)
text = ""
for page in pdf_reader.pages:
text += page.extract_text() + "\n"
return text
except Exception as e:
return f"Error extracting PDF: {e}"
def identify_subject_from_content(text, filename):
"""Identify subject from content and filename"""
text_sample = text[:2000].lower()
filename_lower = filename.lower()
# Check filename first
for subject_key in IGCSE_SUBJECTS.keys():
subject_code = subject_key.split("(")[1].split(")")[0]
subject_name = subject_key.split("(")[0].strip()
if subject_code in filename_lower or subject_name.lower() in filename_lower:
return subject_key
# Check content for subject indicators
subject_keywords = {
"Mathematics (0580)": ["equation", "graph", "algebra", "geometry", "trigonometry"],
"Physics (0625)": ["force", "energy", "motion", "circuit", "wave"],
"Chemistry (0620)": ["atom", "molecule", "reaction", "element", "compound"],
"Biology (0610)": ["cell", "organism", "enzyme", "photosynthesis", "respiration"],
"ICT (0417)": ["computer", "network", "software", "database", "programming"],
"Physical Education (0413)": ["muscle", "skeletal", "fitness", "training", "cardiovascular"]
}
for subject, keywords in subject_keywords.items():
count = sum(1 for kw in keywords if kw in text_sample)
if count >= 3:
return subject
return "Unknown Subject"
def extract_questions_from_pdf(pdf_text, paper_id, paper_title, subject):
"""Extract questions using AI with comprehensive analysis"""
if not pdf_text or len(pdf_text) < 100:
return []
# Split into chunks if too long
max_chunk = 6000
chunks = [pdf_text[i:i+max_chunk] for i in range(0, len(pdf_text), max_chunk)]
all_questions = []
for chunk_idx, chunk in enumerate(chunks):
prompt = f"""Extract ALL questions from this IGCSE {subject} exam paper.
Paper: {paper_title}
Text:
{chunk}
Return ONLY valid JSON array:
[
{{
"question_number": "1(a)",
"question_text": "Complete question text",
"marks": 2,
"topic": "Specific topic",
"command_words": ["explain", "describe"],
"difficulty": "medium"
}}
]
Include all questions, preserve exact numbering, identify topics clearly."""
try:
response, _ = ask_ai(prompt, temperature=0.1)
clean_txt = response.replace("```json", "").replace("```", "").strip()
# Extract JSON
json_match = re.search(r'\[.*\]', clean_txt, re.DOTALL)
if json_match:
questions = json.loads(json_match.group())
for q in questions:
q['paper_id'] = paper_id
q['paper_title'] = paper_title
q['subject'] = subject
q['chunk_index'] = chunk_idx
all_questions.extend(questions)
except Exception as e:
print(f"Error extracting questions from chunk {chunk_idx}: {e}")
continue
return all_questions
# ============================================================================
# LESSON NOTES GENERATOR
# ============================================================================
def generate_lesson_notes(subject, topic, detail_level="comprehensive"):
"""Generate comprehensive teaching notes - content focused"""
prompt = f"""Generate {detail_level} IGCSE {subject} teaching notes for: {topic}
Provide complete, ready-to-teach content:
1. KEY CONCEPTS & DEFINITIONS
- All essential terms with clear explanations
- Core principles and laws
2. DETAILED CONTENT
- Comprehensive explanation of all subtopics
- Step-by-step breakdowns where applicable
- Important formulas, equations, or processes
- Worked examples with solutions
3. IMPORTANT POINTS TO EMPHASIZE
- Critical information students must know
- Common misconceptions to address
- Exam-relevant points
4. PRACTICE PROBLEMS/QUESTIONS
- 5-8 questions with full solutions
- Range of difficulty levels
5. QUICK REFERENCE SUMMARY
- Bullet points of key takeaways
- Memory aids or mnemonics
Focus on CONTENT and KNOWLEDGE - provide the actual information to teach, not meta-instructions about teaching."""
response, source = ask_ai(prompt, temperature=0.5)
if source != "gemini":
response = f"[Generated using {source.title()}]\n\n{response}"
return response
def generate_notes_pdf(notes_content, subject, topic):
"""Generate PDF from lesson notes"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, topMargin=72, bottomMargin=72)
story = []
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=18,
textColor=colors.HexColor('#1a237e'),
spaceAfter=20,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
)
heading_style = ParagraphStyle(
'CustomHeading',
parent=styles['Heading2'],
fontSize=14,
textColor=colors.HexColor('#283593'),
spaceAfter=12,
spaceBefore=12,
fontName='Helvetica-Bold'
)
# Title
story.append(Paragraph(f"IGCSE {subject}", title_style))
story.append(Paragraph(f"Topic: {topic}", styles['Heading2']))
story.append(Spacer(1, 20))
# Content - parse markdown-style formatting
lines = notes_content.split('\n')
for line in lines:
line = line.strip()
if not line:
story.append(Spacer(1, 6))
continue
# Handle headings
if line.startswith('###'):
story.append(Paragraph(line.replace('###', '').strip(), heading_style))
elif line.startswith('##'):
story.append(Paragraph(line.replace('##', '').strip(), title_style))
elif line.startswith('#'):
story.append(Paragraph(line.replace('#', '').strip(), title_style))
# Handle bold
elif line.startswith('**') or '**' in line:
line = line.replace('**', '<b>').replace('**', '</b>')
story.append(Paragraph(line, styles['Normal']))
# Handle lists
elif line.startswith('-') or line.startswith('β€’'):
line = line.lstrip('-β€’').strip()
story.append(Paragraph(f" β€’ {line}", styles['Normal']))
# Regular text
else:
story.append(Paragraph(line, styles['Normal']))
doc.build(story)
buffer.seek(0)
return buffer
# ============================================================================
# ASSESSMENT GENERATOR
# ============================================================================
def generate_assessment_questions(subject, topics, num_questions=10, difficulty="mixed"):
"""Generate assessment questions"""
topics_str = ", ".join(topics) if isinstance(topics, list) else topics
prompt = f"""Create {num_questions} IGCSE {subject} assessment questions.
Topics: {topics_str}
Difficulty: {difficulty}
Return ONLY valid JSON:
[
{{
"question_number": 1,
"question_text": "Complete question",
"marks": 4,
"topic": "Topic name",
"difficulty": "medium",
"mark_scheme": "Point-by-point marking scheme",
"model_answer": "Detailed model answer"
}}
]
Mix question types (knowledge, application, analysis). Include mark schemes."""
response, _ = ask_ai(prompt, temperature=0.5)
try:
clean_txt = response.replace("```json", "").replace("```", "").strip()
json_match = re.search(r'\[.*\]', clean_txt, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return []
except:
return []
# ============================================================================
# EXAM PAPER GENERATOR - SEPARATE QUESTION AND MARK SCHEME PDFs
# ============================================================================
def generate_question_paper_pdf(selected_questions, exam_title):
"""Generate question paper PDF (no answers)"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, topMargin=72, bottomMargin=36)
story = []
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=16,
textColor=colors.HexColor('#1a237e'),
spaceAfter=30,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
)
question_style = ParagraphStyle(
'Question',
parent=styles['Normal'],
fontSize=11,
leading=14,
spaceAfter=12
)
# Title
story.append(Paragraph(exam_title, title_style))
story.append(Paragraph("QUESTION PAPER", styles['Heading2']))
story.append(Spacer(1, 12))
# Instructions
instructions = Paragraph(
"<b>Instructions to Candidates:</b><br/>"
"β€’ Answer all questions<br/>"
"β€’ Show all working clearly<br/>"
"β€’ Write your answers in the spaces provided<br/>"
f"β€’ Total marks: {sum(q.get('marks', 0) for q in selected_questions)}<br/>"
f"β€’ Time allowed: {len(selected_questions) * 2} minutes",
styles['Normal']
)
story.append(instructions)
story.append(Spacer(1, 24))
# Questions
for i, q in enumerate(selected_questions, 1):
q_text = f"<b>{i}. {q.get('question_text', '')}</b> [{q.get('marks', 0)} marks]"
story.append(Paragraph(q_text, question_style))
story.append(Spacer(1, 48)) # Space for answer
doc.build(story)
buffer.seek(0)
return buffer
def generate_marking_scheme_pdf(selected_questions, exam_title):
"""Generate marking scheme PDF (separate)"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, topMargin=72, bottomMargin=36)
story = []
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=16,
textColor=colors.HexColor('#1a237e'),
spaceAfter=30,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
)
# Title
story.append(Paragraph(exam_title, title_style))
story.append(Paragraph("MARKING SCHEME", styles['Heading2']))
story.append(Spacer(1, 24))
# Mark scheme
for i, q in enumerate(selected_questions, 1):
# Question number and marks
q_header = f"<b>Question {i}</b> [{q.get('marks', 0)} marks]"
story.append(Paragraph(q_header, styles['Heading3']))
story.append(Spacer(1, 6))
# Mark scheme
mark_scheme = q.get('mark_scheme', 'Mark scheme not available')
story.append(Paragraph(f"<b>Mark Scheme:</b>", styles['Normal']))
story.append(Paragraph(mark_scheme, styles['Normal']))
story.append(Spacer(1, 8))
# Model answer
model_answer = q.get('model_answer', 'Model answer not available')
story.append(Paragraph(f"<b>Model Answer:</b>", styles['Normal']))
story.append(Paragraph(model_answer, styles['Normal']))
story.append(Spacer(1, 20))
doc.build(story)
buffer.seek(0)
return buffer
# ============================================================================
# SEARCH FUNCTIONS
# ============================================================================
def search_questions(keyword, subject_filter=None):
"""Search questions by keyword"""
if not questions_index:
return "No questions uploaded yet. Please upload past papers first."
# Filter by subject if specified
questions = questions_index
if subject_filter and subject_filter != "All Subjects":
questions = [q for q in questions if q['subject'] == subject_filter]
# Search by keyword
keyword_lower = keyword.lower()
matching = [q for q in questions if (
keyword_lower in q.get('question_text', '').lower() or
keyword_lower in q.get('topic', '').lower()
)]
if not matching:
return f"No questions found matching '{keyword}'"
# Format results
result = f"### Found {len(matching)} question(s) matching '{keyword}'\n\n"
for q in matching[:20]: # Limit to 20 results
result += f"""**Question {q.get('question_number', 'N/A')}** | {q.get('marks', 0)} marks | {q.get('difficulty', 'medium').title()}
**Topic:** {q.get('topic', 'General')}
**Paper:** {q.get('paper_title', 'Unknown')}
{q.get('question_text', '')}
---
"""
return result
# ============================================================================
# GRADIO INTERFACE
# ============================================================================
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="indigo"),
title="IGCSE Teacher Platform",
css="""
.gradio-container {
font-family: 'Inter', sans-serif;
}
.tab-nav button {
font-weight: 500;
}
"""
) as app:
gr.Markdown("""
# IGCSE Professional Teacher Platform
### Comprehensive Teaching Resource & Question Bank System
*Powered by Advanced AI with Multiple Fallback Models*
""")
with gr.Tabs():
# ============================================================
# TAB 1: UPLOAD PAST PAPERS
# ============================================================
with gr.Tab("Upload Papers"):
gr.Markdown("### Upload Past Papers & Build Question Bank")
with gr.Row():
with gr.Column():
upload_pdf = gr.File(
label="Upload PDF Past Paper",
file_types=[".pdf"],
file_count="single"
)
upload_title = gr.Textbox(
label="Paper Title",
placeholder="e.g., Mathematics 0580 Paper 2 June 2023"
)
upload_subject = gr.Dropdown(
choices=list(IGCSE_SUBJECTS.keys()),
label="Subject",
value="Mathematics (0580)"
)
upload_btn = gr.Button("Upload & Extract Questions", variant="primary")
upload_status = gr.Textbox(label="Status", lines=5)
with gr.Column():
gr.Markdown("### Uploaded Papers")
papers_list = gr.Textbox(
label="Papers in Database",
lines=15,
value="No papers uploaded yet"
)
stats_display = gr.Textbox(
label="Statistics",
lines=3,
value="Papers: 0 | Questions: 0"
)
def upload_paper(pdf_file, title, subject):
if not pdf_file or not title:
return "Please provide both PDF and title", papers_list.value, stats_display.value
paper_id = len(papers_storage) + 1
# Extract text
status = "Extracting text from PDF..."
pdf_text = extract_text_from_pdf(pdf_file)
if pdf_text.startswith("Error"):
return pdf_text, papers_list.value, stats_display.value
# Auto-detect subject if not clearly specified
if subject == "Unknown Subject":
subject = identify_subject_from_content(pdf_text, pdf_file.name)
status += f"\n\nIdentified as: {subject}"
status += f"\nExtracting questions..."
# Extract questions
questions = extract_questions_from_pdf(pdf_text, paper_id, title, subject)
# Store paper
papers_storage.append({
"id": paper_id,
"title": title,
"subject": subject,
"uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"questions_count": len(questions)
})
pdf_content_storage[paper_id] = pdf_text
questions_index.extend(questions)
status += f"\n\nSUCCESS!"
status += f"\nExtracted {len(questions)} questions"
status += f"\nPaper added to database"
# Update papers list
papers_str = "\n\n".join([
f"{p['title']}\n{p['subject']}\n{p['uploaded_at']} | {p['questions_count']} questions"
for p in papers_storage
])
stats_str = f"Papers: {len(papers_storage)} | Questions: {len(questions_index)}"
return status, papers_str, stats_str
upload_btn.click(
upload_paper,
[upload_pdf, upload_title, upload_subject],
[upload_status, papers_list, stats_display]
)
# ============================================================
# TAB 2: SEARCH QUESTIONS
# ============================================================
with gr.Tab("Question Search"):
gr.Markdown("### Search & Browse Questions")
with gr.Row():
search_keyword = gr.Textbox(
label="Search Keyword",
placeholder="e.g., quadratic equations, photosynthesis, ohm's law"
)
search_subject_filter = gr.Dropdown(
choices=["All Subjects"] + list(IGCSE_SUBJECTS.keys()),
label="Filter by Subject",
value="All Subjects"
)
search_btn = gr.Button("Search Questions", variant="primary")
search_results = gr.Markdown(value="Enter a keyword to search")
# Selected questions for exam generation
gr.Markdown("### Create Exam from Search Results")
selected_questions_text = gr.Textbox(
label="Selected Question Numbers (comma-separated)",
placeholder="e.g., 1, 3, 5, 7"
)
search_btn.click(
search_questions,
[search_keyword, search_subject_filter],
search_results
)
# ============================================================
# TAB 3: LESSON NOTES GENERATOR
# ============================================================
with gr.Tab("Lesson Notes"):
gr.Markdown("### Generate Comprehensive Teaching Notes")
gr.Markdown("*AI provides complete content and knowledge - ready to teach*")
with gr.Row():
with gr.Column(scale=2):
with gr.Row():
notes_subject = gr.Dropdown(
choices=list(IGCSE_SUBJECTS.keys()),
label="Subject",
value="Mathematics (0580)"
)
notes_topic = gr.Dropdown(
choices=IGCSE_SUBJECTS["Mathematics (0580)"]["topics"],
label="Topic"
)
def update_topics(subject):
topics = IGCSE_SUBJECTS[subject]["topics"]
return gr.Dropdown(choices=topics, value=topics[0] if topics else None)
notes_subject.change(update_topics, notes_subject, notes_topic)
notes_detail = gr.Radio(
choices=["concise", "comprehensive", "detailed"],
label="Detail Level",
value="comprehensive"
)
with gr.Row():
generate_notes_btn = gr.Button("Generate Lesson Notes", variant="primary")
download_notes_btn = gr.Button("Download as PDF", variant="secondary")
notes_pdf_output = gr.File(label="Download Notes PDF")
with gr.Column(scale=3):
notes_output = gr.Markdown(value="Select topic and click generate")
# Store generated notes
generated_notes = gr.State("")
def generate_and_store(subject, topic, detail):
notes = generate_lesson_notes(subject, topic, detail)
return notes, notes
def create_notes_pdf(notes, subject, topic):
if not notes:
return None
pdf_buffer = generate_notes_pdf(notes, subject, topic)
# Save to temporary file
import tempfile
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
temp_file.write(pdf_buffer.read())
temp_file.close()
return temp_file.name
generate_notes_btn.click(
generate_and_store,
[notes_subject, notes_topic, notes_detail],
[notes_output, generated_notes]
)
download_notes_btn.click(
create_notes_pdf,
[generated_notes, notes_subject, notes_topic],
notes_pdf_output
)
gr.Markdown("---")
gr.Markdown("### Ask for Clarification")
gr.Markdown("*Have questions about the content? Ask the AI for clarification*")
clarification_chat = gr.Chatbot(height=300, label="Clarification Chat")
clarification_input = gr.Textbox(
placeholder="Ask for clarification on any part of the notes...",
label="Your Question"
)
with gr.Row():
clarify_btn = gr.Button("Ask", variant="primary")
clear_chat_btn = gr.Button("Clear Chat")
def handle_clarification(question, history, subject, topic, notes):
if not question.strip():
return history
context = f"""You are helping a teacher understand IGCSE {subject} content on {topic}.
Current notes context:
{notes[:1000]}...
Teacher's question: {question}
Provide a clear, direct answer focused on the content and concepts. Be specific and helpful."""
response, _ = ask_ai(context, temperature=0.6)
history.append((question, response))
return history
clarify_btn.click(
handle_clarification,
[clarification_input, clarification_chat, notes_subject, notes_topic, generated_notes],
clarification_chat
)
clarification_input.submit(
handle_clarification,
[clarification_input, clarification_chat, notes_subject, notes_topic, generated_notes],
clarification_chat
)
clear_chat_btn.click(lambda: [], outputs=clarification_chat)
# ============================================================
# TAB 4: ASSESSMENT GENERATOR
# ============================================================
with gr.Tab("Assessment Builder"):
gr.Markdown("### Create Custom Assessments")
with gr.Row():
with gr.Column():
assess_subject = gr.Dropdown(
choices=list(IGCSE_SUBJECTS.keys()),
label="Subject",
value="Mathematics (0580)"
)
assess_topics = gr.CheckboxGroup(
choices=IGCSE_SUBJECTS["Mathematics (0580)"]["topics"],
label="Select Topics"
)
def update_assess_topics(subject):
topics = IGCSE_SUBJECTS[subject]["topics"]
return gr.CheckboxGroup(choices=topics, value=[])
assess_subject.change(update_assess_topics, assess_subject, assess_topics)
assess_num = gr.Slider(
minimum=5,
maximum=20,
value=10,
step=1,
label="Number of Questions"
)
assess_difficulty = gr.Radio(
choices=["easy", "mixed", "challenging"],
label="Difficulty Level",
value="mixed"
)
generate_assess_btn = gr.Button("Generate Assessment", variant="primary")
with gr.Column():
assess_output = gr.Markdown(value="Configure settings and generate")
exam_title_input = gr.Textbox(
label="Exam Title",
value="IGCSE Assessment"
)
include_answers_check = gr.Checkbox(
label="Include Mark Scheme",
value=True
)
download_pdf_btn = gr.Button("Download as PDF")
pdf_output = gr.File(label="Download PDF")
# Store generated questions temporarily
generated_questions = gr.State([])
def generate_and_display(subject, topics, num, difficulty):
if not topics:
return "Please select at least one topic", []
questions = generate_assessment_questions(subject, topics, num, difficulty)
if not questions:
return "Failed to generate questions. Please try again.", []
# Format display
display = f"### Generated {len(questions)} Questions\n\n"
for q in questions:
display += f"""**Question {q['question_number']}** | {q['marks']} marks | {q['difficulty'].title()}
**Topic:** {q['topic']}
{q['question_text']}
**Mark Scheme:** {q.get('mark_scheme', 'Not available')}
---
"""
return display, questions
def create_pdf(questions, title, include_answers):
if not questions:
return None
pdf_buffer = generate_exam_paper(questions, title, include_answers)
return pdf_buffer
generate_assess_btn.click(
generate_and_display,
[assess_subject, assess_topics, assess_num, assess_difficulty],
[assess_output, generated_questions]
)
download_pdf_btn.click(
create_pdf,
[generated_questions, exam_title_input, include_answers_check],
pdf_output
)
# ============================================================
# TAB 5: TOPIC RESOURCES
# ============================================================
with gr.Tab("Topic Resources"):
gr.Markdown("### Browse All IGCSE Topics & Subtopics")
resource_subject = gr.Dropdown(
choices=list(IGCSE_SUBJECTS.keys()),
label="Select Subject",
value="Mathematics (0580)"
)
resource_display = gr.Markdown()
def display_topics(subject):
info = IGCSE_SUBJECTS[subject]
display = f"## {subject}\n\n### Main Topics:\n"
for i, topic in enumerate(info["topics"], 1):
display += f"{i}. **{topic}**\n"
if "subtopics" in info and info["subtopics"]:
display += "\n### Detailed Subtopics:\n"
for main_topic, subtopics in info["subtopics"].items():
display += f"\n**{main_topic}:**\n"
for subtopic in subtopics:
display += f" - {subtopic}\n"
return display
resource_subject.change(display_topics, resource_subject, resource_display)
# Initialize with default subject
app.load(lambda: display_topics("Mathematics (0580)"), outputs=resource_display)
gr.Markdown("""
---
### πŸŽ“ About This Platform
**Professional Teaching Tools for IGCSE Educators**
This platform combines advanced AI technology with comprehensive IGCSE syllabuses to provide:
- Automated question extraction from past papers
- Intelligent lesson note generation
- Custom assessment creation
- Complete syllabus coverage for all major subjects
- Professional PDF export functionality
**AI Models:** Gemini 2.5 Pro (Primary) β†’ Cohere (Secondary) β†’ Z.ai (Tertiary) β†’ MiniMax (Fallback)
**Supported Subjects:** Mathematics, Physics, Chemistry, Biology, ICT, Physical Education
*Designed to save teachers time and enhance teaching quality*
""")
if __name__ == "__main__":
app.launch()