Samanfatima563474 commited on
Commit
10e6593
·
verified ·
1 Parent(s): 97e1fa3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -205
app.py CHANGED
@@ -38,6 +38,75 @@ client = openai.OpenAI(
38
  api_key=api_key,
39
  )
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  ### --- HELPER & AI FUNCTIONS --- ###
43
 
@@ -63,55 +132,11 @@ def make_api_call(system_prompt, user_prompt, timeout=120.0):
63
  except Exception as e:
64
  return {"success": False, "error": f"An API error occurred: {e}"}
65
 
66
- def generate_dynamic_curriculum(topic):
67
- """
68
- Asks the AI to generate a full curriculum and practice problems as a JSON object.
69
- """
70
- system_prompt = """
71
- You are an expert Python programming instructor and curriculum designer. Your task is to generate a complete, interactive learning module based on a user-provided topic. You MUST respond with ONLY a valid JSON object and nothing else. Do not include any introductory text, markdown formatting like ```json, or any explanations outside of the JSON structure.
72
-
73
- The JSON object must have two top-level keys: "curriculum" and "practice_problems".
74
-
75
- 1. **curriculum**: This must be a JSON list of 2 to 3 lesson objects. Each lesson object must have:
76
- - "module": A string with the name of the overall topic (e.g., "Mastering Dictionaries").
77
- - "title": A string for the specific lesson title (e.g., "Lesson 1: What is a Dictionary?").
78
- - "explanation": A concise, simple explanation of the concept (1-3 sentences).
79
- - "check": A JSON object containing:
80
- - "type": The string "mcq".
81
- - "question": A string with a clear question.
82
- - "options": A JSON list of 3 string options.
83
- - "answer": A string that exactly matches one of the options.
84
-
85
- 2. **practice_problems**: This must be a JSON list containing exactly ONE practice problem object. This object must have:
86
- - "title": A string (e.g., "Practice: Word Frequency Counter").
87
- - "problem_statement": A clear problem description.
88
- - "starter_code": A string of Python code for the user to start with.
89
- - "check_type": The string "variable".
90
- - "check_variable": The name of the variable to check for the final answer.
91
- - "expected_result": The correct final value of the check_variable (can be a number, string, list, or dictionary).
92
- - "background_knowledge": A brief hint about the overall approach.
93
- """
94
- user_prompt = f"Generate a learning module for the Python topic: '{topic}'"
95
-
96
  result = make_api_call(system_prompt, user_prompt)
97
-
98
- if result["success"]:
99
- if not result["content"] or not result["content"].strip():
100
- return {"success": False, "error": "The AI returned an empty response. This might be a temporary issue. Please try again."}
101
- try:
102
- json_string = result["content"].strip()
103
- if json_string.startswith("```json"):
104
- json_string = json_string[7:-3].strip()
105
-
106
- data = json.loads(json_string)
107
- if "curriculum" in data and "practice_problems" in data:
108
- return {"success": True, "data": data}
109
- else:
110
- return {"success": False, "error": "AI response was missing the required 'curriculum' or 'practice_problems' keys."}
111
- except json.JSONDecodeError as e:
112
- return {"success": False, "error": f"AI returned invalid JSON. Error: {e}\n\nRaw response received:\n{result['content']}"}
113
- else:
114
- return {"success": False, "error": result["error"]}
115
 
116
 
117
  def get_socratic_hint(problem, user_code, actual_result, error_message=None):
@@ -142,91 +167,68 @@ def get_code_coaching(problem, user_code):
142
  return result["content"] if result["success"] else f"Error getting coaching feedback: {result['error']}"
143
 
144
 
145
- ### --- GRADIO APP WITH DYNAMIC TUTOR --- ###
146
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
147
- time_str = "Thursday, August 21, 2025 at 09:30 PM PKT"
148
 
149
- gr.Markdown(f"# Welcome to the AI Python Master\n*Current Location: Jhang, Punjab, Pakistan. Current Time: {time_str}*")
150
-
151
- # --- STATE MANAGEMENT ---
152
- dynamic_curriculum = gr.State([])
153
- dynamic_practice_problems = gr.State([])
154
- lesson_index = gr.State(0)
155
- practice_attempts = gr.State(0)
156
-
157
- # --- UI for Topic Selection ---
158
- with gr.Column(visible=True) as topic_selection_ui:
159
- gr.Markdown("## What Python concept do you want to master today?")
160
- topic_input = gr.Textbox(label="Enter a Topic", placeholder="e.g., Python Lists, Dictionaries, For Loops, etc.")
161
- generate_lesson_btn = gr.Button("Generate Lesson Plan")
162
- generation_status = gr.Markdown("")
163
-
164
- # --- UI for Dynamic Learning ---
165
- with gr.Column(visible=False) as learning_ui:
166
- # --- Learn Section ---
167
- progress_bar = gr.Markdown("")
168
- lesson_progress = gr.Markdown("")
169
- explanation_box = gr.Markdown("")
170
- with gr.Column() as check_ui:
171
- check_question = gr.Markdown("")
172
- mcq_input = gr.Radio(label="Select your answer")
173
- submit_button = gr.Button("Submit Answer")
174
- lesson_feedback_box = gr.Markdown("")
175
- next_button = gr.Button("➡️ Continue", visible=False)
176
-
177
- # --- Practice Section ---
178
- with gr.Column(visible=False) as practice_ui:
179
- gr.Markdown("--- \n ## Practice Arena")
 
 
 
 
 
 
 
