AneelaFatima commited on
Commit
aeac200
·
verified ·
1 Parent(s): a63032e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +367 -113
app.py CHANGED
@@ -1,142 +1,396 @@
 
 
 
 
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
- # Initialize Hugging Face client (replace with your HF API key in secrets)
5
- client = InferenceClient(model="facebook/blenderbot-400M-distill")
6
-
7
- # ---------- Backend Functions ----------
8
-
9
- def generate_explanation(subject, topic, language, level):
10
- prompt = f"Explain the topic '{topic}' in {language} for a {level} level computer science student studying {subject}. Keep it step-by-step and clear."
11
- response = client.text_generation(prompt, max_new_tokens=400)
12
- return response
13
-
14
- def generate_resources(subject, topic, language, level):
15
- prompt = f"List some helpful study resources (websites, books, videos) for learning '{topic}' in {language}, at a {level} level in {subject}."
16
- response = client.text_generation(prompt, max_new_tokens=300)
17
- return response
18
-
19
- def generate_roadmap(subject, topic, language, level):
20
- prompt = f"Give a short roadmap to master '{topic}' in {subject}, in {language}, at {level} level."
21
- response = client.text_generation(prompt, max_new_tokens=300)
22
- return response
23
-
24
- def generate_quiz(subject, topic, language, level):
25
- prompt = f"Create a short 3-question multiple-choice quiz (with 4 options each) on '{topic}' in {subject}, in {language}, for {level} students. Format:\nQ1...\nA)\nB)\nC)\nD)\nCorrect: X"
26
- response = client.text_generation(prompt, max_new_tokens=400)
27
-
28
- questions = []
29
- correct_answers = []
30
-
31
- for block in response.split("Q")[1:]:
32
- lines = block.strip().split("\n")
33
- if len(lines) >= 6:
34
- q_text = "Q" + lines[0]
35
- options = lines[1:5]
36
- correct = None
37
- for l in lines:
38
- if "Correct" in l:
39
- correct = l.split(":")[-1].strip()
40
- questions.append((q_text, options))
41
- correct_answers.append(correct)
42
-
43
- return questions, correct_answers
44
-
45
- def display_quiz(subject, topic, language, level):
46
- questions, answers = generate_quiz(subject, topic, language, level)
47
- quiz_ui = []
48
- for i, (q, opts) in enumerate(questions):
49
- quiz_ui.append(gr.Markdown(f"**{q}**"))
50
- quiz_ui.append(
51
- gr.Radio(opts, label=f"Your Answer for {q}", interactive=True)
52
  )
