| | |
| | |
| | |
| | |
| |
|
| | 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 |
| |
|
| | |
| | import fitz |
| | import PyPDF2 |
| | import pdfplumber |
| |
|
| | |
| | import google.generativeai as genai |
| | import cohere |
| | from huggingface_hub import InferenceClient |
| |
|
| | |
| | import gradio as gr |
| |
|
| | |
| | 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...") |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | 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 |
| |
|
| | |
| | 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 |
| |
|
| | |
| | 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 |
| |
|
| | |
| | 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 |
| |
|
| | |
| | |
| | |
| |
|
| | def ask_ai(prompt, temperature=0.7, max_retries=2): |
| | """Try models in order: Gemini β Cohere β Z.ai β MiniMax""" |
| | last_error = None |
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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 |
| | |
| | |
| | return f"Error: All AI services failed. Last error: {str(last_error)}", "error" |
| |
|
| | |
| | |
| | |
| |
|
| | 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"] |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | papers_storage = [] |
| | questions_index = [] |
| | pdf_content_storage = {} |
| |
|
| | |
| | |
| | |
| |
|
| | def extract_text_from_pdf(pdf_file): |
| | """Extract text from PDF using multiple methods""" |
| | if pdf_file is None: |
| | return "" |
| | try: |
| | |
| | doc = fitz.open(pdf_file.name) |
| | text = "" |
| | for page in doc: |
| | text += page.get_text() + "\n" |
| | doc.close() |
| | |
| | if len(text.strip()) < 100: |
| | |
| | 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() |
| | |
| | |
| | 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 |
| | |
| | |
| | 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 [] |
| | |
| | |
| | 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() |
| | |
| | |
| | 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 |
| |
|
| | |
| | |
| | |
| |
|
| | 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() |
| | |
| | |
| | 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' |
| | ) |
| | |
| | |
| | story.append(Paragraph(f"IGCSE {subject}", title_style)) |
| | story.append(Paragraph(f"Topic: {topic}", styles['Heading2'])) |
| | story.append(Spacer(1, 20)) |
| | |
| | |
| | lines = notes_content.split('\n') |
| | for line in lines: |
| | line = line.strip() |
| | if not line: |
| | story.append(Spacer(1, 6)) |
| | continue |
| | |
| | |
| | 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)) |
| | |
| | elif line.startswith('**') or '**' in line: |
| | line = line.replace('**', '<b>').replace('**', '</b>') |
| | story.append(Paragraph(line, styles['Normal'])) |
| | |
| | elif line.startswith('-') or line.startswith('β’'): |
| | line = line.lstrip('-β’').strip() |
| | story.append(Paragraph(f" β’ {line}", styles['Normal'])) |
| | |
| | else: |
| | story.append(Paragraph(line, styles['Normal'])) |
| | |
| | doc.build(story) |
| | buffer.seek(0) |
| | return buffer |
| |
|
| | |
| | |
| | |
| |
|
| | 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 [] |
| |
|
| | |
| | |
| | |
| |
|
| | 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() |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | story.append(Paragraph(exam_title, title_style)) |
| | story.append(Paragraph("QUESTION PAPER", styles['Heading2'])) |
| | story.append(Spacer(1, 12)) |
| | |
| | |
| | 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)) |
| | |
| | |
| | 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)) |
| | |
| | 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() |
| | |
| | |
| | title_style = ParagraphStyle( |
| | 'CustomTitle', |
| | parent=styles['Heading1'], |
| | fontSize=16, |
| | textColor=colors.HexColor('#1a237e'), |
| | spaceAfter=30, |
| | alignment=TA_CENTER, |
| | fontName='Helvetica-Bold' |
| | ) |
| | |
| | |
| | story.append(Paragraph(exam_title, title_style)) |
| | story.append(Paragraph("MARKING SCHEME", styles['Heading2'])) |
| | story.append(Spacer(1, 24)) |
| | |
| | |
| | for i, q in enumerate(selected_questions, 1): |
| | |
| | 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 = 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 = 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 |
| |
|
| | |
| | |
| | |
| |
|
| | 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." |
| | |
| | |
| | questions = questions_index |
| | if subject_filter and subject_filter != "All Subjects": |
| | questions = [q for q in questions if q['subject'] == subject_filter] |
| | |
| | |
| | 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}'" |
| | |
| | |
| | result = f"### Found {len(matching)} question(s) matching '{keyword}'\n\n" |
| | |
| | for q in matching[:20]: |
| | 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 |
| |
|
| | |
| | |
| | |
| |
|
| | 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(): |
| | |
| | |
| | |
| | |
| | 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 |
| | |
| | |
| | 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 |
| | |
| | |
| | if subject == "Unknown Subject": |
| | subject = identify_subject_from_content(pdf_text, pdf_file.name) |
| | |
| | status += f"\n\nIdentified as: {subject}" |
| | status += f"\nExtracting questions..." |
| | |
| | |
| | questions = extract_questions_from_pdf(pdf_text, paper_id, title, subject) |
| | |
| | |
| | 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" |
| | |
| | |
| | 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] |
| | ) |
| | |
| | |
| | |
| | |
| | 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") |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | |
| | |
| | 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") |
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |
| | |
| | |
| | |
| | |
| | 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") |
| | |
| | |
| | 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.", [] |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | |
| | |
| | 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) |
| | |
| | |
| | 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() |