180
  with gr.Row():
181
  with gr.Column(scale=2):
182
- practice_problem_statement = gr.Markdown("")
183
- practice_code_input = gr.Code(language="python", label="Your Code")
184
  with gr.Column(scale=1):
 
185
  coach_feedback_box = gr.Markdown(visible=False)
186
-
187
- submit_practice_button = gr.Button("Run Code")
188
  hint_button = gr.Button("🤔 Get a Hint", visible=False)
189
  coach_button = gr.Button("🤖 Coach Me: Improve My Code", visible=False)
190
  practice_feedback_box = gr.Markdown("")
191
 
192
- success_message = gr.Markdown("🎉 **Congratulations! You have finished this module!** 🎉", visible=False)
193
- start_new_topic_btn = gr.Button("Learn Another Topic", visible=False)
194
-
195
- # --- LOGIC FOR DYNAMIC TUTOR ---
196
- def generate_and_start_lesson(topic, progress=gr.Progress()):
197
- progress(0, desc="Asking the AI to design your lesson... This may take a moment.")
198
-
199
- result = generate_dynamic_curriculum(topic)
200
 
201
- if not result["success"]:
202
- return {
203
- topic_selection_ui: gr.update(visible=True),
204
- generation_status: gr.update(visible=True, value=f"❌ **Error:** {result['error']}"),
205
- learning_ui: gr.update(visible=False)
206
- }
207
-
208
- new_curriculum = result["data"]["curriculum"]
209
- new_practice = result["data"]["practice_problems"]
210
-
211
- first_lesson_ui = update_ui_for_lesson(0, new_curriculum)
212
-
213
- return {
214
- dynamic_curriculum: new_curriculum,
215
- dynamic_practice_problems: new_practice,
216
- lesson_index: 0,
217
- practice_attempts: 0,
218
- learning_ui: gr.update(visible=True),
219
- topic_selection_ui: gr.update(visible=False),
220
- generation_status: gr.update(visible=False),
221
- **first_lesson_ui
222
- }
223
-
224
- def update_ui_for_lesson(index, current_curriculum):
225
- lesson = current_curriculum[index]
226
- check_data = lesson.get("check", {}) # Safer access
227
-
228
- total_lessons = len(current_curriculum)
229
- completed_lessons = index
230
  percentage = int((completed_lessons / total_lessons) * 100)
231
  bar_length = 30
232
  filled_length = int(bar_length * percentage / 100)
@@ -234,57 +236,83 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
234
  progress_text = f"**Progress:** {bar} {percentage}% ({completed_lessons}/{total_lessons} Lessons)"
235
 
236
  return {
237
- lesson_index: index,
238
- lesson_progress: f"### Module: {lesson.get('module', 'Module')} - Lesson {index + 1} / {total_lessons}",
239
- explanation_box: lesson.get('explanation', ''),
240
- check_ui: gr.update(visible=True),
241
- check_question: f"**Question:** {check_data.get('question', '')}",
242
- mcq_input: gr.update(choices=check_data.get('options', []), value=None),
243
- lesson_feedback_box: "",
244
- next_button: gr.update(visible=False),
245
  progress_bar: progress_text
246
  }
247
 
248
- def process_answer(index, mcq_answer, current_curriculum):
249
- lesson = current_curriculum[index]
250
- check_data = lesson.get("check", {})
251
- is_correct = str(mcq_answer).strip().lower() == str(check_data.get('answer', '')).strip().lower()
252
  if is_correct:
253
- return { lesson_feedback_box: "✅ **Correct!**", check_ui: gr.update(visible=False), next_button: gr.update(visible=True) }
254
  else:
255
- return { lesson_feedback_box: "❌ Not quite. Try again.", check_ui: gr.update(visible=True), next_button: gr.update(visible=False) }
256
-
257
- def on_next_lesson(index, current_curriculum, current_practice):
258
- new_index = index + 1
259
- if new_index < len(current_curriculum):
260
- return update_ui_for_lesson(new_index, current_curriculum)
 
 
 
 
 
261
  else:
