mikaelJ46 commited on
Commit
e4378b3
Β·
verified Β·
1 Parent(s): b1611b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -103
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # --------------------------------------------------------------
2
  # IGCSE/GCSE Language Platform – 100% free on Hugging Face Spaces
3
- # Model: deepseek-ai/DeepSeek-V3.2-Exp:novita (via HF Inference API)
4
  # --------------------------------------------------------------
5
 
6
  import os
@@ -18,7 +18,9 @@ if not HF_TOKEN:
18
 
19
  client = InferenceClient(api_key=os.environ["HF_TOKEN"])
20
 
21
- MODEL = "deepseek-ai/DeepSeek-V3.2-Exp:novita"
 
 
22
 
23
  # ---------- 2. Global storage ----------
24
  papers_storage = []
@@ -45,8 +47,9 @@ efl_topics = [
45
  "Listening Comprehension"
46
  ]
47
 
48
- # ---------- 4. Helper: call the model ----------
49
  def call_model(messages: list, system: str = "", max_tokens=1000):
 
50
  try:
51
  # Prepare messages with system prompt if provided
52
  formatted_messages = []
@@ -54,13 +57,26 @@ def call_model(messages: list, system: str = "", max_tokens=1000):
54
  formatted_messages.append({"role": "system", "content": system})
55
  formatted_messages.extend(messages)
56
 
57
- resp = client.chat.completions.create(
58
- model=MODEL,
59
- messages=formatted_messages,
60
- max_tokens=max_tokens,
61
- temperature=0.7,
62
- )
63
- return resp.choices[0].message.content.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  except Exception as e:
65
  return f"Error: {e}"
66
 
@@ -86,7 +102,8 @@ def ai_tutor_chat(message, history, subject, topic, level):
86
  system = f"""You are an expert {'French' if subject == 'French' else 'EFL'} {level} tutor.
87
  Focus on {topic or 'any topic'}. Be encouraging, clear, and pedagogical.
88
  Adjust difficulty and explanations appropriately for {level} level students.
89
- Provide detailed explanations with examples when needed."""
 
90
 
91
  msgs = [{"role": "user" if i % 2 == 0 else "assistant", "content": turn}
92
  for pair in history for i, turn in enumerate(pair) if turn]
@@ -105,22 +122,23 @@ def translate_text(text, direction):
105
  return "Enter text first."
106
  src = "English" if direction == "English β†’ French" else "French"
107
  tgt = "French" if direction == "English β†’ French" else "English"
108
- system = f"You are a professional translator. Provide accurate, natural translations from {src} to {tgt}."
109
- prompt = f"Translate this text to {tgt}. Provide only the translation without any explanations:\n\n{text}"
110
  return call_model([{"role": "user", "content": prompt}], system, max_tokens=800)
111
 
112
  # ---------- 8. Dictionary ----------
113
  def dictionary_lookup(word):
114
  if not word.strip():
115
  return "Enter a French word."
116
- system = "You are a French language dictionary expert. Provide comprehensive, accurate definitions."
117
  prompt = f"""Provide a detailed French dictionary entry for "{word}":
118
- - Part of speech
119
- - English meaning(s)
120
- - Gender (if noun)
121
- - 3 example sentences (French with English translations)
122
  - Common phrases and idioms using this word
123
- - Any important usage notes"""
 
124
  return call_model([{"role": "user", "content": prompt}], system, max_tokens=1200)
125
 
126
  # ---------- 9. Practice Questions (Enhanced with PDF context) ----------
@@ -136,20 +154,21 @@ def generate_question(subject, topic, level):
136
  # Use first 3000 chars of PDF content for better context
137
  pdf_context += f"\n\nReference material from {paper['title']}:\n{content[:3000]}"
138
 
139
- system = f"You are an expert {level} {subject} examiner. Create authentic, challenging exam questions."
140
- prompt = f"""Create ONE {level} {subject} exam question on the topic: "{topic}".
141
- {"Base the question style and difficulty on this reference material:" + pdf_context if pdf_context else ""}
142
 
143
  The question should:
144
- - Be appropriate for {level} level
145
- - Test understanding, not just memorization
146
- - Include clear instructions
147
  - Be answerable in 5-10 minutes
 
148
 
149
- Return ONLY valid JSON with exactly these keys (no markdown formatting):
150
- {{"question": "the complete question text", "expectedAnswer": "detailed description of what a good answer should include", "markScheme": "clear marking criteria with point allocations"}}"""
151
 
