Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
| 67 |
-
""
|
| 68 |
-
|
| 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
|
| 146 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 147 |
-
time_str = "
|
| 148 |
|
| 149 |
-
gr.Markdown(f"# Welcome to the
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 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 |
-
|
| 202 |
-
|
| 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:
|
| 238 |
-
lesson_progress: f"### Module: {lesson
|
| 239 |
-
explanation_box: lesson
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
lesson_feedback_box: "",
|
| 244 |
-
next_button: gr.update(visible=False),
|
| 245 |
progress_bar: progress_text
|
| 246 |
}
|
| 247 |
|
| 248 |
-
def process_answer(
|
| 249 |
-
lesson =
|
| 250 |
-
|
| 251 |
-
is_correct = str(
|
| 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 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
else:
|
| 262 |
-
|
| 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
|
| 270 |
-
|
| 271 |
-
check_ui: gr.update(visible=False),
|
| 272 |
-
|
| 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
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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(
|
| 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(
|
| 337 |
-
problem =
|
| 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 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
)
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
|
|
|
|
|
|
| 375 |
)
|
| 376 |
submit_practice_button.click(
|
| 377 |
fn=process_practice_answer,
|
| 378 |
-
inputs=[
|
| 379 |
outputs=[practice_feedback_box, hint_button, coach_button, practice_attempts]
|
| 380 |
)
|
| 381 |
hint_button.click(
|
| 382 |
fn=provide_hint,
|
| 383 |
-
inputs=[
|
| 384 |
outputs=[practice_feedback_box, hint_button, practice_attempts]
|
| 385 |
)
|
| 386 |
coach_button.click(
|
| 387 |
fn=coach_code,
|
| 388 |
-
inputs=[
|
| 389 |
-
outputs=[coach_feedback_box, coach_button
|
| 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)
|