262
- # End of lessons, show practice problem
263
- practice_problem = current_practice[0]
264
- total_lessons = len(current_curriculum)
265
  bar = '█' * 30
266
  progress_text = f"**Progress:** {bar} 100% ({total_lessons}/{total_lessons} Lessons)"
267
-
268
  return {
269
- lesson_progress: "### All Lessons Complete! Now for the Practice Arena.",
270
- explanation_box: "",
271
- check_ui: gr.update(visible=False),
272
- next_button: gr.update(visible=False),
273
- practice_ui: gr.update(visible=True),
274
- practice_problem_statement: practice_problem["problem_statement"],
275
- practice_code_input: practice_problem["starter_code"],
276
- progress_bar: progress_text,
277
- lesson_index: new_index
278
  }
279
-
280
- def process_practice_answer(user_code, current_practice):
281
- problem = current_practice[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  result = execute_practice_code(user_code)
283
 
 
284
  attempts = 0
 
285
  if not result['success']:
286
  return {
287
- practice_feedback_box: f"❌ **Code Error:**\n\n```\n{result['error']}\n```",
288
  hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
289
  practice_attempts: attempts
290
  }
@@ -293,7 +321,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
293
  expected_value = problem['expected_result']
294
  actual_value = result['scope'].get(var_name, f"Variable '{var_name}' not found!")
295
 
296
- if str(actual_value) == str(expected_value):
297
  return {
298
  practice_feedback_box: f"✅ **Success!** The variable `{var_name}` has the correct value. Great job! Now, let the AI Code Coach show you how to improve it.",
299
  hint_button: gr.update(visible=False), coach_button: gr.update(visible=True),
@@ -305,11 +333,9 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
305
  hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
306
  practice_attempts: attempts
307
  }
308
-
309
- def provide_hint(user_code, current_practice, attempts):
310
  new_attempts = attempts + 1
311
- problem = current_practice[0]
312
-
313
  if new_attempts > MAX_HINTS:
314
  return {
315
  practice_feedback_box: f"**Hint Limit Reached ({MAX_HINTS}/{MAX_HINTS}).**",
@@ -317,6 +343,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
317
  practice_attempts: new_attempts
318
  }
319
 
 
320
  result = execute_practice_code(user_code)
321
 
322
  if new_attempts == 1 and result['success']:
@@ -333,66 +360,49 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
333
  practice_attempts: new_attempts
334
  }
335
 
336
- def coach_code(user_code, current_practice):
337
- problem = current_practice[0]
338
  coaching_text = get_code_coaching(problem, user_code)
339
  return {
340
  coach_feedback_box: gr.update(visible=True, value=coaching_text),
341
- coach_button: gr.update(interactive=False),
342
- success_message: gr.update(visible=True),
343
- start_new_topic_btn: gr.update(visible=True)
344
- }
345
-
346
- def reset_to_start():
347
- return {
348
- topic_selection_ui: gr.update(visible=True),
349
- learning_ui: gr.update(visible=False),
350
- practice_ui: gr.update(visible=False),
351
- success_message: gr.update(visible=False),
352
- start_new_topic_btn: gr.update(visible=False),
353
- coach_feedback_box: gr.update(visible=False, value=""),
354
- coach_button: gr.update(visible=False, interactive=True),
355
- hint_button: gr.update(visible=False),
356
- practice_feedback_box: "",
357
- generation_status: ""
358
  }
359
 
360
  # --- EVENT LISTENERS ---
361
- generate_lesson_btn.click(
362
- fn=generate_and_start_lesson,
363
- inputs=[topic_input],
364
- outputs=[topic_selection_ui, generation_status, dynamic_curriculum, dynamic_practice_problems, lesson_index, practice_attempts, learning_ui, lesson_progress, explanation_box, check_ui, check_question, mcq_input, lesson_feedback_box, next_button, progress_bar]
365
- )
366
- submit_button.click(
367
- fn=process_answer,
368
- inputs=[lesson_index, mcq_input, dynamic_curriculum],
369
- outputs=[lesson_feedback_box, check_ui, next_button]
370
- )
371
- next_button.click(
372
- fn=on_next_lesson,
373
- inputs=[lesson_index, dynamic_curriculum, dynamic_practice_problems],
374
- outputs=[lesson_progress, explanation_box, check_ui, next_button, practice_ui, practice_problem_statement, practice_code_input, progress_bar, lesson_index]
 
 
375
  )
376
  submit_practice_button.click(
377
  fn=process_practice_answer,
378
- inputs=[practice_code_input, dynamic_practice_problems],
379
  outputs=[practice_feedback_box, hint_button, coach_button, practice_attempts]
380
  )
381
  hint_button.click(
382
  fn=provide_hint,
383
- inputs=[practice_code_input, dynamic_practice_problems, practice_attempts],
384
  outputs=[practice_feedback_box, hint_button, practice_attempts]
385
  )