152
- txt = call_model([{"role": "user", "content": prompt}], system, max_tokens=1000)
153
  try:
154
  # Clean up potential markdown formatting
155
  clean_txt = txt.replace("```json", "").replace("```", "").strip()
@@ -162,8 +181,8 @@ def check_answer(question, expected, user_answer, subject, level):
162
  if not user_answer.strip():
163
  return "Write your answer first!"
164
 
165
- system = f"You are a {level} {subject} examiner. Provide constructive, detailed feedback."
166
- prompt = f"""Evaluate this student's answer:
167
 
168
  Question: {question}
169
 
@@ -172,22 +191,29 @@ Expected answer criteria: {expected}
172
  Student's answer:
173
  {user_answer}
174
 
175
- Provide a detailed evaluation in JSON format (no markdown):
176
- {{"isCorrect": true/false, "score": 0-100, "feedback": "detailed feedback on what was done well", "improvements": "specific suggestions for improvement", "strengths": "what the student did correctly"}}"""
 
 
 
 
 
 
 
177
 
178
- txt = call_model([{"role": "user", "content": prompt}], system, max_tokens=800)
179
  try:
180
  clean_txt = txt.replace("```json", "").replace("```", "").strip()
181
  fb = json.loads(clean_txt)
182
  return f"""βœ… Score: {fb['score']}%
183
 
184
- πŸ“ Feedback:
185
  {fb['feedback']}
186
 
187
- πŸ’ͺ Strengths:
188
  {fb.get('strengths', 'Good effort!')}
189
 
190
- 🎯 Areas for Improvement:
191
  {fb['improvements']}"""
192
  except Exception:
193
  return txt
@@ -195,12 +221,12 @@ Provide a detailed evaluation in JSON format (no markdown):
195
  # ---------- 10. Admin – Past Papers ----------
196
  def verify_admin_password(password):
197
  if password == ADMIN_PASSWORD:
198
- return gr.update(visible=True), gr.update(visible=False), "βœ… Access granted!"
199
- return gr.update(visible=False), gr.update(visible=True), "❌ Incorrect password!"
200
 
201
  def upload_paper(title, subject, level, content, pdf_file):
202
  if not all([title, subject, level, content]):
203
- return "❌ Fill all required fields!", get_papers_list()
204
 
205
  paper_id = len(papers_storage) + 1
206
 
@@ -210,7 +236,9 @@ def upload_paper(title, subject, level, content, pdf_file):
210
  pdf_text = extract_text_from_pdf(pdf_file)
211
  if pdf_text and not pdf_text.startswith("Error"):
212
  pdf_content_storage[paper_id] = pdf_text
213
- content += f"\n\n[πŸ“„ PDF content extracted: {len(pdf_text)} characters]"
 
 
214
 
215
  papers_storage.append({
216
  "id": paper_id,
@@ -221,13 +249,13 @@ def upload_paper(title, subject, level, content, pdf_file):
221
  "has_pdf": bool(pdf_text and not pdf_text.startswith("Error")),
222
  "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
223
  })
224
- return "βœ… Uploaded successfully!", get_papers_list()
225
 
226
  def get_papers_list():
227
  if not papers_storage:
228
- return "No papers yet."
229
  return "\n".join(
230
- f"**{p['title']}** ({p['subject'].upper()} - {p['level']}) {'πŸ“„ PDF' if p.get('has_pdf') else ''}\nπŸ“… {p['uploaded_at']}\n{p['content'][:120]}...\n{'─'*60}"
231
  for p in papers_storage
232
  )
233
 
@@ -235,18 +263,18 @@ def view_papers_student(subject, level):
235
  filtered = [p for p in papers_storage
236
  if p["subject"] == subject.lower() and p["level"] == level]
237
  if not filtered:
238
- return f"No {subject} {level} papers available yet."
239
  return "\n".join(
240
- f"**{p['title']}** {'πŸ“„ Has PDF reference material' if p.get('has_pdf') else ''}\n{p['content']}\n{'═'*60}"
241
  for p in filtered
242
  )
