mikaelJ46 commited on
Commit
9afb5c4
ยท
verified ยท
1 Parent(s): bbc3703

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1083 -0
app.py ADDED
@@ -0,0 +1,1083 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------------
2
+ # IGCSE Science Platform โ€“ Chemistry & Biology with Deep Understanding Focus
3
+ # Models: Gemini 2.5 (Primary) โ†’ Cohere โ†’ Z.ai โ†’ MiniMax (Fallbacks)
4
+ # --------------------------------------------------------------
5
+
6
+ import os
7
+ import json
8
+ from datetime import datetime
9
+ import gradio as gr
10
+ import PyPDF2
11
+ import time
12
+ import re
13
+ from PIL import Image
14
+ import io
15
+
16
+ # ---------- 1. Configure ALL AI Systems ----------
17
+ # Gemini (Primary)
18
+ try:
19
+ import google.generativeai as genai
20
+ genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
21
+ gemini_model = genai.GenerativeModel('gemini-2.5-pro')
22
+ print("โœ… Gemini AI initialized successfully (PRIMARY)")
23
+ except Exception as e:
24
+ print(f"โŒ Error initializing Gemini: {e}")
25
+ gemini_model = None
26
+
27
+ # Cohere (Secondary)
28
+ try:
29
+ import cohere
30
+ cohere_client = cohere.Client(os.getenv("COHERE_API_KEY"))
31
+ print("โœ… Cohere initialized successfully (SECONDARY)")
32
+ except Exception as e:
33
+ print(f"โŒ Error initializing Cohere: {e}")
34
+ cohere_client = None
35
+
36
+ # Z.ai (Tertiary)
37
+ try:
38
+ from huggingface_hub import InferenceClient
39
+ zai_client = InferenceClient(
40
+ provider="novita",
41
+ api_key=os.environ.get("HF_TOKEN"),
42
+ )
43
+ print("โœ… Z.ai GLM-4.6 initialized successfully (TERTIARY)")
44
+ except Exception as e:
45
+ print(f"โŒ Error initializing Z.ai: {e}")
46
+ zai_client = None
47
+
48
+ # MiniMax (Final Fallback)
49
+ try:
50
+ minimax_client = InferenceClient(
51
+ provider="novita",
52
+ api_key=os.environ.get("HF_TOKEN"),
53
+ )
54
+ print("โœ… MiniMax AI initialized successfully (FINAL FALLBACK)")
55
+ except Exception as e:
56
+ print(f"โŒ Error initializing MiniMax: {e}")
57
+ minimax_client = None
58
+
59
+ # ---------- 2. Unified AI Function with Smart Fallback ----------
60
+ def ask_ai(prompt, temperature=0.7, max_retries=2):
61
+ """
62
+ Try models in order: Gemini โ†’ Cohere โ†’ Z.ai โ†’ MiniMax
63
+ Returns: (response_text, source_name)
64
+ """
65
+ last_error = None
66
+
67
+ # Try Gemini first (Primary)
68
+ if gemini_model:
69
+ for attempt in range(max_retries):
70
+ try:
71
+ response = gemini_model.generate_content(
72
+ prompt,
73
+ generation_config=genai.types.GenerationConfig(
74
+ temperature=temperature,
75
+ )
76
+ )
77
+ return response.text, "gemini"
78
+ except Exception as e:
79
+ last_error = e
80
+ print(f"โš  Gemini attempt {attempt+1} failed: {str(e)}")
81
+ if attempt < max_retries - 1:
82
+ time.sleep(1)
83
+
84
+ # Try Cohere (Secondary)
85
+ if cohere_client:
86
+ for attempt in range(max_retries):
87
+ try:
88
+ response = cohere_client.chat(
89
+ model="command-r-plus-08-2024",
90
+ message=prompt,
91
+ temperature=temperature
92
+ )
93
+ return response.text, "cohere"
94
+ except Exception as e:
95
+ last_error = e
96
+ print(f"โš  Cohere attempt {attempt+1} failed: {str(e)}")
97
+ if attempt < max_retries - 1:
98
+ time.sleep(1)
99
+
100
+ # Try Z.ai (Tertiary)
101
+ if zai_client:
102
+ for attempt in range(max_retries):
103
+ try:
104
+ completion = zai_client.chat.completions.create(
105
+ model="zai-org/GLM-4.6",
106
+ messages=[{"role": "user", "content": prompt}],
107
+ temperature=temperature
108
+ )
109
+ return completion.choices[0].message.content, "zai"
110
+ except Exception as e:
111
+ last_error = e
112
+ print(f"โš  Z.ai attempt {attempt+1} failed: {str(e)}")
113
+ if attempt < max_retries - 1:
114
+ time.sleep(1)
115
+
116
+ # Try MiniMax (Final Fallback)
117
+ if minimax_client:
118
+ try:
119
+ completion = minimax_client.chat.completions.create(
120
+ model="MiniMaxAI/MiniMax-M2",
121
+ messages=[{"role": "user", "content": prompt}],
122
+ temperature=temperature
123
+ )
124
+ return completion.choices[0].message.content, "minimax"
125
+ except Exception as e:
126
+ last_error = e
127
+ print(f"โš  MiniMax fallback failed: {str(e)}")
128
+
129
+ # All failed
130
+ error_msg = f"โŒ Error: All AI services failed. Last error: {str(last_error)}"
131
+ return error_msg, "error"
132
+
133
+ # ---------- 3. Enhanced Global Storage ----------
134
+ papers_storage = []
135
+ pdf_content_storage = {}
136
+ insert_storage = {}
137
+ questions_index = []
138
+ ADMIN_PASSWORD = "@mikaelJ46"
139
+
140
+ # ---------- 4. Comprehensive Topic Lists ----------
141
+ chemistry_topics = [
142
+ # Principles of Chemistry
143
+ "States of Matter", "Atoms, Elements & Compounds", "Mixtures & Separation Techniques",
144
+ "Atomic Structure", "Electronic Configuration", "Periodic Table",
145
+ "Chemical Bonding: Ionic", "Chemical Bonding: Covalent", "Chemical Bonding: Metallic",
146
+ "Structure & Properties of Materials", "Nanoparticles",
147
+
148
+ # Inorganic Chemistry
149
+ "Group 1: Alkali Metals", "Group 7: Halogens", "Group 0: Noble Gases",
150
+ "Transition Metals", "Reactivity Series", "Extraction of Metals",
151
+ "Corrosion & Rusting", "Alloys",
152
+
153
+ # Physical Chemistry
154
+ "Chemical Reactions", "Exothermic & Endothermic Reactions", "Energy Changes",
155
+ "Rates of Reaction", "Catalysts", "Reversible Reactions", "Equilibrium",
156
+ "Redox Reactions", "Electrolysis", "Electrochemistry",
157
+
158
+ # Acids, Bases & Salts
159
+ "Acids & Alkalis", "pH Scale", "Neutralization", "Making Salts",
160
+ "Titrations", "Strong & Weak Acids",
161
+
162
+ # Organic Chemistry
163
+ "Hydrocarbons: Alkanes", "Hydrocarbons: Alkenes", "Crude Oil & Fractional Distillation",
164
+ "Polymers", "Alcohols", "Carboxylic Acids", "Organic Synthesis",
165
+
166
+ # Chemistry of the Environment
167
+ "Air Composition", "Air Pollution", "Greenhouse Effect & Climate Change",
168
+ "Water Treatment", "Sustainable Chemistry",
169
+
170
+ # Quantitative Chemistry
171
+ "Relative Formula Mass", "Moles & Molar Mass", "Empirical & Molecular Formulae",
172
+ "Reacting Masses", "Limiting Reactants", "Percentage Yield",
173
+ "Gas Volumes", "Concentration Calculations",
174
+
175
+ # Practical Skills
176
+ "Laboratory Safety", "Experimental Techniques", "Analysis & Evaluation"
177
+ ]
178
+
179
+ biology_topics = [
180
+ # Cell Biology
181
+ "Cell Structure & Function", "Specialised Cells", "Microscopy",
182
+ "Cell Division: Mitosis", "Cell Division: Meiosis", "Stem Cells",
183
+ "Diffusion", "Osmosis", "Active Transport",
184
+
185
+ # Organisation
186
+ "Organisation of Organisms", "Enzymes", "Digestive System",
187
+ "Circulatory System: Heart", "Circulatory System: Blood Vessels", "Blood Components",
188
+ "Respiratory System", "Gas Exchange", "Breathing Mechanism",
189
+
190
+ # Infection & Response
191
+ "Communicable Diseases", "Pathogens: Bacteria & Viruses", "Disease Prevention",
192
+ "Immune System", "Vaccination", "Antibiotics & Painkillers",
193
+ "Developing New Medicines", "Monoclonal Antibodies",
194
+
195
+ # Bioenergetics
196
+ "Photosynthesis", "Factors Affecting Photosynthesis", "Uses of Glucose",
197
+ "Respiration: Aerobic", "Respiration: Anaerobic", "Metabolism",
198
+
199
+ # Homeostasis & Response
200
+ "Homeostasis Principles", "Nervous System", "Reflex Actions", "Brain Structure",
201
+ "Eye Structure & Function", "Body Temperature Control",
202
+ "Endocrine System", "Hormones", "Blood Glucose Regulation",
203
+ "Diabetes", "Water & Nitrogen Balance", "Kidneys & Dialysis",
204
+
205
+ # Inheritance, Variation & Evolution
206
+ "DNA Structure", "Protein Synthesis", "Genetic Inheritance",
207
+ "Inherited Disorders", "Sex Determination", "Genetic Diagrams",
208
+ "Variation", "Evolution", "Natural Selection", "Selective Breeding",
209
+ "Genetic Engineering", "Cloning", "Classification",
210
+
211
+ # Ecology
212
+ "Ecosystems", "Food Chains & Webs", "Energy Transfer",
213
+ "Nutrient Cycles: Carbon", "Nutrient Cycles: Water", "Nutrient Cycles: Nitrogen",
214
+ "Biodiversity", "Habitat Loss", "Conservation",
215
+ "Population Dynamics", "Competition", "Adaptations",
216
+ "Waste Management", "Pollution", "Global Warming Impact",
217
+ "Deforestation", "Sustainable Development",
218
+
219
+ # Practical Skills
220
+ "Scientific Method", "Variables & Controls", "Data Analysis",
221
+ "Biological Techniques", "Field Studies"
222
+ ]
223
+
224
+ # ---------- 5. Enhanced PDF Processing ----------
225
+ def extract_text_from_pdf(pdf_file):
226
+ """Extract text from uploaded PDF file"""
227
+ if pdf_file is None:
228
+ return ""
229
+ try:
230
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
231
+ text = ""
232
+ for page in pdf_reader.pages:
233
+ text += page.extract_text() + "\n"
234
+ return text
235
+ except Exception as e:
236
+ return f"Error extracting PDF: {e}"
237
+
238
+ def identify_paper_details(text, filename):
239
+ """Use AI to identify paper year, series, variant, and subject from content"""
240
+ sample_text = text[:2000] if len(text) > 2000 else text
241
+
242
+ prompt = f"""Analyze this IGCSE science past paper and identify its details.
243
+
244
+ Filename: {filename}
245
+ Paper Text Sample:
246
+ {sample_text}
247
+
248
+ Identify and return ONLY a JSON object with:
249
+ - subject: "Chemistry" or "Biology"
250
+ - year: The year (e.g., "2023", "2022")
251
+ - series: The exam series (e.g., "June", "November", "May/June", "October/November")
252
+ - variant: The paper variant (e.g., "1", "2", "3" or "11", "12", "21", "22")
253
+ - paper_number: The paper number (e.g., "1", "2", "3", "4", "6")
254
+ - syllabus_code: If visible (e.g., "0620" for Chemistry, "0610" for Biology)
255
+
256
+ Look for clues like "Cambridge IGCSE", subject codes, dates, paper numbers.
257
+
258
+ Return ONLY valid JSON (no markdown):
259
+ {{"subject": "...", "year": "...", "series": "...", "variant": "...", "paper_number": "...", "syllabus_code": "..."}}"""
260
+
261
+ try:
262
+ response, _ = ask_ai(prompt, temperature=0.1)
263
+ clean_txt = response.replace("```json", "").replace("```", "").strip()
264
+ details = json.loads(clean_txt)
265
+ return details
266
+ except Exception as e:
267
+ print(f"Error identifying paper details: {e}")
268
+ return parse_filename_for_details(filename)
269
+
270
+ def parse_filename_for_details(filename):
271
+ """Fallback: Parse filename for paper details"""
272
+ details = {
273
+ "subject": "Unknown",
274
+ "year": "Unknown",
275
+ "series": "Unknown",
276
+ "variant": "Unknown",
277
+ "paper_number": "Unknown",
278
+ "syllabus_code": "Unknown"
279
+ }
280
+
281
+ # Extract year
282
+ year_match = re.search(r'(20\d{2})|(\d{2}(?=_[wsmj]|[WS]))', filename)
283
+ if year_match:
284
+ year = year_match.group(1) or ("20" + year_match.group(2))
285
+ details["year"] = year
286
+
287
+ # Extract series
288
+ if re.search(r'[Jj]une?|[Mm]ay[_/-]?[Jj]une?|mj|MJ', filename):
289
+ details["series"] = "May/June"
290
+ elif re.search(r'[Nn]ov(ember)?|[Oo]ct(ober)?|ON', filename):
291
+ details["series"] = "October/November"
292
+ elif re.search(r'[Mm]ar(ch)?|[Ff]eb(ruary)?|FM', filename):
293
+ details["series"] = "February/March"
294
+
295
+ # Extract variant
296
+ variant_match = re.search(r'[Vv]ariant[_\s]?(\d)|[Pp]aper[_\s]?(\d{1,2})|_qp_(\d{1,2})', filename)
297
+ if variant_match:
298
+ details["variant"] = variant_match.group(1) or variant_match.group(2) or variant_match.group(3)
299
+
300
+ # Extract syllabus code and subject
301
+ code_match = re.search(r'\b(0\d{3})\b', filename)
302
+ if code_match:
303
+ details["syllabus_code"] = code_match.group(1)
304
+ code_subject_map = {
305
+ '0620': 'Chemistry', '0610': 'Biology'
306
+ }
307
+ details["subject"] = code_subject_map.get(code_match.group(1), "Unknown")
308
+
309
+ return details
310
+
311
+ def extract_questions_from_text(text, paper_id, paper_title, subject, paper_details):
312
+ """Use AI to intelligently extract questions from past paper text"""
313
+ if not text or len(text) < 100:
314
+ return []
315
+
316
+ prompt = f"""Analyze this IGCSE {subject} past paper and extract ALL questions.
317
+
318
+ Paper Details:
319
+ - Subject: {subject}
320
+ - Year: {paper_details.get('year', 'Unknown')}
321
+ - Series: {paper_details.get('series', 'Unknown')}
322
+ - Paper: {paper_details.get('paper_number', 'Unknown')}
323
+ - Variant: {paper_details.get('variant', 'Unknown')}
324
+
325
+ Paper Text:
326
+ {text[:8000]}
327
+
328
+ Extract each question and return as JSON array. For each question include:
329
+ - question_number (e.g., "1(a)", "2(b)(i)")
330
+ - question_text (the complete question)
331
+ - marks (number of marks)
332
+ - topic (specific IGCSE {subject} topic)
333
+ - requires_insert (true/false - references diagrams, figures, data?)
334
+ - question_type (e.g., "multiple choice", "structured", "practical", "calculation", "explanation")
335
+
336
+ Return ONLY valid JSON array (no markdown):
337
+ [{{"question_number": "1(a)", "question_text": "...", "marks": 2, "topic": "...", "requires_insert": false, "question_type": "..."}}]"""
338
+
339
+ try:
340
+ response, _ = ask_ai(prompt, temperature=0.2)
341
+ clean_txt = response.replace("```json", "").replace("```", "").strip()
342
+ questions = json.loads(clean_txt)
343
+
344
+ for q in questions:
345
+ q['paper_id'] = paper_id
346
+ q['paper_title'] = paper_title
347
+ q['subject'] = subject
348
+ q['year'] = paper_details.get('year', 'Unknown')
349
+ q['series'] = paper_details.get('series', 'Unknown')
350
+ q['variant'] = paper_details.get('variant', 'Unknown')
351
+ q['paper_number'] = paper_details.get('paper_number', 'Unknown')
352
+ q['syllabus_code'] = paper_details.get('syllabus_code', 'Unknown')
353
+
354
+ return questions
355
+ except Exception as e:
356
+ print(f"Error extracting questions: {e}")
357
+ return extract_questions_fallback(text, paper_id, paper_title, subject, paper_details)
358
+
359
+ def extract_questions_fallback(text, paper_id, paper_title, subject, paper_details):
360
+ """Fallback method using regex patterns"""
361
+ questions = []
362
+ pattern = r'(\d+(?:\([a-z]\))?(?:\([ivx]+\))?)\s+(.{20,500}?)\[(\d+)\]'
363
+ matches = re.finditer(pattern, text, re.IGNORECASE)
364
+
365
+ for match in matches:
366
+ q_num = match.group(1)
367
+ q_text = match.group(2).strip()
368
+ marks = int(match.group(3))
369
+
370
+ questions.append({
371
+ 'question_number': q_num,
372
+ 'question_text': q_text,
373
+ 'marks': marks,
374
+ 'topic': 'General',
375
+ 'requires_insert': bool(re.search(r'Fig\.|diagram|table|graph|data|shown', q_text, re.IGNORECASE)),
376
+ 'question_type': 'structured',
377
+ 'paper_id': paper_id,
378
+ 'paper_title': paper_title,
379
+ 'subject': subject,
380
+ 'year': paper_details.get('year', 'Unknown'),
381
+ 'series': paper_details.get('series', 'Unknown'),
382
+ 'variant': paper_details.get('variant', 'Unknown'),
383
+ 'paper_number': paper_details.get('paper_number', 'Unknown'),
384
+ 'syllabus_code': paper_details.get('syllabus_code', 'Unknown')
385
+ })
386
+
387
+ return questions
388
+
389
+ def process_insert_file(insert_file):
390
+ """Process insert file (PDF or image)"""
391
+ if insert_file is None:
392
+ return None, None
393
+
394
+ try:
395
+ file_name = insert_file.name
396
+ file_ext = file_name.lower().split('.')[-1]
397
+
398
+ if file_ext == 'pdf':
399
+ text = extract_text_from_pdf(insert_file)
400
+ return text, "pdf"
401
+ elif file_ext in ['jpg', 'jpeg', 'png', 'gif']:
402
+ image = Image.open(insert_file)
403
+ return image, "image"
404
+ else:
405
+ return None, None
406
+ except Exception as e:
407
+ print(f"Error processing insert: {e}")
408
+ return None, None
409
+
410
+ # ---------- 6. Deep Understanding AI Tutor ----------
411
+ def ai_tutor_chat(message, history, subject, topic):
412
+ """AI tutor focused on deep understanding and conceptual clarity"""
413
+ if not message.strip():
414
+ return history
415
+
416
+ subject_context = {
417
+ "Chemistry": """You are an expert IGCSE Chemistry tutor who prioritizes DEEP UNDERSTANDING over memorization.
418
+
419
+ Your teaching approach:
420
+ - Always explain the WHY behind chemical phenomena (not just the what)
421
+ - Connect microscopic (atomic/molecular) behavior to macroscopic observations
422
+ - Use real-world examples and applications to make concepts tangible
423
+ - Break down complex reactions into step-by-step mechanisms
424
+ - Emphasize patterns and relationships (e.g., periodic trends, reaction types)
425
+ - Address common misconceptions directly
426
+ - Use analogies and visual descriptions to clarify abstract concepts
427
+ - Encourage students to predict outcomes based on understanding, not memorization
428
+ - Link different topics together (e.g., bonding โ†’ properties โ†’ reactivity)
429
+
430
+ Key teaching principles:
431
+ - Particle theory underlies everything (structure determines properties)
432
+ - Energy changes drive chemical processes
433
+ - Conservation laws (mass, charge, energy) are fundamental
434
+ - Equilibrium and rates are about competing processes""",
435
+
436
+ "Biology": """You are an expert IGCSE Biology tutor who emphasizes DEEP UNDERSTANDING and interconnected thinking.
437
+
438
+ Your teaching approach:
439
+ - Always explain biological processes in terms of structure-function relationships
440
+ - Connect molecular/cellular processes to organism-level phenomena
441
+ - Use real-world health, ecology, and evolution examples
442
+ - Explain mechanisms step-by-step (don't just list facts)
443
+ - Emphasize the REASONS for biological adaptations and processes
444
+ - Address common misconceptions about evolution, genetics, and body systems
445
+ - Use analogies to make complex processes accessible (but explain their limits)
446
+ - Show how different biological systems interact and depend on each other
447
+ - Encourage students to apply knowledge to novel situations
448
+ - Link topics together (e.g., respiration โ†’ transport โ†’ gas exchange)
449
+
450
+ Key teaching principles:
451
+ - Evolution by natural selection explains adaptations
452
+ - Enzymes control the rate of life processes
453
+ - Homeostasis maintains stable internal conditions
454
+ - Energy flow and nutrient cycling connect ecology
455
+ - DNA โ†’ RNA โ†’ protein โ†’ trait (central dogma)"""
456
+ }
457
+
458
+ system = f"""{subject_context[subject]}
459
+
460
+ Current focus: {topic or 'any topic'}
461
+
462
+ When answering:
463
+ 1. Check for understanding gaps before giving the full answer
464
+ 2. Use the Socratic method - guide thinking with questions
465
+ 3. Provide detailed step-by-step explanations with reasoning
466
+ 4. Include diagrams descriptions when helpful
467
+ 5. Give practice examples for students to try
468
+ 6. Connect to exam skills (command words, mark schemes)
469
+ 7. Celebrate curiosity and deeper questions
470
+
471
+ Remember: Understanding beats memorization. Help students THINK like scientists."""
472
+
473
+ # Build conversation context
474
+ conversation = ""
475
+ for user_msg, bot_msg in history[-6:]:
476
+ if user_msg:
477
+ conversation += f"Student: {user_msg}\n"
478
+ if bot_msg:
479
+ clean_msg = bot_msg.replace("๐Ÿ”ต ", "").replace("๐ŸŸข ", "").replace("๐ŸŸฃ ", "")
480
+ conversation += f"Tutor: {clean_msg}\n"
481
+
482
+ conversation += f"Student: {message}\nTutor:"
483
+ full_prompt = f"{system}\n\nConversation:\n{conversation}"
484
+
485
+ bot_response, source = ask_ai(full_prompt, temperature=0.7)
486
+
487
+ # Add source indicator
488
+ if source == "cohere":
489
+ bot_response = f"๐Ÿ”ต {bot_response}"
490
+ elif source == "zai":
491
+ bot_response = f"๐ŸŸข {bot_response}"
492
+ elif source == "minimax":
493
+ bot_response = f"๐ŸŸฃ {bot_response}"
494
+
495
+ history.append((message, bot_response))
496
+ return history
497
+
498
+ def clear_chat():
499
+ return []
500
+
501
+ # ---------- 7. Concept Explainer with Depth ----------
502
+ def explain_concept(subject, concept):
503
+ """Deep dive explanation of scientific concepts"""
504
+ if not concept:
505
+ return "Enter a concept to explain!"
506
+
507
+ prompt = f"""Provide a COMPREHENSIVE explanation of this IGCSE {subject} concept: "{concept}"
508
+
509
+ Structure your explanation as follows:
510
+
511
+ **1. CORE IDEA** (In simple terms - what IS it?)
512
+
513
+ **2. DEEPER UNDERSTANDING** (Why does it work this way? What's the mechanism?)
514
+
515
+ **3. KEY DETAILS & FACTS** (Important specifics students need to know)
516
+
517
+ **4. COMMON MISCONCEPTIONS** (What do students often get wrong?)
518
+
519
+ **5. REAL-WORLD CONNECTIONS** (Where do we see this? Why does it matter?)
520
+
521
+ **6. EXAM TIPS** (What questions test this? How to approach them?)
522
+
523
+ **7. PRACTICE THINKING** (A question to test understanding)
524
+
525
+ Use clear language, step-by-step reasoning, and helpful analogies.
526
+ Make connections to other topics. Focus on UNDERSTANDING, not just facts."""
527
+
528
+ response, source = ask_ai(prompt, temperature=0.5)
529
+
530
+ if source in ["cohere", "zai", "minimax"]:
531
+ response = f"{response}\n\n_[Explained by {source.title()}]_"
532
+
533
+ return response
534
+
535
+ # ---------- 8. Calculation Helper ----------
536
+ def solve_calculation(subject, problem, show_steps):
537
+ """Step-by-step calculation solver with conceptual explanation"""
538
+ if not problem.strip():
539
+ return "Enter a calculation problem!"
540
+
541
+ steps_instruction = "Show EVERY step with full working" if show_steps else "Show key steps"
542
+
543
+ prompt = f"""Solve this IGCSE {subject} calculation problem with DEEP EXPLANATION:
544
+
545
+ Problem: {problem}
546
+
547
+ Provide:
548
+ 1. **What we're finding**: Identify what the question asks for
549
+ 2. **What we know**: List given information and its meaning
550
+ 3. **Formula/Concept**: Which formula/principle applies and WHY
551
+ 4. **Step-by-step solution**: {steps_instruction} with units
552
+ 5. **Checking**: Does the answer make sense? Why?
553
+ 6. **Concept explanation**: What does this result mean scientifically?
554
+ 7. **Common mistakes**: What errors do students typically make?
555
+ 8. **Related problems**: Similar question types to practice
556
+
557
+ Use clear formatting. Explain the reasoning at each step, not just the math."""
558
+
559
+ response, source = ask_ai(prompt, temperature=0.3)
560
+
561
+ if source in ["cohere", "zai", "minimax"]:
562
+ response = f"{response}\n\n_[Solved by {source.title()}]_"
563
+
564
+ return response
565
+
566
+ # ---------- 9. Experiment Analyzer ----------
567
+ def analyze_experiment(subject, experiment_description, question):
568
+ """Analyze experiments and practical work with scientific reasoning"""
569
+ if not experiment_description.strip():
570
+ return "Describe the experiment!"
571
+
572
+ prompt = f"""Analyze this IGCSE {subject} experiment with focus on SCIENTIFIC THINKING:
573
+
574
+ Experiment: {experiment_description}
575
+
576
+ Question: {question if question else "Analyze this experiment comprehensively"}
577
+
578
+ Provide:
579
+ 1. **Aim**: What is being investigated and why?
580
+ 2. **Science Behind It**: What principles/concepts does this test?
581
+ 3. **Method Analysis**: Why is it done this way? What makes it valid?
582
+ 4. **Variables**: Independent, dependent, control variables and why they matter
583
+ 5. **Expected Results**: What should happen and WHY (predict using theory)
584
+ 6. **Safety & Practical Tips**: Important precautions and techniques
585
+ 7. **Possible Errors**: What could go wrong? How to minimize errors?
586
+ 8. **Results Analysis**: How to interpret data scientifically
587
+ 9. **Evaluation**: How could this experiment be improved?
588
+ 10. **Exam Connection**: How might this be tested?
589
+
590
+ Think like a scientist - connect method to theory."""
591
+
592
+ response, source = ask_ai(prompt, temperature=0.4)
593
+
594
+ if source in ["cohere", "zai", "minimax"]:
595
+ response = f"{response}\n\n_[Analyzed by {source.title()}]_"
596
+
597
+ return response
598
+
599
+ # ---------- 10. Enhanced Practice Questions ----------
600
+ def generate_question(subject, topic, difficulty):
601
+ """Generate practice questions with focus on understanding"""
602
+ if not topic:
603
+ return "Select a topic!", "", ""
604
+
605
+ difficulty_guide = {
606
+ "Easy": "Test basic understanding and recall. Simple calculations or describe questions.",
607
+ "Medium": "Test application and analysis. Require explanations and connections.",
608
+ "Hard": "Test evaluation and synthesis. Multi-step problems, novel scenarios."
609
+ }
610
+
611
+ pdf_context = ""
612
+ for paper_id, content in pdf_content_storage.items():
613
+ paper = next((p for p in papers_storage if p['id'] == paper_id), None)
614
+ if paper and paper['subject'] == subject:
615
+ pdf_context += f"\n\nReference: {paper['title']}:\n{content[:2000]}"
616
+
617
+ prompt = f"""Create ONE high-quality IGCSE {subject} exam question on: "{topic}"
618
+
619
+ Difficulty: {difficulty} - {difficulty_guide[difficulty]}
620
+ {f"Base style on: {pdf_context[:1500]}" if pdf_context else "Create authentic exam-style question."}
621
+
622
+ The question should:
623
+ - Test UNDERSTANDING, not just recall
624
+ - Use appropriate command words (describe, explain, evaluate, calculate, etc.)
625
+ - Be worth 4-8 marks
626
+ - Include context/data if relevant
627
+ - Test ability to apply knowledge to new situations
628
+
629
+ Return ONLY valid JSON (no markdown):
630
+ {{
631
+ "question": "complete question with all context",
632
+ "marks": 6,
633
+ "command_word": "explain/describe/calculate/etc",
634
+ "expectedAnswer": "detailed key points with scientific reasoning",
635
+ "markScheme": "specific mark allocations and what earns each mark",
636
+ "understandingTips": "what concepts students need to understand to answer this"
637
+ }}"""
638
+
639
+ response, source = ask_ai(prompt, temperature=0.4)
640
+
641
+ try:
642
+ clean_txt = response.replace("```json", "").replace("```", "").strip()
643
+ data = json.loads(clean_txt)
644
+
645
+ question_text = f"**[{data['marks']} marks] - {data['command_word'].upper()}**\n\n{data['question']}"
646
+ expected = f"**Understanding Required:**\n{data.get('understandingTips', '')}\n\n**Key Points:**\n{data['expectedAnswer']}"
647
+ marks = data['markScheme']
648
+
649
+ return question_text, expected, marks
650
+ except:
651
+ return response, "", "Error parsing response"
652
+
653
+ def check_answer(question, expected, user_answer, subject):
654
+ """Check answers with focus on understanding and reasoning"""
655
+ if not user_answer.strip():
656
+ return "Write your answer first!"
657
+
658
+ prompt = f"""Evaluate this IGCSE {subject} answer focusing on UNDERSTANDING and SCIENTIFIC REASONING:
659
+
660
+ Question: {question}
661
+
662
+ Expected answer points: {expected}
663
+
664
+ Student's answer:
665
+ {user_answer}
666
+
667
+ Assess:
668
+ 1. Scientific accuracy
669
+ 2. Depth of understanding (not just memorization)
670
+ 3. Use of scientific terminology
671
+ 4. Logical reasoning and explanations
672
+ 5. Answering the specific command word
673
+ 6. Completeness
674
+
675
+ Return JSON (no markdown):
676
+ {{
677
+ "score": 0-100,
678
+ "marks": "X/8",
679
+ "understanding_level": "surface/developing/strong/excellent",
680
+ "feedback": "detailed feedback on scientific understanding",
681
+ "strengths": "what shows good understanding",
682
+ "improvements": "how to deepen understanding",
683
+ "misconceptions": "any misunderstandings evident",
684
+ "examTips": "exam technique advice",
685
+ "followUpQuestion": "a question to test/extend understanding further"
686
+ }}"""
687
+
688
+ response, source = ask_ai(prompt, temperature=0.3)
689
+
690
+ try:
691
+ clean_txt = response.replace("```json", "").replace("```", "").strip()
692
+ fb = json.loads(clean_txt)
693
+
694
+ result = f"""๐Ÿ“Š **Score: {fb['score']}% ({fb['marks']})**
695
+ **Understanding Level:** {fb['understanding_level'].upper()}
696
+
697
+ ๐Ÿ“ **Detailed Feedback:**
698
+ {fb['feedback']}
699
+
700
+ โœ… **Your Strengths:**
701
+ {fb['strengths']}
702
+
703
+ ๐Ÿ“ˆ **How to Deepen Understanding:**
704
+ {fb['improvements']}
705
+
706
+ โš ๏ธ **Misconceptions to Address:**
707
+ {fb.get('misconceptions', 'None identified')}
708
+
709
+ ๐Ÿ’ก **Exam Tips:**
710
+ {fb['examTips']}
711
+
712
+ ๐Ÿค” **Think Further:**
713
+ {fb.get('followUpQuestion', 'Keep practicing!')}"""
714
+
715
+ if source in ["cohere", "zai", "minimax"]:
716
+ result += f"\n\n_[Graded by {source.title()}]_"
717
+
718
+ return result
719
+ except:
720
+ return response
721
+
722
+ # ---------- 11. Past Papers Browser ----------
723
+ def search_questions_by_topic(subject, topic):
724
+ """Search for questions matching a specific topic"""
725
+ if not questions_index:
726
+ return "๐Ÿ“ญ No questions available yet. Admin needs to upload past papers first!"
727
+
728
+ matching = [q for q in questions_index
729
+ if q['subject'] == subject and
730
+ (topic.lower() in q['topic'].lower() or topic.lower() in q['question_text'].lower())]
731
+
732
+ if not matching:
733
+ return f"๐Ÿ“ญ No questions found for {topic} in {subject}. Try a different topic or broader search."
734
+
735
+ result = f"### ๐ŸŽฏ Found {len(matching)} question(s) on '{topic}' in {subject}\n\n"
736
+
737
+ for i, q in enumerate(matching, 1):
738
+ insert_note = " ๐Ÿ–ผ๏ธ **[Requires Insert]**" if q.get('requires_insert') else ""
739
+ q_type = f" ({q.get('question_type', 'structured')})" if q.get('question_type') else ""
740
+
741
+ paper_info = f"**{q['year']} {q['series']}** - Paper {q['paper_number']}"
742
+ if q.get('variant') != 'Unknown':
743
+ paper_info += f" Variant {q['variant']}"
744
+ if q.get('syllabus_code') != 'Unknown':
745
+ paper_info += f" ({q['syllabus_code']})"
746
+
747
+ result += f"""**Question {i}** - {paper_info}
748
+ ๐Ÿ“ **{q['question_number']}** [{q['marks']} marks]{q_type}{insert_note}
749
+ {q['question_text']}
750
+
751
+ {'โ”€'*80}
752
+ """
753
+
754
+ return result
755
+
756
+ def view_papers_student(subject):
757
+ """View all papers for a subject"""
758
+ filtered = [p for p in papers_storage if p["subject"] == subject]
759
+ if not filtered:
760
+ return f"๐Ÿ“ญ No {subject} papers available."
761
+
762
+ result = ""
763
+ for p in filtered:
764
+ insert_note = " ๐Ÿ–ผ๏ธ Insert Available" if p['id'] in insert_storage else ""
765
+ q_count = len([q for q in questions_index if q['paper_id'] == p['id']])
766
+
767
+ paper_details = p.get('paper_details', {})
768
+ year = paper_details.get('year', 'Unknown')
769
+ series = paper_details.get('series', 'Unknown')
770
+ variant = paper_details.get('variant', 'Unknown')
771
+ paper_num = paper_details.get('paper_number', 'Unknown')
772
+ syllabus = paper_details.get('syllabus_code', 'Unknown')
773
+
774
+ paper_info = f"**{year} {series}** - Paper {paper_num}"
775
+ if variant != 'Unknown':
776
+ paper_info += f" Variant {variant}"
777
+ if syllabus != 'Unknown':
778
+ paper_info += f" ({syllabus})"
779
+
780
+ result += f"""**{p['title']}** {'๐Ÿ“„ PDF' if p.get('has_pdf') else ''}{insert_note}
781
+ {paper_info}
782
+ โฐ Uploaded: {p['uploaded_at']} | ๐Ÿ“ {q_count} questions extracted
783
+ {p['content'][:200]}...
784
+
785
+ {'โ•'*80}
786
+ """
787
+
788
+ return result
789
+
790
+ # ---------- 12. Admin Functions ----------
791
+ def verify_admin_password(password):
792
+ if password == ADMIN_PASSWORD:
793
+ return gr.update(visible=True), gr.update(visible=False), "โœ… Access granted!"
794
+ return gr.update(visible=False), gr.update(visible=True), "โŒ Incorrect password!"
795
+
796
+ def upload_paper(title, subject, content, pdf_file, insert_file):
797
+ """Upload past papers with AI extraction"""
798
+ if not all([title, subject, content]):
799
+ return "โš  Please fill all required fields!", get_papers_list(), "๐Ÿ“Š Status: Waiting for upload"
800
+
801
+ paper_id = len(papers_storage) + 1
802
+
803
+ pdf_text = ""
804
+ paper_details = {}
805
+ if pdf_file is not None:
806
+ pdf_text = extract_text_from_pdf(pdf_file)
807
+ if pdf_text and not pdf_text.startswith("Error"):
808
+ paper_details = identify_paper_details(pdf_text, pdf_file.name)
809
+ pdf_content_storage[paper_id] = pdf_text
810
+
811
+ detail_str = f"\n\n๐Ÿ“‹ **Paper Details:**"
812
+ detail_str += f"\n- Year: {paper_details.get('year', 'Unknown')}"
813
+ detail_str += f"\n- Series: {paper_details.get('series', 'Unknown')}"
814
+ detail_str += f"\n- Paper: {paper_details.get('paper_number', 'Unknown')}"
815
+ detail_str += f"\n- Variant: {paper_details.get('variant', 'Unknown')}"
816
+ if paper_details.get('syllabus_code') != 'Unknown':
817
+ detail_str += f"\n- Syllabus Code: {paper_details.get('syllabus_code')}"
818
+ content += detail_str
819
+ content += f"\n[๐Ÿ“„ PDF extracted: {len(pdf_text)} characters]"
820
+
821
+ insert_data = None
822
+ insert_type = None
823
+ if insert_file is not None:
824
+ insert_data, insert_type = process_insert_file(insert_file)
825
+ if insert_data:
826
+ insert_storage[paper_id] = (insert_data, insert_type)
827
+ content += f"\n[๐Ÿ–ผ๏ธ Insert attached: {insert_type}]"
828
+
829
+ papers_storage.append({
830
+ "id": paper_id,
831
+ "title": title,
832
+ "subject": subject,
833
+ "content": content,
834
+ "has_pdf": bool(pdf_text and not pdf_text.startswith("Error")),
835
+ "has_insert": bool(insert_data),
836
+ "paper_details": paper_details,
837
+ "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
838
+ })
839
+
840
+ status_msg = "โœ… Paper uploaded!"
841
+ if pdf_text and not pdf_text.startswith("Error"):
842
+ status_msg += "\nโณ AI is extracting questions..."
843
+ questions = extract_questions_from_text(pdf_text, paper_id, title, subject, paper_details)
844
+ questions_index.extend(questions)
845
+
846
+ paper_info = f"{paper_details.get('year', 'Unknown')} {paper_details.get('series', 'Unknown')}"
847
+ if paper_details.get('variant') != 'Unknown':
848
+ paper_info += f" Variant {paper_details.get('variant')}"
849
+
850
+ status_msg += f"\nโœ… Extracted {len(questions)} questions from **{paper_info}**!"
851
+ status_msg += f"\n๐Ÿ“‹ Identified as: {subject} Paper {paper_details.get('paper_number', 'Unknown')}"
852
+
853
+ return status_msg, get_papers_list(), f"๐Ÿ“Š Total papers: {len(papers_storage)} | Total questions: {len(questions_index)}"
854
+
855
+ def get_papers_list():
856
+ """Get formatted list of all papers"""
857
+ if not papers_storage:
858
+ return "No papers yet."
859
+
860
+ result = []
861
+ for p in papers_storage:
862
+ paper_details = p.get('paper_details', {})
863
+ year = paper_details.get('year', 'Unknown')
864
+ series = paper_details.get('series', 'Unknown')
865
+ variant = paper_details.get('variant', 'Unknown')
866
+ paper_num = paper_details.get('paper_number', 'Unknown')
867
+
868
+ paper_info = f"{year} {series} - Paper {paper_num}"
869
+ if variant != 'Unknown':
870
+ paper_info += f" V{variant}"
871
+
872
+ insert_icon = '๐Ÿ–ผ๏ธ Insert' if p.get('has_insert') else ''
873
+ pdf_icon = '๐Ÿ“„ PDF' if p.get('has_pdf') else ''
874
+
875
+ result.append(f"**{p['title']}** ({p['subject']}) {pdf_icon} {insert_icon}\n{paper_info}\nโฐ {p['uploaded_at']}\n{p['content'][:120]}...\n{'โ”€'*60}")
876
+
877
+ return "\n".join(result)
878
+
879
+ # ---------- 13. Gradio UI ----------
880
+ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE Science Platform") as app:
881
+ gr.Markdown("""
882
+ # ๐Ÿ”ฌ IGCSE Science Learning Platform
883
+ Chemistry โš—๏ธ | Biology ๐Ÿงฌ
884
+ _Deep Understanding Through AI-Powered Learning_
885
+ """)
886
+
887
+ with gr.Tabs():
888
+ # โ”€โ”€โ”€โ”€โ”€ STUDENT PORTAL โ”€โ”€โ”€โ”€โ”€
889
+ with gr.Tab("๐Ÿ‘จโ€๐ŸŽ“ Student Portal"):
890
+ with gr.Tabs():
891
+ # AI TUTOR
892
+ with gr.Tab("๐Ÿค– AI Tutor - Deep Understanding"):
893
+ gr.Markdown("""### Chat with Your AI Science Tutor
894
+ *Focus on understanding WHY, not just memorizing facts*
895
+
896
+ **Tips for getting the most from your tutor:**
897
+ - Ask "why" and "how" questions
898
+ - Request step-by-step explanations
899
+ - Ask for real-world examples
900
+ - Challenge yourself with "what if" scenarios""")
901
+
902
+ with gr.Row():
903
+ subj = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
904
+ topc = gr.Dropdown(chemistry_topics, label="Topic (optional)", allow_custom_value=True)
905
+
906
+ def update_topics(s):
907
+ topics = {"Chemistry": chemistry_topics, "Biology": biology_topics}
908
+ return gr.Dropdown(choices=topics[s], value=None)
909
+ subj.change(update_topics, subj, topc)
910
+
911
+ chat = gr.Chatbot(height=500, show_label=False)
912
+ txt = gr.Textbox(placeholder="Ask anything... e.g., 'Why do ionic compounds conduct electricity when molten but not when solid?'", label="Message")
913
+ with gr.Row():
914
+ send = gr.Button("Send ๐Ÿ“ค", variant="primary")
915
+ clr = gr.Button("Clear ๐Ÿ—‘")
916
+
917
+ send.click(ai_tutor_chat, [txt, chat, subj, topc], chat)
918
+ txt.submit(ai_tutor_chat, [txt, chat, subj, topc], chat)
919
+ clr.click(clear_chat, outputs=chat)
920
+
921
+ # CONCEPT EXPLAINER
922
+ with gr.Tab("๐Ÿ’ก Concept Explainer"):
923
+ gr.Markdown("""### Deep Dive into Scientific Concepts
924
+ *Get comprehensive explanations that build real understanding*""")
925
+
926
+ with gr.Row():
927
+ ce_subj = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
928
+ ce_concept = gr.Textbox(label="Concept to Explain",
929
+ placeholder="e.g., 'covalent bonding', 'osmosis', 'enzyme action'")
930
+
931
+ ce_output = gr.Markdown(label="Explanation")
932
+ gr.Button("๐Ÿ” Explain Concept", variant="primary", size="lg").click(
933
+ explain_concept, [ce_subj, ce_concept], ce_output
934
+ )
935
+
936
+ # CALCULATION HELPER
937
+ with gr.Tab("๐Ÿงฎ Calculation Helper"):
938
+ gr.Markdown("""### Step-by-Step Problem Solving
939
+ *Understand the reasoning, not just the answer*""")
940
+
941
+ calc_subj = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
942
+ calc_problem = gr.Textbox(lines=4, label="Problem",
943
+ placeholder="e.g., 'Calculate the mass of calcium carbonate needed to produce 22g of carbon dioxide'")
944
+ calc_steps = gr.Checkbox(label="Show detailed steps", value=True)
945
+ calc_output = gr.Markdown(label="Solution")
946
+
947
+ gr.Button("โœ๏ธ Solve Problem", variant="primary", size="lg").click(
948
+ solve_calculation, [calc_subj, calc_problem, calc_steps], calc_output
949
+ )
950
+
951
+ # EXPERIMENT ANALYZER
952
+ with gr.Tab("๐Ÿ”ฌ Experiment Analyzer"):
953
+ gr.Markdown("""### Understand Scientific Investigations
954
+ *Connect practical work to theory*""")
955
+
956
+ exp_subj = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
957
+ exp_desc = gr.Textbox(lines=5, label="Experiment Description",
958
+ placeholder="Describe the experiment setup and procedure...")
959
+ exp_q = gr.Textbox(label="Specific Question (optional)",
960
+ placeholder="e.g., 'Why must we use excess acid in this experiment?'")
961
+ exp_output = gr.Markdown(label="Analysis")
962
+
963
+ gr.Button("๐Ÿ” Analyze Experiment", variant="primary", size="lg").click(
964
+ analyze_experiment, [exp_subj, exp_desc, exp_q], exp_output
965
+ )
966
+
967
+ # PAST PAPERS BROWSER
968
+ with gr.Tab("๐Ÿ“š Past Papers Browser"):
969
+ gr.Markdown("""### ๐ŸŽฏ Search Real Exam Questions by Topic
970
+ *Practice with actual IGCSE questions*""")
971
+
972
+ with gr.Row():
973
+ pp_subject = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
974
+ pp_topic = gr.Dropdown(chemistry_topics, label="Select Topic")
975
+
976
+ pp_subject.change(update_topics, pp_subject, pp_topic)
977
+
978
+ search_btn = gr.Button("๐Ÿ” Search Questions", variant="primary", size="lg")
979
+ questions_output = gr.Markdown(label="Questions Found", value="Select a topic and click Search")
980
+
981
+ search_btn.click(search_questions_by_topic, [pp_subject, pp_topic], questions_output)
982
+
983
+ gr.Markdown("---\n### ๐Ÿ“„ Browse All Papers")
984
+ browse_subject = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
985
+ papers_display = gr.Markdown(label="Available Papers")
986
+ gr.Button("๐Ÿ“– Show All Papers").click(view_papers_student, browse_subject, papers_display)
987
+
988
+ # PRACTICE QUESTIONS
989
+ with gr.Tab("โœ Practice Questions"):
990
+ gr.Markdown("""### Generate & Practice Exam Questions
991
+ *Focus on understanding, not just correct answers*""")
992
+
993
+ with gr.Row():
994
+ ps = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
995
+ pt = gr.Dropdown(chemistry_topics, label="Topic")
996
+ diff = gr.Radio(["Easy", "Medium", "Hard"], label="Difficulty", value="Medium")
997
+
998
+ ps.change(update_topics, ps, pt)
999
+
1000
+ q = gr.Textbox(label="๐Ÿ“ Question", lines=8, interactive=False)
1001
+ exp = gr.Textbox(label="Understanding Required & Expected Points", lines=6, interactive=False)
1002
+ mark = gr.Textbox(label="๐Ÿ“Š Mark Scheme", lines=5, interactive=False)
1003
+ ans = gr.Textbox(lines=12, label="โœ Your Answer",
1004
+ placeholder="Write your answer here. Focus on explaining your reasoning...")
1005
+ fb = gr.Textbox(lines=18, label="๐Ÿ“‹ Detailed Feedback", interactive=False)
1006
+
1007
+ with gr.Row():
1008
+ gr.Button("๐ŸŽฒ Generate Question", variant="primary").click(
1009
+ generate_question, [ps, pt, diff], [q, exp, mark]
1010
+ )
1011
+ gr.Button("โœ… Check Answer", variant="secondary").click(
1012
+ check_answer, [q, exp, ans, ps], fb
1013
+ )
1014
+
1015
+ # โ”€โ”€โ”€โ”€โ”€ ADMIN PANEL โ”€โ”€โ”€โ”€โ”€
1016
+ with gr.Tab("๐Ÿ” Admin Panel"):
1017
+ with gr.Column() as login_section:
1018
+ gr.Markdown("### ๐Ÿ” Admin Login")
1019
+ pwd = gr.Textbox(label="Password", type="password", placeholder="Enter admin password")
1020
+ login_btn = gr.Button("๐Ÿ”“ Login", variant="primary")
1021
+ login_status = gr.Textbox(label="Status", interactive=False)
1022
+
1023
+ with gr.Column(visible=False) as admin_section:
1024
+ gr.Markdown("""### ๐Ÿ“ค Upload Past Papers & Resources
1025
+
1026
+ **Instructions:**
1027
+ 1. **Title**: e.g., "Paper 2 Chemistry - June 2023"
1028
+ 2. **Subject**: Select Chemistry or Biology
1029
+ 3. **Content**: Add description, syllabus code (0620 Chemistry, 0610 Biology), or notes
1030
+ 4. **PDF**: Upload the actual past paper (questions will be auto-extracted)
1031
+ 5. **Insert**: Upload any accompanying insert/resource booklet
1032
+
1033
+ The AI will automatically:
1034
+ - Identify paper details (year, series, variant)
1035
+ - Extract all questions with topics
1036
+ - Index them for student search
1037
+ - Store insert materials for reference
1038
+ """)
1039
+
1040
+ with gr.Row():
1041
+ with gr.Column():
1042
+ t = gr.Textbox(label="๐Ÿ“‹ Title", placeholder="e.g., Paper 2 Chemistry - October/November 2023")
1043
+ s = gr.Radio(["Chemistry", "Biology"], label="Subject", value="Chemistry")
1044
+ c = gr.Textbox(lines=5, label="Content/Description",
1045
+ placeholder="Add notes, syllabus code (0620/0610), or instructions...")
1046
+ pdf = gr.File(label="๐Ÿ“„ Past Paper PDF (questions will be extracted)", file_types=[".pdf"])
1047
+ insert = gr.File(label="๐Ÿ–ผ๏ธ Insert/Resource Booklet (optional)",
1048
+ file_types=[".pdf", ".jpg", ".jpeg", ".png"])
1049
+
1050
+ up = gr.Button("โฌ† Upload Paper", variant="primary", size="lg")
1051
+ st = gr.Textbox(label="Upload Status", lines=4)
1052
+ stats = gr.Textbox(label="๐Ÿ“Š Database Statistics", value="๐Ÿ“Š Status: No papers uploaded yet")
1053
+
1054
+ with gr.Column():
1055
+ gr.Markdown("### ๐Ÿ“š All Uploaded Papers")
1056
+ lst = gr.Textbox(lines=26, label="Papers Database", value=get_papers_list(),
1057
+ interactive=False, show_label=False)
1058
+
1059
+ up.click(upload_paper, [t, s, c, pdf, insert], [st, lst, stats])
1060
+
1061
+ login_btn.click(verify_admin_password, [pwd], [admin_section, login_section, login_status])
1062
+
1063
+ gr.Markdown("""
1064
+ ---
1065
+ **System Status:** ๐ŸŸข Gemini AI (Primary) | ๐Ÿ”ต Cohere (Secondary) | ๐ŸŸข Z.ai (Tertiary) | ๐ŸŸฃ MiniMax (Fallback)
1066
+
1067
+ **Key Features:**
1068
+ - ๐Ÿง  **Deep Understanding Focus**: AI emphasizes WHY, not just WHAT
1069
+ - ๐ŸŽฏ Smart question extraction and topic-based search
1070
+ - ๐Ÿ–ผ๏ธ Insert/resource support for diagrams and data
1071
+ - ๐Ÿ” Comprehensive concept explanations
1072
+ - ๐Ÿงฎ Step-by-step calculation support
1073
+ - ๐Ÿ”ฌ Experiment analysis with theory connections
1074
+ - ๐Ÿค– Multi-AI fallback system for reliability
1075
+
1076
+ **Teaching Philosophy:**
1077
+ - Structure determines function
1078
+ - Understanding beats memorization
1079
+ - Connect concepts across topics
1080
+ - Apply knowledge to novel situations
1081
+ """)
1082
+
1083
+ app.launch()