53
- return quiz_ui, answers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- def evaluate_quiz(user_answers, correct_answers):
56
- score = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  details = []
58
- for ua, ca in zip(user_answers, correct_answers):
59
- if ua and ua.strip().startswith(ca):
60
- score += 1
61
- details.append(f"✅ Correct ({ua})")
62
- else:
63
- details.append(f"❌ Wrong (Your answer: {ua}, Correct: {ca})")
64
- result = f"Your Score: {score}/{len(correct_answers)}\n\n" + "\n".join(details)
65
- return result
66
-
67
-
68
- # ---------- UI Layout ----------
69
- with gr.Blocks(css=".gradio-container {background-color: #f2f2f2; color: #222;} h1 {color:#111;} h3 {color:#333;}") as demo:
70
-
71
- gr.Markdown(
72
- "<h1 style='text-align: center;'>🎓 AI Learning Tutor For Computer Science Students</h1>"
73
- "<h3 style='text-align: center;'>Pick a subject, enter a topic, then get a clear explanation, resources, a roadmap, and a quiz.</h3>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  )
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  with gr.Row():
77
- subject = gr.Textbox(label="Subject", placeholder="e.g. Operating Systems")
78
- topic = gr.Textbox(label="Topic", placeholder="e.g. CPU Scheduling")
79
- with gr.Row():
80
- language = gr.Dropdown(
81
- ["English", "Urdu", "Arabic", "French", "German", "Spanish"],
82
- label="Language",
83
- value="English",
84
- allow_custom_value=True
85
- )
86
- level = gr.Radio(["Beginner", "Intermediate", "Advanced"], label="Level", value="Beginner")
87
 
88
- # Explanation Section
89
- explanation_area = gr.Textbox(label="Explanation", interactive=False, lines=8)
90
- explain_btn = gr.Button("Generate Explanation")
 
 
 
91
 
92
- # Resources Section
93
- resources_area = gr.Textbox(label="Extra Resources", interactive=False, lines=6)
94
- resources_btn = gr.Button("Generate Resources")
 
 
95
 
96
- # Roadmap Section
97
- roadmap_area = gr.Textbox(label="Roadmap", interactive=False, lines=6)
98
- roadmap_btn = gr.Button("Generate Roadmap")
 
 
 
99
 
100
- # Quiz Section
101
- quiz_area = gr.Column()
102
- quiz_btn = gr.Button("Generate Quiz")
103
- result_btn = gr.Button("Display Result")
104
- result_area = gr.Textbox(label="Quiz Results", interactive=False, lines=6)
 
 
 
 
 
 
 
 
105
 
106
- state = gr.State()
 
 
 
 
 
 
107
 
108
- # ---------- Event Bindings ----------
109
- explain_btn.click(
110
- generate_explanation,
111
  inputs=[subject, topic, language, level],
112
- outputs=explanation_area
113
  )
114
 
115
- resources_btn.click(
116
- generate_resources,
117
  inputs=[subject, topic, language, level],
118
- outputs=resources_area
119
  )
120
 
121
- roadmap_btn.click(
122
- generate_roadmap,
123
  inputs=[subject, topic, language, level],
124
- outputs=roadmap_area
125
  )
126
 
127
- quiz_btn.click(
128
- display_quiz,
129
  inputs=[subject, topic, language, level],
130
- outputs=[quiz_area, state]
131
  )
132
 
133
- result_btn.click(
134
- evaluate_quiz,
135
- inputs=[quiz_area, state],
136
- outputs=result_area
137
  )
138
 
139
-
140
- # ---------- Launch ----------
141
  if __name__ == "__main__":
142
  demo.launch()
 
1
+ import os
2
+ import json
3
+ import re
4
+ from typing import List, Dict, Any, Tuple
5
+
6
  import gradio as gr
7
+ from groq import Groq
8
+
9
+ # -----------------------------
10
+ # Configuration
11
+ # -----------------------------
12
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "").strip()
13
+ client = Groq(api_key=GROQ_API_KEY)
14
+
15
+ LANG_OPTIONS = [
16
+ "English",
17
+ "Urdu",
18
+ "Mandarin Chinease",
19
+ "Hindi",
20
+ "Spanish",
21
+ "Standard Arabic",
22
+ "French",
23
+ "Bengali",
24
+ "Protaguese",
25
+ "Russian",
26
+ "Indonasion",
27
+ ]
28
+
29
+ LEVEL_OPTIONS = ["Beginner", "Intermediate", "Advanced"]
30
+
31
+
32
+ # -----------------------------
33
+ # Helpers
34
+ # -----------------------------
35
+ def generate_with_groq(prompt: str) -> str:
36
+ """
37
+ Call Groq chat completions with the specified model and return text content.
38
+ Includes basic error handling and a concise error message for the UI.
39
+ """
40
+ if not GROQ_API_KEY:
41
+ return "❌ Missing GROQ_API_KEY. Please set it as a secret/environment variable."
42
+
43
+ try:
44
+ response = client.chat.completions.create(
45
+ model="llama-3.1-8b-instant",
46
+ messages=[{"role": "user", "content": prompt}],
47
+ temperature=0.7,
48
+ max_tokens=500,
 
 
 
 
 
 
 
 
49
  )