386
  coach_button.click(
387
  fn=coach_code,
388
- inputs=[practice_code_input, dynamic_practice_problems],
389
- outputs=[coach_feedback_box, coach_button, success_message, start_new_topic_btn]
390
- )
391
- start_new_topic_btn.click(
392
- fn=reset_to_start,
393
- inputs=None,
394
- outputs=[topic_selection_ui, learning_ui, practice_ui, success_message, start_new_topic_btn, coach_feedback_box, coach_button, hint_button, practice_feedback_box, generation_status]
395
  )
 
 
396
 
397
  if __name__ == "__main__":
398
  demo.launch(debug=True)
 
38
  api_key=api_key,
39
  )
40
 
41
+ ### --- DATA: FULL 6-MODULE CURRICULUM (EXPANDED) --- ###
42
+ curriculum = [
43
+ # MODULE 1: Variables
44
+ {"module": "Mastering Variables", "title": "Lesson 1: What is a Variable?", "explanation": "In programming, a **variable** is a named container that holds a value. Think of it like a labeled box where you can store a piece of information, like a number or a piece of text. You can then refer to the information by the box's label.\n\n**Example:**\n```python\n# Here, 'city' is the variable, and 'Jhang' is the value.\ncity = \"Jhang\"\n```", "check": {"type": "mcq", "question": "In `age = 25`, what is the variable name?", "options": ["age", "25", "="], "answer": "age"}},
45
+ {"module": "Mastering Variables", "title": "Lesson 2: Common Data Types", "explanation": "Variables can store different **types** of data. The most common are:\n- **`int` (Integer):** Whole numbers, like `10`, `-5`, or `150`.\n- **`str` (String):** Text, enclosed in quotes, like `\"Hello, Gojra\"` or `'Python'`.\n- **`float`:** Numbers with a decimal point, like `3.14` or `99.9`.", "check": {"type": "writing", "question": "What is the data type for text, like \"hello\"?", "answer": "string"}},
46
+
47
+ # MODULE 2: Lists
48
+ {"module": "Mastering Lists", "title": "Lesson 1: What is a List?", "explanation": "A **list** is an ordered collection of items, enclosed in square brackets `[]`. Lists are incredibly useful because they let you store multiple related values in a single variable.\n\n**Example:**\n```python\n# A list of numbers\nscores = [88, 92, 100]\n\n# A list of strings\ncities = [\"Jhang\", \"Gojra\", \"Faisalabad\"]\n```", "check": {"type": "mcq", "question": "Which of these creates a valid Python list?", "options": ["(1, 2)", "[1, 2]", "{1, 2}"], "answer": "[1, 2]"}},
49
+ {"module": "Mastering Lists", "title": "Lesson 2: Accessing Elements by Index", "explanation": "You can get a single item from a list using its **index**—its position in the list. **Important:** Indexing starts at `0` for the first item.\n\n**Example:**\n```python\nletters = ['a', 'b', 'c']\nfirst_letter = letters[0] # This gets 'a'\nthird_letter = letters[2] # This gets 'c'\n```", "check": {"type": "logical", "question": "For `letters = ['a', 'b', 'c']`, how do you get the item 'b'?", "options": ["letters[0]", "letters[1]", "letters[2]"], "answer": "letters[1]"}},
50
+ {"module": "Mastering Lists", "title": "Lesson 3: Modifying Lists", "explanation": "Lists are **mutable**, which means you can change them after they are created. A common way to modify a list is to add an item to the end using the `.append()` method.\n\n**Example:**\n```python\nscores = [88, 92]\nscores.append(100) # Now, scores is [88, 92, 100]\n```", "check": {"type": "writing", "question": "Which method adds an item to the end of a list?", "answer": "append"}},
51
+
52
+ # MODULE 3: Loops
53
+ {"module": "Mastering Loops", "title": "Lesson 1: The `for` Loop", "explanation": "A **`for` loop** is used for iterating over a sequence (like a list). It runs a block of code once for each item in the sequence. This is perfect for when you want to do something to every item in a collection.\n\n**Example:**\n```python\n# This will print each city on a new line\ncities = [\"Jhang\", \"Gojra\"]\nfor city in cities:\n print(city)\n```", "check": {"type": "writing", "question": "Which loop is best for going through every single item in a list?", "answer": "for"}},
54
+
55
+ # MODULE 4: Operators
56
+ {"module": "Mastering Operators", "title": "Lesson 1: Comparison Operators", "explanation": "Comparison operators compare two values and return a boolean result (`True` or `False`). They are the foundation of decision-making in code.\n- `==` : Equal to\n- `!=` : Not equal to\n- `>` : Greater than\n- `<` : Less than\n\n**Example:**\n```python\nage = 18\nis_adult = age > 17 # This will be True\n```", "check": {"type": "mcq", "question": "Which operator checks if two values are exactly equal?", "options": ["=", "==", "!="], "answer": "=="}},
57
+
58
+ # MODULE 5: Strings
59
+ {"module": "Mastering Strings", "title": "Lesson 1: String Methods", "explanation": "Strings have built-in functions called **methods** that perform actions on the string. They are called using a dot (`.`) after the string variable.\n\n**Example:**\n```python\nmessage = \"Hello World\"\nprint(message.upper()) # Prints 'HELLO WORLD'\nprint(message.lower()) # Prints 'hello world'\n```", "check": {"type": "logical", "question": "What would `'Python'.lower()` return?", "options": ["'PYTHON'", "'Python'", "'python'"], "answer": "'python'"}},
60
+
61
+ # MODULE 6: Stacks
62
+ {"module": "Mastering DSA - Stacks", "title": "Lesson 1: Stacks (LIFO)", "explanation": "A **stack** is a data structure that follows the LIFO (Last-In, First-Out) principle. In Python, a list can be used as a stack. You use `.append()` to add an item to the top ('push') and `.pop()` to remove the most recently added item ('pop').\n\n**Example:**\n```python\nstack = []\nstack.append('a') # stack is ['a']\nstack.append('b') # stack is ['a', 'b']\nitem = stack.pop() # item is 'b', stack is now ['a']\n```", "check": {"type": "mcq", "question": "Which principle does a stack follow?", "options": ["FIFO", "LIFO", "Random"], "answer": "LIFO"}},
63
+ ]
64
+
65
+ ### --- DATA: DEDICATED PRACTICE PROBLEMS (ALIGNED WITH LESSONS) --- ###
66
+ practice_problems = [
67
+ # --- Foundations (Baby Steps) ---
68
+ {
69
+ "title": "Foundations: Access a List Item",
70
+ "concepts_covered": ["Lists"],
71
+ "problem_statement": "You are given a list: `cities = ['Jhang', 'Gojra', 'Faisalabad']`. Get the second item, 'Gojra', and store it in a variable called `my_city`.",
72
+ "starter_code": "cities = ['Jhang', 'Gojra', 'Faisalabad']\n\n# Your code here\nmy_city = ...",
73
+ "check_type": "variable", "check_variable": "my_city", "expected_result": "Gojra",
74
+ "background_knowledge": "Remember that list indexing starts at 0. The first item is at index 0, the second at index 1, and so on. You access an item using square brackets `[]`."
75
+ },
76
+ {
77
+ "title": "Foundations: Add to a List",
78
+ "concepts_covered": ["Lists"],
79
+ "problem_statement": "You have a list of numbers: `scores = [88, 92]`. Add the number `100` to the **end** of this list.",
80
+ "starter_code": "scores = [88, 92]\n\n# Your code here",
81
+ "check_type": "variable", "check_variable": "scores", "expected_result": [88, 92, 100],
82
+ "background_knowledge": "The `.append()` method is used to add a single item to the end of a list. For example, `my_list.append(5)` would add the number 5 to the end of `my_list`."
83
+ },
84
+ {
85
+ "title": "Foundations: Uppercase a String",
86
+ "concepts_covered": ["Strings"],
87
+ "problem_statement": "You have a string `name = \"gojra\"`. Convert this string to all uppercase letters and store the result in a variable called `uppercase_name`.",
88
+ "starter_code": "name = \"gojra\"\n\n# Your code here\nuppercase_name = ...",
89
+ "check_type": "variable", "check_variable": "uppercase_name", "expected_result": "GOJRA",
90
+ "background_knowledge": "Strings have many useful built-in methods. The `.upper()` method will return a new string where all characters are in uppercase. Remember to call it with parentheses, like `my_string.upper()`."
91
+ },
92
+ # --- Easy (Combining Concepts) ---
93
+ {
94
+ "title": "Easy: Count Positive Numbers",
95
+ "concepts_covered": ["Lists", "Loops", "Operators"],
96
+ "problem_statement": "You are given a list: `data = [10, -5, 3, 0, -1, 8]`. Write a script that loops through the list and counts how many numbers are **strictly greater than 0**. The final count should be in a variable named `positive_count`.",
97
+ "starter_code": "data = [10, -5, 3, 0, -1, 8]\npositive_count = 0\n\n# Your code here",
98
+ "check_type": "variable", "check_variable": "positive_count", "expected_result": 3,
99
+ "background_knowledge": "This problem combines loops and conditional statements. Remember, a `for` loop is great for checking every item in a list, and an `if` statement can be used inside the loop to check if a number meets a certain condition (like being greater than zero)."
100
+ },
101
+ {
102
+ "title": "Easy: Filter Long Words",
103
+ "concepts_covered": ["Lists", "Loops", "Strings"],
104
+ "problem_statement": "You have a list of words: `words = ['cat', 'python', 'excellent', 'door', 'window']`. Create a new list called `long_words` that contains only the words from the original list that have **more than 5 characters**.",
105
+ "starter_code": "words = ['cat', 'python', 'excellent', 'door', 'window']\nlong_words = []\n\n# Your code here",
106
+ "check_type": "variable", "check_variable": "long_words", "expected_result": ['python', 'excellent', 'window'],
107
+ "background_knowledge": "To solve this, you'll need to loop through the `words` list. Inside the loop, you can find the length of each word using the `len()` function. If the length meets the condition, you can add that word to your `long_words` list using the `.append()` method."
108
+ },
109
+ ]
110
 