243
 
244
  # ---------- 11. Gradio UI ----------
245
  with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
246
  gr.Markdown("""
247
- # πŸŽ“ IGCSE/GCSE Language Platform
248
- ### Powered by DeepSeek AI - Advanced Language Learning Assistant
249
- Free AI Tutor, Translator, Dictionary & Past Papers with PDF Support
250
  """)
251
 
252
  with gr.Tabs():
@@ -254,7 +282,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
254
  with gr.Tab("πŸ“š Student Portal"):
255
  with gr.Tabs():
256
  with gr.Tab("πŸ€– AI Tutor"):
257
- gr.Markdown("### Chat with your AI tutor - Get personalized help!")
258
  with gr.Row():
259
  subj = gr.Radio(["French", "EFL"], label="Subject", value="French")
260
  lvl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
@@ -264,77 +292,93 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
264
  return gr.Dropdown(choices=french_topics if s == "French" else efl_topics, value=None)
265
  subj.change(upd_topics, subj, topc)
266
 
267
- chat = gr.Chatbot(height=450, show_label=False)
268
- txt = gr.Textbox(placeholder="Ask anything about your studies...", label="Message", scale=4)
269
  with gr.Row():
270
  send = gr.Button("Send πŸ“€", variant="primary")
271
- clr = gr.Button("Clear πŸ—‘οΈ")
272
  send.click(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
273
  txt.submit(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
274
  clr.click(clear_chat, outputs=chat)
275
 
276
  with gr.Tab("πŸ”„ Translator"):
277
- gr.Markdown("### Accurate English ⟷ French Translation")
278
- dir_ = gr.Radio(["English β†’ French", "French β†’ English"], label="Direction", value="English β†’ French")
279
- inp = gr.Textbox(lines=5, label="Input Text", placeholder="Enter text to translate...")
280
- out = gr.Textbox(lines=5, label="Translation")
281
- gr.Button("Translate πŸ”„", variant="primary").click(translate_text, [inp, dir_], out)
282
 
283
  with gr.Tab("πŸ“– Dictionary"):
284
- gr.Markdown("### French Dictionary Lookup")
285
- w = gr.Textbox(placeholder="Enter a French word...", label="Word")
286
- o = gr.Textbox(lines=15, label="Definition & Examples")
287
- gr.Button("Lookup πŸ”", variant="primary").click(dictionary_lookup, w, o)
288
-
289
- with gr.Tab("✍️ Practice"):
290
- gr.Markdown("### Generate Exam-Style Questions\n*Questions are generated based on uploaded past papers for authenticity*")
 
 
291
  with gr.Row():
292
  ps = gr.Radio(["French", "EFL"], label="Subject", value="French")
293
  pl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
294
- pt = gr.Dropdown(french_topics, label="Topic")
295
  ps.change(upd_topics, ps, pt)
296
 
297
- q = gr.Textbox(label="πŸ“ Question", lines=4)
298
- exp = gr.Textbox(label="Expected Answer (Hidden)", lines=2, visible=False)
299
- mark = gr.Textbox(label="πŸ“Š Mark Scheme", lines=3)
300
- ans = gr.Textbox(lines=6, label="✍️ Your Answer", placeholder="Type your answer here...")
301
- fb = gr.Textbox(lines=10, label="πŸ“‹ Detailed Feedback")
 
 
 
302
 
303
  with gr.Row():
304
- gr.Button("Generate Question 🎲", variant="primary").click(generate_question, [ps, pt, pl], [q, exp, mark])
305
- gr.Button("Check Answer βœ…", variant="secondary").click(check_answer, [q, exp, ans, ps, pl], fb)
306
-
307
- with gr.Tab("πŸ“„ Past Papers"):
308
- gr.Markdown("### View Past Papers & Reference Materials")
 
 
 
 
 
309
  with gr.Row():
310
  psb = gr.Radio(["French", "EFL"], label="Subject", value="French")
311
  plb = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
312
- pd = gr.Textbox(lines=20, label="Available Papers", show_label=False)
313
- gr.Button("Show Papers πŸ“š", variant="primary").click(view_papers_student, [psb, plb], pd)
314
 
315
  # ───── ADMIN ─────
316
  with gr.Tab("πŸ” Admin Panel"):
317
  with gr.Column() as login_section:
318
- gr.Markdown("### πŸ”’ Admin Login Required")
319
- pwd = gr.Textbox(label="Password", type="password", placeholder="Enter admin password")
320
- login_btn = gr.Button("Login πŸ”“", variant="primary")
321
- login_status = gr.Textbox(label="Status", interactive=False)
 
 
322
 
323
  with gr.Column(visible=False) as admin_section:
324
- gr.Markdown("### πŸ“€ Upload Past Papers (with optional PDF)")
 
 
 
325
  with gr.Row():
326
  with gr.Column():
327
- t = gr.Textbox(label="Title", placeholder="e.g., Paper 1 Reading - June 2023")
328
  with gr.Row():
329
  s = gr.Radio(["French", "EFL"], label="Subject", value="French")
330
  lv = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
331
- c = gr.Textbox(lines=5, label="Description/Notes")
332
- pdf = gr.File(label="πŸ“Ž Upload PDF (optional)", file_types=[".pdf"])
333
- gr.Markdown("*πŸ“„ PDF content will be extracted and used to generate authentic exam-style questions*")
334
- up = gr.Button("Upload πŸ“€", variant="primary")
335
- st = gr.Textbox(label="Status")
336
  with gr.Column():
337
- lst = gr.Textbox(lines=22, label="πŸ“š All Uploaded Papers", value=get_papers_list())
 
338
  up.click(upload_paper, [t, s, lv, c, pdf], [st, lst])
339
 
340
  login_btn.click(
@@ -345,23 +389,32 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
345
 
346
  gr.Markdown("""
347
  ---
348
- ### πŸš€ How to Deploy on Hugging Face Spaces:
349
- 1. **Fork/Create** this Space
350
- 2. **Settings** β†’ **Secrets** β†’ Add `HF_TOKEN` (your Hugging Face token)
351
- 3. Upload `requirements.txt` with: `gradio`, `huggingface_hub`, `PyPDF2`
352
- 4. **Restart** the Space β†’ You're live! πŸŽ‰
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
- ### ✨ Key Features:
355
- - βœ… **IGCSE & GCSE** support for both levels
356
- - βœ… **PDF Upload** for authentic past papers
357
- - βœ… **AI-Powered** question generation using DeepSeek V3.2
358
- - βœ… **Intelligent Feedback** on practice answers
359
- - βœ… **Password Protection** for admin panel
360
- - βœ… **Translator & Dictionary** tools included
361
 
362
- **Admin Password:** `@mikaelJ46`
 
 
363
 
364
- *Powered by DeepSeek V3.2 Exp via Hugging Face Inference API*
365
  """)
366
 
367
  app.launch()
 
1
  # --------------------------------------------------------------
2
  # IGCSE/GCSE Language Platform – 100% free on Hugging Face Spaces
3
+ # Model: MiniMaxAI/MiniMax-M2:novita (Primary) + DeepSeek V3.2 (Backup)
4
  # --------------------------------------------------------------
5
 
6
  import os
 
18
 
19
  client = InferenceClient(api_key=os.environ["HF_TOKEN"])
20
 
21
+ # Primary and backup models
22
+ PRIMARY_MODEL = "MiniMaxAI/MiniMax-M2:novita"
23
+ BACKUP_MODEL = "deepseek-ai/DeepSeek-V3.2-Exp:novita"
24
 
25
  # ---------- 2. Global storage ----------
26
  papers_storage = []
 
47
  "Listening Comprehension"
48
  ]
49
 
50
+ # ---------- 4. Helper: call the model with fallback ----------
51
  def call_model(messages: list, system: str = "", max_tokens=1000):
52
+ """Call primary model with automatic fallback to backup"""
53
  try:
54
  # Prepare messages with system prompt if provided
55
  formatted_messages = []
 
57
  formatted_messages.append({"role": "system", "content": system})
58
  formatted_messages.extend(messages)
59
 
60
+ # Try primary model first
61
+ try:
62
+ resp = client.chat.completions.create(
63
+ model=PRIMARY_MODEL,
64
+ messages=formatted_messages,
65
+ max_tokens=max_tokens,
66
+ temperature=0.7,
67
+ )
68
+ return resp.choices[0].message.content.strip()
69
+ except Exception as primary_error:
70
+ # Fallback to backup model
71
+ print(f"Primary model failed, using backup: {primary_error}")
72
+ resp = client.chat.completions.create(
73
+ model=BACKUP_MODEL,
74
+ messages=formatted_messages,
75
+ max_tokens=max_tokens,
76
+ temperature=0.7,
77
+ )
78
+ return resp.choices[0].message.content.strip()
79
+
80
  except Exception as e:
81
  return f"Error: {e}"
82
 
 
102
  system = f"""You are an expert {'French' if subject == 'French' else 'EFL'} {level} tutor.
103
  Focus on {topic or 'any topic'}. Be encouraging, clear, and pedagogical.
104
  Adjust difficulty and explanations appropriately for {level} level students.
105
+ Provide detailed explanations with examples when needed.
106
+ Use a friendly, supportive tone to help students learn effectively."""
107
 
108
  msgs = [{"role": "user" if i % 2 == 0 else "assistant", "content": turn}
109
  for pair in history for i, turn in enumerate(pair) if turn]
 
122
  return "Enter text first."
123
  src = "English" if direction == "English β†’ French" else "French"
124
  tgt = "French" if direction == "English β†’ French" else "English"
125
+ system = f"You are a professional translator specializing in {src} to {tgt} translation. Provide accurate, natural, and contextually appropriate translations."
126
+ prompt = f"Translate this text to {tgt}. Provide only the translation without any explanations or additional commentary:\n\n{text}"
127
  return call_model([{"role": "user", "content": prompt}], system, max_tokens=800)
128
 
129
  # ---------- 8. Dictionary ----------
130
  def dictionary_lookup(word):
131
  if not word.strip():
132
  return "Enter a French word."
133
+ system = "You are a French language dictionary expert with deep knowledge of French vocabulary, grammar, and usage. Provide comprehensive, accurate definitions."
134
  prompt = f"""Provide a detailed French dictionary entry for "{word}":
135
+ - Part of speech (noun, verb, adjective, etc.)
136
+ - Gender (if noun: masculine/feminine)
137
+ - English meaning(s) and translations
138
+ - 3 example sentences in French with English translations
139
  - Common phrases and idioms using this word
140
+ - Any important usage notes or context
141
+ - Related words or derivatives"""
142
  return call_model([{"role": "user", "content": prompt}], system, max_tokens=1200)
143
 
144
  # ---------- 9. Practice Questions (Enhanced with PDF context) ----------
 
154
  # Use first 3000 chars of PDF content for better context
155
  pdf_context += f"\n\nReference material from {paper['title']}:\n{content[:3000]}"
156
 
157
+ system = f"You are an expert {level} {subject} examiner with years of experience creating authentic exam questions. You understand exam board requirements and student needs."
158
+ prompt = f"""Create ONE high-quality {level} {subject} exam question on the topic: "{topic}".
159
+ {"Base the question style, difficulty level, and format on this reference material from actual past papers:" + pdf_context if pdf_context else "Create an authentic exam-style question appropriate for {level} level."}
160
 
161
  The question should:
162
+ - Be appropriate for {level} level students
163
+ - Test understanding and application, not just memorization
164
+ - Include clear, unambiguous instructions
165
  - Be answerable in 5-10 minutes
166
+ - Follow standard {level} exam format and conventions
167
 
168
+ Return ONLY valid JSON with exactly these keys (no markdown formatting, no extra text):
169
+ {{"question": "the complete question text with all instructions", "expectedAnswer": "detailed description of what a good answer should include and key points to cover", "markScheme": "clear marking criteria with point allocations (e.g., 2 marks for..., 3 marks for...)"}}"""
170
 
171
+ txt = call_model([{"role": "user", "content": prompt}], system, max_tokens=1200)
172
  try:
173
  # Clean up potential markdown formatting
174
  clean_txt = txt.replace("```json", "").replace("```", "").strip()
 
181
  if not user_answer.strip():
182
  return "Write your answer first!"
183
 
184
+ system = f"You are a {level} {subject} examiner with expertise in providing constructive, detailed feedback that helps students improve. Be fair, encouraging, and specific."
185
+ prompt = f"""Evaluate this student's answer professionally and constructively:
186
 
187
  Question: {question}
188
 
 
191
  Student's answer:
192
  {user_answer}
193
 
194
+ Provide a comprehensive evaluation considering:
195
+ - Accuracy and completeness
196
+ - Use of appropriate terminology
197
+ - Structure and clarity
198
+ - Relevance to the question
199
+ - Grammar and language quality (for language subjects)
200
+
201
+ Return your evaluation in JSON format (no markdown):
202
+ {{"isCorrect": true/false, "score": 0-100, "feedback": "detailed feedback on what was done well and what could be improved", "improvements": "specific, actionable suggestions for how to improve this answer", "strengths": "highlight what the student did correctly and well"}}"""
203
 
204
+ txt = call_model([{"role": "user", "content": prompt}], system, max_tokens=1000)
205
  try:
206
  clean_txt = txt.replace("```json", "").replace("```", "").strip()
207
  fb = json.loads(clean_txt)
208
  return f"""βœ… Score: {fb['score']}%
209
 
210
+ πŸ“ Detailed Feedback:
211
  {fb['feedback']}
212
 
213
+ πŸ’ͺ Your Strengths:
214
  {fb.get('strengths', 'Good effort!')}
215
 
216
+ 🎯 How to Improve:
217
  {fb['improvements']}"""
218
  except Exception:
219
  return txt
 
221
  # ---------- 10. Admin – Past Papers ----------
222
  def verify_admin_password(password):
223
  if password == ADMIN_PASSWORD:
224
+ return gr.update(visible=True), gr.update(visible=False), "βœ… Access granted! Welcome, Admin."
225
+ return gr.update(visible=False), gr.update(visible=True), "❌ Incorrect password. Please try again."
226
 
227
  def upload_paper(title, subject, level, content, pdf_file):
228
  if not all([title, subject, level, content]):
229
+ return "❌ Please fill all required fields!", get_papers_list()
230
 
231
  paper_id = len(papers_storage) + 1
232
 
 
236
  pdf_text = extract_text_from_pdf(pdf_file)
237
  if pdf_text and not pdf_text.startswith("Error"):
238
  pdf_content_storage[paper_id] = pdf_text
239
+ content += f"\n\n[πŸ“„ PDF content extracted successfully: {len(pdf_text)} characters]"
240
+ else:
241
+ content += f"\n\n[⚠️ PDF upload issue: {pdf_text}]"
242
 
243
  papers_storage.append({
244
  "id": paper_id,
 
249
  "has_pdf": bool(pdf_text and not pdf_text.startswith("Error")),
250
  "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
251
  })
252
+ return "βœ… Paper uploaded successfully!", get_papers_list()
253
 
254
  def get_papers_list():
255
  if not papers_storage:
256
+ return "No papers uploaded yet. Upload your first past paper to get started!"
257
  return "\n".join(
258
+ f"**{p['title']}** ({p['subject'].upper()} - {p['level']}) {'πŸ“„ PDF Available' if p.get('has_pdf') else 'πŸ“ Text Only'}\nπŸ“… Uploaded: {p['uploaded_at']}\nπŸ“– {p['content'][:120]}...\n{'─'*60}"
259
  for p in papers_storage
260
  )
261
 
 
263
  filtered = [p for p in papers_storage
264
  if p["subject"] == subject.lower() and p["level"] == level]
265
  if not filtered:
266
+ return f"πŸ“­ No {subject} {level} papers available yet.\n\nCheck back later or ask your teacher to upload some past papers!"
267
  return "\n".join(
268
+ f"**{p['title']}** {'πŸ“„ Includes PDF reference material' if p.get('has_pdf') else ''}\nπŸ“… {p['uploaded_at']}\n\n{p['content']}\n\n{'═'*60}"
269
  for p in filtered
270
  )
271
 
272
  # ---------- 11. Gradio UI ----------
273
  with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
274
  gr.Markdown("""
275
+ # πŸŽ“ IGCSE/GCSE Language Learning Platform
276
+ ### Powered by MiniMax-M2 AI - Your Personal Language Tutor
277
+ 🌟 Free AI Tutor | πŸ”„ Translator | πŸ“– Dictionary | πŸ“š Past Papers with PDF Support
278
  """)
279
 
280
  with gr.Tabs():
 
282
  with gr.Tab("πŸ“š Student Portal"):
283
  with gr.Tabs():
284
  with gr.Tab("πŸ€– AI Tutor"):
285
+ gr.Markdown("### Chat with Your AI Tutor\n*Get personalized help, explanations, and guidance for your studies*")
286
  with gr.Row():
287
  subj = gr.Radio(["French", "EFL"], label="Subject", value="French")
288
  lvl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
 
292
  return gr.Dropdown(choices=french_topics if s == "French" else efl_topics, value=None)
293
  subj.change(upd_topics, subj, topc)
294
 
295
+ chat = gr.Chatbot(height=450, show_label=False, avatar_images=(None, "πŸ€–"))
296
+ txt = gr.Textbox(placeholder="Ask me anything about your studies... e.g., 'Explain the passΓ© composΓ©' or 'Help me with essay writing'", label="Your Message", scale=4)
297
  with gr.Row():
298
  send = gr.Button("Send πŸ“€", variant="primary")
299
+ clr = gr.Button("Clear Chat πŸ—‘οΈ")
300
  send.click(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
301
  txt.submit(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
302
  clr.click(clear_chat, outputs=chat)
303
 
304
  with gr.Tab("πŸ”„ Translator"):
305
+ gr.Markdown("### Professional English ⟷ French Translation\n*Accurate, natural translations powered by AI*")
306
+ dir_ = gr.Radio(["English β†’ French", "French β†’ English"], label="Translation Direction", value="English β†’ French")
307
+ inp = gr.Textbox(lines=6, label="Input Text", placeholder="Enter the text you want to translate...")
308
+ out = gr.Textbox(lines=6, label="Translation Result")
309
+ gr.Button("Translate πŸ”„", variant="primary", size="lg").click(translate_text, [inp, dir_], out)
310
 
311
  with gr.Tab("πŸ“– Dictionary"):
312
+ gr.Markdown("### Comprehensive French Dictionary\n*Detailed definitions, examples, and usage notes*")
313
+ w = gr.Textbox(placeholder="Enter a French word to look up...", label="French Word")
314
+ o = gr.Textbox(lines=16, label="Dictionary Entry")
315
+ gr.Button("Look Up πŸ”", variant="primary", size="lg").click(dictionary_lookup, w, o)
316
+
317
+ with gr.Tab("✍️ Practice Questions"):
318
+ gr.Markdown("""### Generate & Practice Exam Questions
319
+ *AI-generated questions based on uploaded past papers for authentic exam preparation*
320
+ """)
321
  with gr.Row():
322
  ps = gr.Radio(["French", "EFL"], label="Subject", value="French")
323
  pl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
324
+ pt = gr.Dropdown(french_topics, label="Select Topic", interactive=True)
325
  ps.change(upd_topics, ps, pt)
326
 
327
+ gr.Markdown("---")
328
+ q = gr.Textbox(label="πŸ“ Exam Question", lines=5, interactive=False)
329
+ exp = gr.Textbox(label="Expected Answer (Hidden from student)", lines=2, visible=False)
330
+ mark = gr.Textbox(label="πŸ“Š Mark Scheme", lines=3, interactive=False)
331
+
332
+ gr.Markdown("**Your Answer:**")
333
+ ans = gr.Textbox(lines=8, label="Type your answer here", placeholder="Write your complete answer to the question above...")
334
+ fb = gr.Textbox(lines=12, label="πŸ“‹ Detailed Feedback & Score", interactive=False)
335
 
336
  with gr.Row():
337
+ gen_btn = gr.Button("🎲 Generate New Question", variant="primary", size="lg")
338
+ check_btn = gr.Button("βœ… Check My Answer", variant="secondary", size="lg")
339
+
340
+ gen_btn.click(generate_question, [ps, pt, pl], [q, exp, mark])
341
+ check_btn.click(check_answer, [q, exp, ans, ps, pl], fb)
342
+
343
+ with gr.Tab("πŸ“„ Past Papers Library"):
344
+ gr.Markdown("""### Access Past Papers & Reference Materials
345
+ *Browse uploaded past papers organized by subject and level*
346
+ """)
347
  with gr.Row():
348
  psb = gr.Radio(["French", "EFL"], label="Subject", value="French")
349
  plb = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
350
+ pd = gr.Textbox(lines=22, label="Available Papers", show_label=False, interactive=False)
351
+ gr.Button("πŸ“š Show Papers", variant="primary", size="lg").click(view_papers_student, [psb, plb], pd)
352
 
353
  # ───── ADMIN ─────
354
  with gr.Tab("πŸ” Admin Panel"):
355
  with gr.Column() as login_section:
356
+ gr.Markdown("""### πŸ”’ Admin Access Required
357
+ This section is for teachers and administrators only.
358
+ """)
359
+ pwd = gr.Textbox(label="Admin Password", type="password", placeholder="Enter admin password")
360
+ login_btn = gr.Button("πŸ”“ Login", variant="primary", size="lg")
361
+ login_status = gr.Textbox(label="Login Status", interactive=False)
362
 
363
  with gr.Column(visible=False) as admin_section:
364
+ gr.Markdown("""### πŸ“€ Upload Past Papers
365
+ Upload past examination papers to help students practice with authentic materials.
366
+ PDF files will be automatically processed to generate exam-style questions.
367
+ """)
368
  with gr.Row():
369
  with gr.Column():
370
+ t = gr.Textbox(label="Paper Title", placeholder="e.g., IGCSE French Paper 1 Reading - June 2023")
371
  with gr.Row():
372
  s = gr.Radio(["French", "EFL"], label="Subject", value="French")
373
  lv = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
374
+ c = gr.Textbox(lines=6, label="Description/Notes", placeholder="Add any relevant notes about this paper...")
375
+ pdf = gr.File(label="πŸ“Ž Upload PDF File (Optional)", file_types=[".pdf"])
376
+ gr.Markdown("*πŸ’‘ Tip: Upload PDF files for better question generation. The AI will analyze the PDF content.*")
377
+ up = gr.Button("πŸ“€ Upload Paper", variant="primary", size="lg")
378
+ st = gr.Textbox(label="Upload Status")
379
  with gr.Column():
380
+ gr.Markdown("**πŸ“š All Uploaded Papers:**")
381
+ lst = gr.Textbox(lines=24, label="Papers Database", value=get_papers_list(), interactive=False)
382
  up.click(upload_paper, [t, s, lv, c, pdf], [st, lst])
383
 
384
  login_btn.click(
 
389
 
390
  gr.Markdown("""
391
  ---
392
+ ### πŸš€ Deployment Guide for Hugging Face Spaces:
393
+
394
+ **Quick Setup:**
395
+ 1. **Create/Fork** this Space on Hugging Face
396
+ 2. **Settings** β†’ **Repository Secrets** β†’ Add `HF_TOKEN` (Get token from [HF Settings](https://huggingface.co/settings/tokens))
397
+ 3. Upload `requirements.txt` containing: `gradio`, `huggingface_hub`, `PyPDF2`
398
+ 4. **Restart** the Space β†’ Your platform is live! πŸŽ‰
399
+
400
+ ### ✨ Platform Features:
401
+ | Feature | Description |
402
+ |---------|-------------|
403
+ | πŸŽ“ **Dual Certification** | Supports both IGCSE and GCSE levels |
404
+ | πŸ€– **AI Tutor** | Powered by MiniMax-M2 for intelligent tutoring |
405
+ | πŸ“„ **PDF Processing** | Upload PDFs for authentic question generation |
406
+ | πŸ”„ **Smart Fallback** | Automatic backup to DeepSeek V3.2 if needed |
407
+ | ✍️ **Practice Mode** | AI-generated questions with detailed feedback |
408
+ | πŸ” **Secure Admin** | Password-protected content management |
409
+ | πŸ†“ **100% Free** | No API costs, runs on HF Spaces |
410
 
411
+ **Admin Credentials:** Password = `@mikaelJ46`
 
 
 
 
 
 
412
 
413
+ **Models Used:**
414
+ - **Primary:** MiniMax-M2 (Latest multilingual model)
415
+ - **Backup:** DeepSeek V3.2 Exp (Automatic fallback)
416
 
417
+ *Built with ❀️ for language learners everywhere*
418
  """)
419
 
420
  app.launch()