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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -90
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # --------------------------------------------------------------
2
  # IGCSE/GCSE Language Platform – 100% free on Hugging Face Spaces
3
- # Model: openai/gpt-oss-20b:groq (via HF Inference API)
4
  # --------------------------------------------------------------
5
 
6
  import os
@@ -18,7 +18,7 @@ if not HF_TOKEN:
18
 
19
  client = InferenceClient(api_key=os.environ["HF_TOKEN"])
20
 
21
- MODEL = "openai/gpt-oss-20b:groq"
22
 
23
  # ---------- 2. Global storage ----------
24
  papers_storage = []
@@ -48,10 +48,15 @@ efl_topics = [
48
  # ---------- 4. Helper: call the model ----------
49
  def call_model(messages: list, system: str = "", max_tokens=1000):
50
  try:
 
 
 
 
 
 
51
  resp = client.chat.completions.create(
52
  model=MODEL,
53
- messages=[{"role": "system", "content": system}] + messages
54
- if system else messages,
55
  max_tokens=max_tokens,
56
  temperature=0.7,
57
  )
@@ -79,12 +84,15 @@ def ai_tutor_chat(message, history, subject, topic, level):
79
  return history
80
 
81
  system = f"""You are an expert {'French' if subject == 'French' else 'EFL'} {level} tutor.
82
- Focus on {topic or 'any topic'}. Be encouraging and clear. Adjust difficulty for {level} level."""
 
 
 
83
  msgs = [{"role": "user" if i % 2 == 0 else "assistant", "content": turn}
84
  for pair in history for i, turn in enumerate(pair) if turn]
85
  msgs.append({"role": "user", "content": message})
86
 
87
- bot = call_model(msgs, system)
88
  history.append((message, bot))
89
  return history
90
 
@@ -97,20 +105,23 @@ def translate_text(text, direction):
97
  return "Enter text first."
98
  src = "English" if direction == "English β†’ French" else "French"
99
  tgt = "French" if direction == "English β†’ French" else "English"
100
- prompt = f"Translate from {src} to {tgt}. Only the translation:\n\n{text}"
101
- return call_model([{"role": "user", "content": prompt}], max_tokens=500)
 
102
 
103
  # ---------- 8. Dictionary ----------
104
  def dictionary_lookup(word):
105
  if not word.strip():
106
  return "Enter a French word."
107
- prompt = f"""Give a clear French dictionary entry for "{word}":
 
108
  - Part of speech
109
  - English meaning(s)
110
  - Gender (if noun)
111
- - 2 example sentences (French + English)
112
- - Common phrases"""
113
- return call_model([{"role": "user", "content": prompt}], max_tokens=800)
 
114
 
115
  # ---------- 9. Practice Questions (Enhanced with PDF context) ----------
116
  def generate_question(subject, topic, level):
@@ -122,50 +133,74 @@ def generate_question(subject, topic, level):
122
  for paper_id, content in pdf_content_storage.items():
123
  paper = next((p for p in papers_storage if p['id'] == paper_id), None)
124
  if paper and paper['subject'].lower() == subject.lower() and paper['level'] == level:
125
- # Use first 2000 chars of PDF content
126
- pdf_context += f"\n\nReference material from {paper['title']}:\n{content[:2000]}"
127
 
128
- prompt = f"""Create ONE {level} {subject} exam question on "{topic}".
129
- {"Use this reference material to ensure authenticity:" + pdf_context if pdf_context else ""}
130
-
131
- Return valid JSON with exactly these keys:
132
- {{"question": "...", "expectedAnswer": "short description of expected answer", "markScheme": "brief marking criteria"}}"""
 
 
 
 
 
 
 
133
 
134
- txt = call_model([{"role": "user", "content": prompt}], max_tokens=800)
135
  try:
136
- data = json.loads(txt.replace("```json", "").replace("```", "").strip())
 
 
137
  return data["question"], data.get("expectedAnswer", ""), data.get("markScheme", "")
138
- except Exception:
139
- return txt, "", ""
140
 
141
  def check_answer(question, expected, user_answer, subject, level):
142
  if not user_answer.strip():
143
  return "Write your answer first!"
144
- prompt = f"""You are a {level} {subject} examiner.
 
 
 
145
  Question: {question}
146
- Expected: {expected}
147
- Student answer: {user_answer}
148
 
149
- Reply with JSON:
150
- {{"isCorrect": true/false, "score": 0-100, "feedback": "...", "improvements": "..."}}"""
151
- txt = call_model([{"role": "user", "content": prompt}], max_tokens=600)
 
 
 
 
 
 
152
  try:
153
- fb = json.loads(txt.replace("```json", "").replace("```", "").strip())
154
- return f"""Score: {fb['score']}%
155
- Feedback: {fb['feedback']}
156
- Improvements: {fb['improvements']}"""
 
 
 
 
 
 
 
 
157
  except Exception:
158
  return txt
159
 
160
  # ---------- 10. Admin – Past Papers ----------
161
  def verify_admin_password(password):
162
  if password == ADMIN_PASSWORD:
163
- return gr.update(visible=True), gr.update(visible=False), "Access granted!"
164
- return gr.update(visible=False), gr.update(visible=True), "Incorrect password!"
165
 
166
  def upload_paper(title, subject, level, content, pdf_file):
167
  if not all([title, subject, level, content]):
168
- return "Fill all required fields!", get_papers_list()
169
 
170
  paper_id = len(papers_storage) + 1
171
 
@@ -175,7 +210,7 @@ def upload_paper(title, subject, level, content, pdf_file):
175
  pdf_text = extract_text_from_pdf(pdf_file)
176
  if pdf_text and not pdf_text.startswith("Error"):
177
  pdf_content_storage[paper_id] = pdf_text
178
- content += f"\n\n[PDF content extracted: {len(pdf_text)} characters]"
179
 
180
  papers_storage.append({
181
  "id": paper_id,
@@ -186,13 +221,13 @@ def upload_paper(title, subject, level, content, pdf_file):
186
  "has_pdf": bool(pdf_text and not pdf_text.startswith("Error")),
187
  "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
188
  })
189
- return "Uploaded successfully!", get_papers_list()
190
 
191
  def get_papers_list():
192
  if not papers_storage:
193
  return "No papers yet."
194
  return "\n".join(
195
- f"**{p['title']}** ({p['subject'].upper()} - {p['level']}) {'πŸ“„ PDF' if p.get('has_pdf') else ''}\n{p['uploaded_at']}\n{p['content'][:120]}...\n{'─'*40}"
196
  for p in papers_storage
197
  )
198
 
@@ -200,22 +235,26 @@ def view_papers_student(subject, level):
200
  filtered = [p for p in papers_storage
201
  if p["subject"] == subject.lower() and p["level"] == level]
202
  if not filtered:
203
- return f"No {subject} {level} papers."
204
  return "\n".join(
205
- f"**{p['title']}** {'πŸ“„ Has PDF reference' if p.get('has_pdf') else ''}\n{p['content']}\n{'═'*50}"
206
  for p in filtered
207
  )
208
 
209
  # ---------- 11. Gradio UI ----------
210
  with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
211
- gr.Markdown("# IGCSE/GCSE Language Platform\nπŸŽ“ Free AI Tutor, Translator, Dictionary & Past Papers with PDF Support")
 
 
 
 
212
 
213
  with gr.Tabs():
214
  # ───── STUDENT ─────
215
- with gr.Tab("Student Portal"):
216
  with gr.Tabs():
217
- with gr.Tab("AI Tutor"):
218
- gr.Markdown("### Chat with your AI tutor")
219
  with gr.Row():
220
  subj = gr.Radio(["French", "EFL"], label="Subject", value="French")
221
  lvl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
@@ -225,57 +264,60 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
225
  return gr.Dropdown(choices=french_topics if s == "French" else efl_topics, value=None)
226
  subj.change(upd_topics, subj, topc)
227
 
228
- chat = gr.Chatbot(height=450)
229
- txt = gr.Textbox(placeholder="Ask anything…", label="Message", scale=4)
230
  with gr.Row():
231
- send = gr.Button("Send")
232
- clr = gr.Button("Clear")
233
  send.click(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
234
  txt.submit(ai_tutor_chat, [txt, chat, subj, topc, lvl], chat)
235
  clr.click(clear_chat, outputs=chat)
236
 
237
- with gr.Tab("Translator"):
 
238
  dir_ = gr.Radio(["English β†’ French", "French β†’ English"], label="Direction", value="English β†’ French")
239
- inp = gr.Textbox(lines=4, label="Text")
240
- out = gr.Textbox(lines=4, label="Translation")
241
- gr.Button("Translate").click(translate_text, [inp, dir_], out)
242
-
243
- with gr.Tab("Dictionary"):
244
- w = gr.Textbox(placeholder="French word", label="Word")
245
- o = gr.Textbox(lines=12, label="Definition")
246
- gr.Button("Lookup").click(dictionary_lookup, w, o)
247
-
248
- with gr.Tab("Practice"):
249
- gr.Markdown("### Generate exam-style questions (powered by uploaded past papers)")
 
250
  with gr.Row():
251
  ps = gr.Radio(["French", "EFL"], label="Subject", value="French")
252
  pl = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
253
  pt = gr.Dropdown(french_topics, label="Topic")
254
  ps.change(upd_topics, ps, pt)
255
 
256
- q = gr.Textbox(label="Question", lines=3)
257
- exp = gr.Textbox(label="Expected Answer", lines=2, visible=False)
258
- mark = gr.Textbox(label="Mark Scheme", lines=2)
259
- ans = gr.Textbox(lines=5, label="Your answer")
260
- fb = gr.Textbox(lines=8, label="Feedback")
261
 
262
- gr.Button("Generate Question").click(generate_question, [ps, pt, pl], [q, exp, mark])
263
- gr.Button("Check Answer").click(check_answer, [q, exp, ans, ps, pl], fb)
 
264
 
265
- with gr.Tab("Past Papers"):
266
- gr.Markdown("### View past papers and reference materials")
267
  with gr.Row():
268
  psb = gr.Radio(["French", "EFL"], label="Subject", value="French")
269
  plb = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
270
- pd = gr.Textbox(lines=20, label="Papers")
271
- gr.Button("Show Papers").click(view_papers_student, [psb, plb], pd)
272
 
273
  # ───── ADMIN ─────
274
- with gr.Tab("Admin Panel"):
275
  with gr.Column() as login_section:
276
- gr.Markdown("### πŸ”’ Admin Login")
277
  pwd = gr.Textbox(label="Password", type="password", placeholder="Enter admin password")
278
- login_btn = gr.Button("Login", variant="primary")
279
  login_status = gr.Textbox(label="Status", interactive=False)
280
 
281
  with gr.Column(visible=False) as admin_section:
@@ -286,13 +328,13 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
286
  with gr.Row():
287
  s = gr.Radio(["French", "EFL"], label="Subject", value="French")
288
  lv = gr.Radio(["IGCSE", "GCSE"], label="Level", value="IGCSE")
289
- c = gr.Textbox(lines=4, label="Description/Notes")
290
- pdf = gr.File(label="Upload PDF (optional)", file_types=[".pdf"])
291
- gr.Markdown("*PDF will be analyzed to generate authentic exam questions*")
292
- up = gr.Button("Upload", variant="primary")
293
  st = gr.Textbox(label="Status")
294
  with gr.Column():
295
- lst = gr.Textbox(lines=20, label="All papers", value=get_papers_list())
296
  up.click(upload_paper, [t, s, lv, c, pdf], [st, lst])
297
 
298
  login_btn.click(
@@ -303,17 +345,23 @@ with gr.Blocks(theme=gr.themes.Soft(), title="IGCSE/GCSE Platform") as app:
303
 
304
  gr.Markdown("""
305
  ---
306
- **How to run:**
307
- 1. Fork this Space
308
- 2. Settings β†’ Secrets β†’ Add `HF_TOKEN` (your Hugging Face token)
309
- 3. Install dependencies: `gradio`, `huggingface_hub`, `PyPDF2`
310
- 4. Restart β†’ Done!
 
 
 
 
 
 
 
 
 
 
311
 
312
- **Features:**
313
- - βœ… IGCSE & GCSE support
314
- - βœ… PDF upload for past papers
315
- - βœ… AI generates questions based on uploaded PDFs
316
- - βœ… Password-protected admin panel (@mikaelJ46)
317
  """)
318
 
319
  app.launch()
 
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
 
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 = []
 
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 = []
53
+ if system:
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
  )
 
84
  return history
85
 
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]
93
  msgs.append({"role": "user", "content": message})
94
 
95
+ bot = call_model(msgs, system, max_tokens=1500)
96
  history.append((message, bot))
97
  return history
98
 
 
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) ----------
127
  def generate_question(subject, topic, level):
 
133
  for paper_id, content in pdf_content_storage.items():
134
  paper = next((p for p in papers_storage if p['id'] == paper_id), None)
135
  if paper and paper['subject'].lower() == subject.lower() and paper['level'] == 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()
156
+ data = json.loads(clean_txt)
157
  return data["question"], data.get("expectedAnswer", ""), data.get("markScheme", "")
158
+ except Exception as e:
159
+ return txt, "", f"Error parsing response: {e}"
160
 
161
  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
 
170
+ Expected answer criteria: {expected}
171
+
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
194
 
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
  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
  "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
  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():
253
  # ───── STUDENT ─────
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
  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:
 
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
 
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()