111
  ### --- HELPER & AI FUNCTIONS --- ###
112
 
 
132
  except Exception as e:
133
  return {"success": False, "error": f"An API error occurred: {e}"}
134
 
135
+ def get_new_explanation(lesson_title, prev_exp, question, answer):
136
+ user_prompt = f"A student is struggling with '{lesson_title}'. They saw '{prev_exp}', were asked '{question}', and answered '{answer}'. Re-explain concisely with a new analogy."
137
+ system_prompt = "You are a helpful Python tutor."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  result = make_api_call(system_prompt, user_prompt)
139
+ return result["content"] if result["success"] else f"Error getting explanation: {result['error']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
 
142
  def get_socratic_hint(problem, user_code, actual_result, error_message=None):
 
167
  return result["content"] if result["success"] else f"Error getting coaching feedback: {result['error']}"
168
 
169
 
170
+ ### --- GRADIO APP WITH TABS --- ###
171
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
172
+ time_str = "Sunday, August 24, 2025 at 02:03 PM PKT"
173
 
174
+ gr.Markdown(f"# Welcome to the CS Pathfinder\n*Current Location: Jhang, Punjab, Pakistan. Current Time: {time_str}*")
175
+
176
+ with gr.Tabs():
177
+ # --- TAB 1: LEARN ---
178
+ with gr.TabItem("🎓 Learn"):
179
+ with gr.Row():
180
+ start_vars_btn = gr.Button("Variables")
181
+ start_lists_btn = gr.Button("Lists")
182
+ start_loops_btn = gr.Button("Loops")
183
+ start_ops_btn = gr.Button("Operators")
184
+ start_strings_btn = gr.Button("Strings")
185
+ start_dsa_btn = gr.Button("DSA - Stacks")
186
+
187
+ lesson_index = gr.State(0)
188
+ failed_attempts = gr.State(0)
189
+
190
+ progress_bar = gr.Markdown("")
191
+
192
+ lesson_progress = gr.Markdown(f"### Module: {curriculum[0]['module']} - Lesson 1 / {len(curriculum)}")
193
+ explanation_box = gr.Markdown(curriculum[0]['explanation'])
194
+ with gr.Column() as check_ui:
195
+ check_question = gr.Markdown(f"**Question:** {curriculum[0]['check']['question']}")
196
+ mcq_input = gr.Radio(choices=curriculum[0]['check']['options'], label="Select your answer")
197
+ writing_input = gr.Textbox(label="Type your answer here", visible=False)
198
+ submit_button = gr.Button("Submit Answer")
199
+ lesson_feedback_box = gr.Markdown("")
200
+ next_button = gr.Button("➡️ Continue", visible=False)
201
+ success_message = gr.Markdown("🎉 **Congratulations! You have finished all learning modules!** 🎉", visible=False)
202
+
203
+ # --- TAB 2: PRACTICE ---
204
+ with gr.TabItem("💻 Practice"):
205
+ gr.Markdown("## Practice Arena\nTest your combined knowledge with these coding challenges.")
206
+
207
+ practice_attempts = gr.State(0)
208
+ problem_titles = [p["title"] for p in practice_problems]
209
+
210
+ practice_problem_selector = gr.Dropdown(choices=problem_titles, label="Choose a Problem", value=None)
211
+
212
  with gr.Row():