50
+ return response.choices[0].message.content
51
+ except Exception as e:
52
+ return f"❌ API error: {e}"
53
+
54
+
55
+ def build_system_context(subject: str, topic: str, language: str, level: str) -> str:
56
+ return (
57
+ f"Subject: {subject}\n"
58
+ f"Topic: {topic}\n"
59
+ f"Language: {language}\n"
60
+ f"Student Level: {level}\n"
61
+ )
62
+
63
+
64
+ def prompt_explanation(subject: str, topic: str, language: str, level: str) -> str:
65
+ ctx = build_system_context(subject, topic, language, level)
66
+ return (
67
+ f"{ctx}\n"
68
+ "Task: Write a clear, friendly, step-by-step explanation of the topic."
69
+ " Use short paragraphs, numbered steps where helpful, and examples."
70
+ " Keep it concise but thorough. Reply in the specified language only."
71
+ )
72
+
73
+
74
+ def prompt_resources(subject: str, topic: str, language: str, level: str) -> str:
75
+ ctx = build_system_context(subject, topic, language, level)
76
+ return (
77
+ f"{ctx}\n"
78
+ "Task: Recommend at least 3 quality learning resources (mix of articles, videos, documentation). "
79
+ "Return as a markdown bulleted list. Each item must include a title, the type (Article/Video/Docs), "
80
+ "a one-line why it's useful, and a URL. Reply in the specified language only."
81
+ )
82
+
83
+
84
+ def prompt_roadmap(subject: str, topic: str, language: str, level: str) -> str:
85
+ ctx = build_system_context(subject, topic, language, level)
86
+ return (
87
+ f"{ctx}\n"
88
+ "Task: Produce a structured learning roadmap for this topic and level. "
89
+ "Organize into stages with bullet points, estimated effort, and key outcomes. "
90
+ "Add a short list of common mistakes to avoid. Reply in the specified language only."
91
+ )
92
+
93
+
94
+ def prompt_quiz(subject: str, topic: str, language: str, level: str) -> str:
95
+ ctx = build_system_context(subject, topic, language, level)
96
+ return (
97
+ f"{ctx}\n"
98
+ "Task: Create a short multiple-choice quiz with 3 to 5 questions. "
99
+ "Return STRICT JSON only with this schema:\n"
100
+ "{\n"
101
+ ' "questions": [\n'
102
+ ' {\n'
103
+ ' "question": "string",\n'
104
+ ' "options": ["A", "B", "C", "D"],\n'
105
+ ' "answer_index": 0\n'
106
+ " }\n"
107
+ " ]\n"
108
+ "}\n"
109
+ "Requirements:\n"
110
+ "- options length 3-5\n"
111
+ "- answer_index is an integer index into the options array\n"
112
+ "- No additional commentary or code fences\n"
113
+ f"- Write the question text and options in {language}."
114
+ )
115
+
116
 
117
+ def parse_quiz_json(text: str) -> Dict[str, Any]:
118
+ """
119
+ Extract and parse the JSON quiz from model output.
120
+ Tries to locate the first JSON-looking block if the response isn't pure JSON.
121
+ """
122
+ # Try direct JSON first
123
+ try:
124
+ parsed = json.loads(text)
125
+ if "questions" in parsed:
126
+ return parsed
127
+ except Exception:
128
+ pass
129
+
130
+ # Fallback: regex to find JSON block
131
+ match = re.search(r"\{(?:[^{}]|(?R))*\}", text, re.DOTALL)
132
+ if match:
133
+ try:
134
+ parsed = json.loads(match.group(0))
135
+ if "questions" in parsed:
136
+ return parsed
137
+ except Exception:
138
+ pass
139
+
140
+ # Final fallback
141
+ return {"questions": []}
142
+
143
+
144
+ def normalize_quiz(quiz: Dict[str, Any]) -> List[Dict[str, Any]]:
145
+ """
146
+ Ensure each question has required fields. Drop invalid ones.
147
+ """
148
+ cleaned = []
149
+ for q in quiz.get("questions", []):
150
+ question = q.get("question")
151
+ options = q.get("options", [])
152
+ answer_index = q.get("answer_index")
153
+ if (
154
+ isinstance(question, str)
155
+ and isinstance(options, list)
156
+ and 3 <= len(options) <= 5
157
+ and isinstance(answer_index, int)
158
+ and 0 <= answer_index < len(options)
159
+ ):
160
+ cleaned.append(
161
+ {
162
+ "question": question.strip(),
163
+ "options": [str(o).strip() for o in options],
164
+ "answer_index": answer_index,
165
+ }
166
+ )
167
+ return cleaned[:5] # at most 5
168
+
169
+
170
+ def evaluate_answers(
171
+ user_choices: List[int], quiz_data: List[Dict[str, Any]]
172
+ ) -> Tuple[str, str]:
173
+ """
174
+ Compute score and short feedback summary.
175
+ """
176
+ correct = 0
177
  details = []
