Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# --------------------------------------------------------------
|
| 2 |
# IGCSE/GCSE Language Platform β 100% free on Hugging Face Spaces
|
| 3 |
-
# Model:
|
| 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 = "
|
| 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=
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 101 |
-
|
|
|
|
| 102 |
|
| 103 |
# ---------- 8. Dictionary ----------
|
| 104 |
def dictionary_lookup(word):
|
| 105 |
if not word.strip():
|
| 106 |
return "Enter a French word."
|
| 107 |
-
|
|
|
|
| 108 |
- Part of speech
|
| 109 |
- English meaning(s)
|
| 110 |
- Gender (if noun)
|
| 111 |
-
-
|
| 112 |
-
- Common phrases
|
| 113 |
-
|
|
|
|
| 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
|
| 126 |
-
pdf_context += f"\n\nReference material from {paper['title']}:\n{content[:
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
-
txt = call_model([{"role": "user", "content": prompt}], max_tokens=
|
| 135 |
try:
|
| 136 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 145 |
Question: {question}
|
| 146 |
-
Expected: {expected}
|
| 147 |
-
Student answer: {user_answer}
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
try:
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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{'β'*
|
| 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{'β'*
|
| 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("
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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=
|
| 240 |
-
out = gr.Textbox(lines=
|
| 241 |
-
gr.Button("Translate").click(translate_text, [inp, dir_], out)
|
| 242 |
-
|
| 243 |
-
with gr.Tab("Dictionary"):
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
gr.
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
| 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=
|
| 257 |
-
exp = gr.Textbox(label="Expected Answer", lines=2, visible=False)
|
| 258 |
-
mark = gr.Textbox(label="Mark Scheme", lines=
|
| 259 |
-
ans = gr.Textbox(lines=
|
| 260 |
-
fb = gr.Textbox(lines=
|
| 261 |
|
| 262 |
-
gr.
|
| 263 |
-
|
|
|
|
| 264 |
|
| 265 |
-
with gr.Tab("Past Papers"):
|
| 266 |
-
gr.Markdown("### View
|
| 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=
|
| 290 |
-
pdf = gr.File(label="Upload PDF (optional)", file_types=[".pdf"])
|
| 291 |
-
gr.Markdown("*PDF will be
|
| 292 |
-
up = gr.Button("Upload", variant="primary")
|
| 293 |
st = gr.Textbox(label="Status")
|
| 294 |
with gr.Column():
|
| 295 |
-
lst = gr.Textbox(lines=
|
| 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 |
-
|
| 307 |
-
1. Fork this Space
|
| 308 |
-
2. Settings β Secrets β Add `HF_TOKEN` (your Hugging Face token)
|
| 309 |
-
3.
|
| 310 |
-
4. Restart β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
-
**
|
| 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()
|