213
  with gr.Column(scale=2):
214
+ practice_problem_statement = gr.Markdown(visible=False)
215
+ practice_code_input = gr.Code(language="python", label="Your Code", visible=False)
216
  with gr.Column(scale=1):
217
+ # Dedicated box for coaching feedback
218
  coach_feedback_box = gr.Markdown(visible=False)
219
+
220
+ submit_practice_button = gr.Button("Run Code", visible=False)
221
  hint_button = gr.Button("🤔 Get a Hint", visible=False)
222
  coach_button = gr.Button("🤖 Coach Me: Improve My Code", visible=False)
223
  practice_feedback_box = gr.Markdown("")
224
 
225
+ # --- LOGIC FOR LEARN TAB ---
226
+ def update_ui_for_lesson(new_lesson_index):
227
+ lesson = curriculum[new_lesson_index]
228
+ mcq_visible = lesson['check']['type'] in ['mcq', 'logical']
 
 
 
 
229
 
230
+ total_lessons = len(curriculum)
231
+ completed_lessons = new_lesson_index
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  percentage = int((completed_lessons / total_lessons) * 100)
233
  bar_length = 30
234
  filled_length = int(bar_length * percentage / 100)
 
236
  progress_text = f"**Progress:** {bar} {percentage}% ({completed_lessons}/{total_lessons} Lessons)"