178
+ for i, q in enumerate(quiz_data):
179
+ user_idx = user_choices[i] if i < len(user_choices) else None
180
+ ans_idx = q["answer_index"]
181
+ is_correct = (user_idx == ans_idx)
182
+ if is_correct:
183
+ correct += 1
184
+ # Build per-question line
185
+ chosen = (
186
+ f"{q['options'][user_idx]}"
187
+ if isinstance(user_idx, int) and 0 <= user_idx < len(q["options"])
188
+ else "No answer"
189
+ )
190
+ details.append(
191
+ f"Q{i+1}: {'✅ Correct' if is_correct else '❌ Incorrect'} | "
192
+ f"Your answer: {chosen} | Correct: {q['options'][ans_idx]}"
193
+ )
194
+
195
+ total = len(quiz_data)
196
+ score_text = f"Score: {correct} / {total}"
197
+ if total == 0:
198
+ return "No quiz generated yet.", ""
199
+ # Brief feedback
200
+ if correct == total and total > 0:
201
+ feedback = "Great job. You’ve mastered this set."
202
+ elif correct >= (total * 0.6):
203
+ feedback = "Good work. Review the missed questions and try again."
204
+ else:
205
+ feedback = "Keep practicing. Revisit the explanation and roadmap."
206
+
207
+ return score_text, feedback + "\n\n" + "\n".join(details)
208
+
209
+
210
+ # -----------------------------
211
+ # Gradio Callbacks
212
+ # -----------------------------
213
+ def on_generate_explanation(subject, topic, language, level):
214
+ prompt = prompt_explanation(subject, topic, language, level)
215
+ return generate_with_groq(prompt)
216
+
217
+
218
+ def on_generate_resources(subject, topic, language, level):
219
+ prompt = prompt_resources(subject, topic, language, level)
220
+ return generate_with_groq(prompt)
221
+
222
+
223
+ def on_generate_roadmap(subject, topic, language, level):
224
+ prompt = prompt_roadmap(subject, topic, language, level)
225
+ return generate_with_groq(prompt)
226
+
227
+
228
+ def on_generate_quiz(subject, topic, language, level):
229
+ raw = generate_with_groq(prompt_quiz(subject, topic, language, level))
230
+ quiz = normalize_quiz(parse_quiz_json(raw))
231
+
232
+ # Build updates for up to 5 radios and their labels
233
+ vis = [False] * 5
234
+ labels = [("Question", ["Option 1", "Option 2", "Option 3"])] * 5
235
+
236
+ for i, q in enumerate(quiz):
237
+ vis[i] = True
238
+ labels[i] = (f"Q{i+1}. {q['question']}", q["options"])
239
+
240
+ return (
241
+ quiz, # gr.State
242
+ gr.update(visible=vis[0], label=labels[0][0], choices=labels[0][1], value=None),
243
+ gr.update(visible=vis[1], label=labels[1][0], choices=labels[1][1], value=None),
244
+ gr.update(visible=vis[2], label=labels[2][0], choices=labels[2][1], value=None),
245
+ gr.update(visible=vis[3], label=labels[3][0], choices=labels[3][1], value=None),
246
+ gr.update(visible=vis[4], label=labels[4][0], choices=labels[4][1], value=None),
247
+ raw if not quiz else "Quiz generated. Select your answers below."
248
  )
249
 
250
+
251
+ def on_display_results(
252
+ quiz_state,
253
+ a1, a2, a3, a4, a5
254
+ ):
255
+ quiz = quiz_state or []
256
+ # Map selected option text back to index
257
+ selections = []
258
+ for i, q in enumerate(quiz):
259
+ # chosen label may be None
260
+ chosen = [a1, a2, a3, a4, a5][i]
261
+ if chosen is None:
262
+ selections.append(None)
263
+ continue
264
+ try:
265
+ idx = q["options"].index(chosen)
266
+ except ValueError:
267
+ idx = None
268
+ selections.append(idx)
269
+
270
+ score_text, feedback = evaluate_answers(selections, quiz)
271
+ return score_text, feedback
272
+
273
+
274
+ # -----------------------------
275
+ # UI
276
+ # -----------------------------
277
+ # Custom blue theme + card-like styling
278
+ CSS = """
279
+ :root {
280
+ --brand-blue: #1e40af; /* indigo-800 */
281
+ --brand-blue-600: #2563eb;
282
+ --card-bg: #f8fafc;
283
+ --border: #cbd5e1;
284
+ }
285
+
286
+ .gradio-container {max-width: 1200px !important}
287
+ #title h1 {color: var(--brand-blue); margin-bottom: 6px}
288
+ #subtitle {color:#334155; margin-top:0}
289
+
290
+ .card {
291
+ background: var(--card-bg);
292
+ border: 1px solid var(--border);
293
+ border-radius: 14px;
294
+ padding: 14px;
295
+ box-shadow: 0 2px 8px rgb(2 6 23 / 6%);
296
+ }
297
+
298
+ .btn-primary button {
299
+ background: var(--brand-blue-600) !important;
300
+ border-color: var(--brand-blue-600) !important;
301
+ color: white !important;
302
+ }
303
+
304
+ .section-title {
305
+ font-weight: 700; color: var(--brand-blue);
306
+ margin-bottom: 6px; font-size: 16px;
307
+ }
308
+ """
309
+
310
+ with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="blue")) as demo:
311
+ gr.Markdown("<div id='title'><h1>AI Study Tutor</h1><p id='subtitle'>Powered by Groq + Gradio</p></div>")
312
+
313
  with gr.Row():