237
 
238
  return {
239
+ lesson_index: new_lesson_index, failed_attempts: 0,
240
+ lesson_progress: f"### Module: {lesson['module']} - Lesson {new_lesson_index + 1} / {len(curriculum)}",
241
+ explanation_box: lesson['explanation'], check_ui: gr.update(visible=True),
242
+ check_question: f"**Question:** {lesson['check']['question']}",
243
+ mcq_input: gr.update(visible=mcq_visible, choices=lesson['check'].get('options', []), value=None),
244
+ writing_input: gr.update(visible=not mcq_visible, value=""),
245
+ lesson_feedback_box: "", next_button: gr.update(visible=False),
 
246
  progress_bar: progress_text
247
  }
248
 
249
+ def process_answer(current_lesson_index, mcq_answer, writing_answer, attempts, explanation_text):
250
+ lesson = curriculum[current_lesson_index]
251
+ user_answer = writing_answer if lesson['check']['type'] == 'writing' else mcq_answer
252
+ is_correct = str(user_answer).strip().lower().replace(".", "").replace("()", "") == str(lesson['check']['answer']).strip().lower()
253
  if is_correct:
254
+ return { lesson_feedback_box: "✅ **Correct!**", check_ui: gr.update(visible=False), next_button: gr.update(visible=True), failed_attempts: 0 }
255
  else:
256
+ new_attempts = attempts + 1
257
+ if new_attempts >= 10:
258
+ return { lesson_feedback_box: "🤔 Let's move on for now.", check_ui: gr.update(visible=False), next_button: gr.update(visible=True), failed_attempts: 0 }
259
+ new_explanation = get_new_explanation(lesson['title'], explanation_text, lesson['check']['question'], user_answer)
260
+ feedback = f"❌ Not quite (Attempt {new_attempts}/10). Read the new explanation above."
261
+ return { explanation_box: new_explanation, lesson_feedback_box: feedback, failed_attempts: new_attempts }
262
+
263
+ def on_next_lesson(current_lesson_index):
264
+ new_index = current_lesson_index + 1
265
+ if new_index < len(curriculum):
266
+ return update_ui_for_lesson(new_index)
267
  else:
268
+ total_lessons = len(curriculum)
 
 
269
  bar = '█' * 30
270
  progress_text = f"**Progress:** {bar} 100% ({total_lessons}/{total_lessons} Lessons)"
 
271
  return {
272
+ lesson_progress: "### All Modules Complete! ###", explanation_box: "",
273
+ success_message: gr.update(visible=True), next_button: gr.update(visible=False),
274
+ check_ui: gr.update(visible=False), lesson_index: new_index,
275
+ progress_bar: progress_text
 
 
 
 
 
276
  }
277
+
278
+ def start_module(module_name):
279
+ for i, lesson in enumerate(curriculum):
280
+ if lesson['module'] == module_name:
281
+ return update_ui_for_lesson(i)
282
+ return {}
283
+
284
+ # --- LOGIC FOR PRACTICE TAB ---
285
+ def select_practice_problem(problem_title):
286
+ if not problem_title:
287
+ return {
288
+ practice_problem_statement: gr.update(visible=False), practice_code_input: gr.update(visible=False),
289
+ submit_practice_button: gr.update(visible=False), hint_button: gr.update(visible=False),
290
+ coach_button: gr.update(visible=False), coach_feedback_box: gr.update(visible=False),
291
+ practice_feedback_box: "", practice_attempts: 0
292
+ }
293
+ problem = next((p for p in practice_problems if p["title"] == problem_title), None)
294
+ if problem:
295
+ # NEW: Display the concepts covered
296
+ concepts_text = f"**Concepts Tested:** {', '.join(problem['concepts_covered'])}"
297
+ return {
298
+ practice_problem_statement: gr.update(visible=True, value=f"{concepts_text}\n\n---\n\n{problem['problem_statement']}"),
299
+ practice_code_input: gr.update(visible=True, value=problem["starter_code"]),
300
+ submit_practice_button: gr.update(visible=True), hint_button: gr.update(visible=False),
301
+ coach_button: gr.update(visible=False), coach_feedback_box: gr.update(visible=False, value=""),
302
+ practice_feedback_box: "", practice_attempts: 0
303
+ }
304
+ return {}
305
+
306
+ def process_practice_answer(problem_title, user_code):
307
+ problem = next((p for p in practice_problems if p["title"] == problem_title), None)
308
  result = execute_practice_code(user_code)
309
 
310
+ # Always reset hint counter on a new submission
311
  attempts = 0
312
+
313
  if not result['success']:
314
  return {
315
+ practice_feedback_box: f"❌ **Code Error:** Your code couldn't run. See the error below. Read it carefully, or ask for a hint to help find the mistake.\n\n```\n{result['error']}\n```",
316
  hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
317
  practice_attempts: attempts
318
  }
 
321
  expected_value = problem['expected_result']
322
  actual_value = result['scope'].get(var_name, f"Variable '{var_name}' not found!")
323
 
324
+ if actual_value == expected_value:
325
  return {
326
  practice_feedback_box: f"✅ **Success!** The variable `{var_name}` has the correct value. Great job! Now, let the AI Code Coach show you how to improve it.",
327
  hint_button: gr.update(visible=False), coach_button: gr.update(visible=True),
 
333
  hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
334
  practice_attempts: attempts
335
  }
336
+
337
+ def provide_hint(problem_title, user_code, attempts):
338
  new_attempts = attempts + 1
 
 
339
  if new_attempts > MAX_HINTS:
340
  return {
341
  practice_feedback_box: f"**Hint Limit Reached ({MAX_HINTS}/{MAX_HINTS}).**",
 
343
  practice_attempts: new_attempts
344
  }
345
 
346
+ problem = next((p for p in practice_problems if p["title"] == problem_title), None)
347
  result = execute_practice_code(user_code)
348
 
349
  if new_attempts == 1 and result['success']:
 
360
  practice_attempts: new_attempts
361
  }
362
 
363
+ def coach_code(problem_title, user_code):
364
+ problem = next((p for p in practice_problems if p["title"] == problem_title), None)
365
  coaching_text = get_code_coaching(problem, user_code)
366
  return {
367
  coach_feedback_box: gr.update(visible=True, value=coaching_text),
368
+ coach_button: gr.update(interactive=False) # Disable button after use
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
 
371
  # --- EVENT LISTENERS ---
372
+ all_learning_components = [lesson_index, failed_attempts, lesson_progress, explanation_box, check_ui, check_question, mcq_input, writing_input, lesson_feedback_box, next_button, progress_bar]
373
+
374
+ submit_button.click(fn=process_answer, inputs=[lesson_index, mcq_input, writing_input, failed_attempts, explanation_box], outputs=[explanation_box, lesson_feedback_box, check_ui, next_button, failed_attempts])
375
+ next_button.click(fn=on_next_lesson, inputs=[lesson_index], outputs=all_learning_components + [success_message])
376
+
377
+ start_vars_btn.click(fn=lambda: start_module("Mastering Variables"), inputs=None, outputs=all_learning_components)
378
+ start_lists_btn.click(fn=lambda: start_module("Mastering Lists"), inputs=None, outputs=all_learning_components)
379
+ start_loops_btn.click(fn=lambda: start_module("Mastering Loops"), inputs=None, outputs=all_learning_components)
380
+ start_ops_btn.click(fn=lambda: start_module("Mastering Operators"), inputs=None, outputs=all_learning_components)
381
+ start_strings_btn.click(fn=lambda: start_module("Mastering Strings"), inputs=None, outputs=all_learning_components)
382
+ start_dsa_btn.click(fn=lambda: start_module("Mastering DSA - Stacks"), inputs=None, outputs=all_learning_components)
383
+
384
+ practice_problem_selector.change(
385
+ fn=select_practice_problem,
386
+ inputs=[practice_problem_selector],
387
+ outputs=[practice_problem_statement, practice_code_input, submit_practice_button, hint_button, coach_button, coach_feedback_box, practice_feedback_box, practice_attempts]
388
  )
389
  submit_practice_button.click(
390
  fn=process_practice_answer,
391
+ inputs=[practice_problem_selector, practice_code_input],
392
  outputs=[practice_feedback_box, hint_button, coach_button, practice_attempts]
393
  )
394
  hint_button.click(
395
  fn=provide_hint,
396
+ inputs=[practice_problem_selector, practice_code_input, practice_attempts],
397
  outputs=[practice_feedback_box, hint_button, practice_attempts]
398
  )
399
  coach_button.click(
400
  fn=coach_code,
401
+ inputs=[practice_problem_selector, practice_code_input],
402
+ outputs=[coach_feedback_box, coach_button]
 
 
 
 
 
403
  )
404
+
405
+ demo.load(lambda: update_ui_for_lesson(0), outputs=all_learning_components)
406
 
407
  if __name__ == "__main__":
408
  demo.launch(debug=True)