314
+ with gr.Column(scale=1):
315
+ with gr.Group(elem_classes="card"):
316
+ gr.Markdown("### Inputs")
317
+ subject = gr.Textbox(label="Subject", placeholder="e.g., Mathematics")
318
+ topic = gr.Textbox(label="Topic", placeholder="e.g., Derivatives of Trigonometric Functions")
319
+ language = gr.Dropdown(LANG_OPTIONS, value="English", label="Language")
320
+ level = gr.Radio(LEVEL_OPTIONS, value="Beginner", label="Level")
 
 
 
321
 
322
+ with gr.Column(scale=2):
323
+ # Explanation
324
+ with gr.Group(elem_classes="card"):
325
+ gr.Markdown("<div class='section-title'>Generate Explanation</div>")
326
+ btn_explain = gr.Button("Generate Explanation", elem_classes="btn-primary")
327
+ explanation = gr.Markdown(label="Explanation", value="")
328
 
329
+ # Resources
330
+ with gr.Group(elem_classes="card"):
331
+ gr.Markdown("<div class='section-title'>Generate Resources</div>")
332
+ btn_resources = gr.Button("Generate Resources", elem_classes="btn-primary")
333
+ resources = gr.Markdown(label="Resources", value="")
334
 
335
+ with gr.Row():
336
+ with gr.Column():
337
+ with gr.Group(elem_classes="card"):
338
+ gr.Markdown("<div class='section-title'>Generate Roadmap</div>")
339
+ btn_roadmap = gr.Button("Generate Roadmap", elem_classes="btn-primary")
340
+ roadmap = gr.Markdown(label="Roadmap", value="")
341
 
342
+ with gr.Row():
343
+ with gr.Column():
344
+ with gr.Group(elem_classes="card"):
345
+ gr.Markdown("<div class='section-title'>Generate Quiz</div>")
346
+ btn_quiz = gr.Button("Generate Quiz", elem_classes="btn-primary")
347
+ quiz_info = gr.Markdown("Click the button to create a quiz.")
348
+ # Quiz state and up to 5 radios
349
+ quiz_state = gr.State([])
350
+ q1 = gr.Radio(label="Question 1", choices=[], visible=False, interactive=True)
351
+ q2 = gr.Radio(label="Question 2", choices=[], visible=False, interactive=True)
352
+ q3 = gr.Radio(label="Question 3", choices=[], visible=False, interactive=True)
353
+ q4 = gr.Radio(label="Question 4", choices=[], visible=False, interactive=True)
354
+ q5 = gr.Radio(label="Question 5", choices=[], visible=False, interactive=True)
355
 
356
+ with gr.Row():
357
+ with gr.Column():
358
+ with gr.Group(elem_classes="card"):
359
+ gr.Markdown("<div class='section-title'>Display Results</div>")
360
+ btn_results = gr.Button("Evaluate Answers", elem_classes="btn-primary")
361
+ score = gr.Markdown("Score will appear here.")
362
+ feedback = gr.Markdown("Feedback will appear here.")
363
 
364
+ # Events
365
+ btn_explain.click(
366
+ fn=on_generate_explanation,
367
  inputs=[subject, topic, language, level],
368
+ outputs=[explanation],
369
  )
370
 
371
+ btn_resources.click(
372
+ fn=on_generate_resources,
373
  inputs=[subject, topic, language, level],
374
+ outputs=[resources],
375
  )
376
 
377
+ btn_roadmap.click(
378
+ fn=on_generate_roadmap,
379
  inputs=[subject, topic, language, level],
380
+ outputs=[roadmap],
381
  )
382
 
383
+ btn_quiz.click(
384
+ fn=on_generate_quiz,
385
  inputs=[subject, topic, language, level],
386
+ outputs=[quiz_state, q1, q2, q3, q4, q5, quiz_info],
387
  )
388
 
389
+ btn_results.click(
390
+ fn=on_display_results,
391
+ inputs=[quiz_state, q1, q2, q3, q4, q5],
392
+ outputs=[score, feedback],
393
  )
394
 
 
 
395
  if __name__ == "__main__":
396
  demo.launch()