|
|
import gradio as gr |
|
|
import openai |
|
|
import os |
|
|
import sys |
|
|
from io import StringIO |
|
|
import traceback |
|
|
import datetime |
|
|
import pytz |
|
|
import time |
|
|
import random |
|
|
import json |
|
|
|
|
|
|
|
|
MODEL_NAME = "openai/gpt-5-chat-latest" |
|
|
MAX_HINTS = 15 |
|
|
|
|
|
PAKISTAN_TZ = pytz.timezone('Asia/Karachi') |
|
|
|
|
|
|
|
|
api_key = None |
|
|
try: |
|
|
|
|
|
from google.colab import userdata |
|
|
api_key = userdata.get('AIML_API_KEY') |
|
|
print("Successfully found API key in Google Colab Secrets.") |
|
|
except (ImportError, KeyError): |
|
|
|
|
|
api_key = os.getenv("AIML_API_KEY") |
|
|
if api_key: |
|
|
print("Successfully found API key in environment variables.") |
|
|
|
|
|
if not api_key: |
|
|
print("FATAL ERROR: API Key not found. Please set the 'AIML_API_KEY' in your environment or secrets.") |
|
|
|
|
|
|
|
|
client = openai.OpenAI( |
|
|
base_url="https://api.aimlapi.com/v1", |
|
|
api_key=api_key, |
|
|
) |
|
|
|
|
|
|
|
|
curriculum = [ |
|
|
|
|
|
{"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"}}, |
|
|
{"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"}}, |
|
|
|
|
|
|
|
|
{"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]"}}, |
|
|
{"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]"}}, |
|
|
{"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"}}, |
|
|
|
|
|
|
|
|
{"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"}}, |
|
|
|
|
|
|
|
|
{"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": "=="}}, |
|
|
|
|
|
|
|
|
{"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'"}}, |
|
|
|
|
|
|
|
|
{"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"}}, |
|
|
] |
|
|
|
|
|
|
|
|
practice_problems = [ |
|
|
|
|
|
{ |
|
|
"title": "Foundations: Access a List Item", |
|
|
"concepts_covered": ["Lists"], |
|
|
"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`.", |
|
|
"starter_code": "cities = ['Jhang', 'Gojra', 'Faisalabad']\n\n# Your code here\nmy_city = ...", |
|
|
"check_type": "variable", "check_variable": "my_city", "expected_result": "Gojra", |
|
|
"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 `[]`." |
|
|
}, |
|
|
{ |
|
|
"title": "Foundations: Add to a List", |
|
|
"concepts_covered": ["Lists"], |
|
|
"problem_statement": "You have a list of numbers: `scores = [88, 92]`. Add the number `100` to the **end** of this list.", |
|
|
"starter_code": "scores = [88, 92]\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "scores", "expected_result": [88, 92, 100], |
|
|
"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`." |
|
|
}, |
|
|
{ |
|
|
"title": "Foundations: Uppercase a String", |
|
|
"concepts_covered": ["Strings"], |
|
|
"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`.", |
|
|
"starter_code": "name = \"gojra\"\n\n# Your code here\nuppercase_name = ...", |
|
|
"check_type": "variable", "check_variable": "uppercase_name", "expected_result": "GOJRA", |
|
|
"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()`." |
|
|
}, |
|
|
{ |
|
|
"title": "Foundations: Reassign a Variable", |
|
|
"concepts_covered": ["Variables"], |
|
|
"problem_statement": "A variable `score` is initially set to 50. On the next line, update (reassign) the value of `score` to be 100.", |
|
|
"starter_code": "score = 50\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "score", "expected_result": 100, |
|
|
"background_knowledge": "To change the value of a variable, you simply assign a new value to it using the equals sign (`=`). The old value is forgotten." |
|
|
}, |
|
|
{ |
|
|
"title": "Foundations: String Concatenation", |
|
|
"concepts_covered": ["Variables", "Strings"], |
|
|
"problem_statement": "You are given two string variables, `first_name` and `last_name`. Combine them to create a `full_name` with a space in between.", |
|
|
"starter_code": "first_name = \"Ali\"\nlast_name = \"Khan\"\n\n# Your code here\nfull_name = ...", |
|
|
"check_type": "variable", "check_variable": "full_name", "expected_result": "Ali Khan", |
|
|
"background_knowledge": "You can join strings using the `+` operator. To add a space, you can concatenate a string that contains just a space: `\" \"`." |
|
|
}, |
|
|
|
|
|
{ |
|
|
"title": "Easy: Sum of List Elements", |
|
|
"concepts_covered": ["Variables", "Lists", "Loops"], |
|
|
"problem_statement": "You are given a list of numbers: `data = [10, 20, 30, 40]`. Write a `for` loop to calculate the sum of all the numbers in the list. Store the final result in a variable called `total`.", |
|
|
"starter_code": "data = [10, 20, 30, 40]\ntotal = 0\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "total", "expected_result": 100, |
|
|
"background_knowledge": "This is a classic introductory problem. You'll need to create a variable to keep track of the running total. Then, loop through each number in the list and add it to your total in each iteration." |
|
|
}, |
|
|
{ |
|
|
"title": "Easy: Count Positive Numbers", |
|
|
"concepts_covered": ["Lists", "Loops", "Operators"], |
|
|
"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`.", |
|
|
"starter_code": "data = [10, -5, 3, 0, -1, 8]\npositive_count = 0\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "positive_count", "expected_result": 3, |
|
|
"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)." |
|
|
}, |
|
|
{ |
|
|
"title": "Easy: Filter Long Words", |
|
|
"concepts_covered": ["Lists", "Loops", "Strings"], |
|
|
"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**.", |
|
|
"starter_code": "words = ['cat', 'python', 'excellent', 'door', 'window']\nlong_words = []\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "long_words", "expected_result": ['python', 'excellent', 'window'], |
|
|
"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." |
|
|
}, |
|
|
{ |
|
|
"title": "Easy: Create a List of Word Lengths", |
|
|
"concepts_covered": ["Lists", "Loops", "Strings"], |
|
|
"problem_statement": "You have a list of words: `words = ['sky', 'is', 'blue']`. Create a new list called `lengths` that contains the length of each word.", |
|
|
"starter_code": "words = ['sky', 'is', 'blue']\nlengths = []\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "lengths", "expected_result": [3, 2, 4], |
|
|
"background_knowledge": "You will need to loop through the `words` list. In each iteration, calculate the length of the word using `len()` and then `.append()` that length to your `lengths` list." |
|
|
}, |
|
|
{ |
|
|
"title": "Easy: Find Sum of Negative Numbers", |
|
|
"concepts_covered": ["Lists", "Loops", "Operators"], |
|
|
"problem_statement": "Given a list `data = [5, -2, -8, 4, -1]`, find the sum of only the negative numbers. Store the result in a variable called `negative_sum`.", |
|
|
"starter_code": "data = [5, -2, -8, 4, -1]\nnegative_sum = 0\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "negative_sum", "expected_result": -11, |
|
|
"background_knowledge": "This is similar to summing all elements, but with a condition. Loop through the list, and use an `if` statement to check if a number is less than 0. If it is, add it to your `negative_sum` variable." |
|
|
}, |
|
|
|
|
|
{ |
|
|
"title": "Medium: Find the Maximum Number", |
|
|
"concepts_covered": ["Lists", "Loops", "Operators"], |
|
|
"problem_statement": "Given a list `numbers = [1, 5, 2, 9, 3, 8]`, find the largest number in the list **without using the built-in `max()` function**. Store your result in a variable called `largest_number`.", |
|
|
"starter_code": "numbers = [1, 5, 2, 9, 3, 8]\n# Initialize with the first element\nlargest_number = numbers[0]\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "largest_number", "expected_result": 9, |
|
|
"background_knowledge": "The key to this problem is to keep track of the biggest number you've seen *so far*. You start by assuming the first number is the largest. Then, you loop through the rest of the numbers, and if you find one that's bigger than your current `largest_number`, you update it." |
|
|
}, |
|
|
{ |
|
|
"title": "Medium: Count Vowels in a String", |
|
|
"concepts_covered": ["Strings", "Loops", "Operators"], |
|
|
"problem_statement": "Given the string `sentence = \"learning python is fun\"`, count how many vowels (`a`, `e`, `i`, `o`, `u`) it contains. Store the result in a variable called `vowel_count`.", |
|
|
"starter_code": "sentence = \"learning python is fun\"\nvowels = \"aeiou\"\nvowel_count = 0\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "vowel_count", "expected_result": 6, |
|
|
"background_knowledge": "You can loop through each character of the `sentence`. Inside the loop, you need to check if the character is one of the vowels. A simple way is to use the `in` operator: `if char in vowels:`." |
|
|
}, |
|
|
{ |
|
|
"title": "Medium: Remove Odd Numbers", |
|
|
"concepts_covered": ["Lists", "Loops", "Operators"], |
|
|
"problem_statement": "You are given a list of numbers `data = [1, 2, 3, 4, 5, 6]`. Create a new list called `even_numbers` that contains only the even numbers from the original list.", |
|
|
"starter_code": "data = [1, 2, 3, 4, 5, 6]\neven_numbers = []\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "even_numbers", "expected_result": [2, 4, 6], |
|
|
"background_knowledge": "You can check if a number is even using the modulo operator (`%`). A number is even if `number % 2 == 0`. Loop through the `data` list, check each number, and if it's even, `.append()` it to the `even_numbers` list." |
|
|
}, |
|
|
{ |
|
|
"title": "Medium: Reverse a String with a Stack", |
|
|
"concepts_covered": ["Strings", "Loops", "Stacks"], |
|
|
"problem_statement": "Using the concepts of a stack (LIFO), reverse the string `text = \"hello\"`. 1. Push each character onto a list (our stack). 2. Pop each character off and join them to form the reversed string. Store the final result in `reversed_text`.", |
|
|
"starter_code": "text = \"hello\"\nstack = []\nreversed_text = \"\"\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "reversed_text", "expected_result": "olleh", |
|
|
"background_knowledge": "This problem demonstrates the LIFO (Last-In, First-Out) nature of stacks. You'll need two loops. The first loop iterates through the input string to `append()` (push) each character onto the `stack` list. The second loop should run as long as the stack is not empty, using `.pop()` to get the last character and add it to your `reversed_text` string." |
|
|
}, |
|
|
{ |
|
|
"title": "Hard: Palindrome Checker", |
|
|
"concepts_covered": ["Strings", "Operators"], |
|
|
"problem_statement": "A palindrome is a word that reads the same forwards and backward, like 'racecar'. Write code to check if the string `word = \"madam\"` is a palindrome. Store your boolean result (`True` or `False`) in a variable called `is_palindrome`.", |
|
|
"starter_code": "word = \"madam\"\nis_palindrome = False\n\n# Hint: You can compare a string to its reversed version.\n# How do you reverse a string? Slicing `[::-1]` is a common way.\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "is_palindrome", "expected_result": True, |
|
|
"background_knowledge": "The most elegant way to solve this in Python is using string slicing. The slice `[::-1]` creates a reversed copy of a string. You can simply compare the original `word` with its reversed version. If they are equal (`==`), then it's a palindrome." |
|
|
}, |
|
|
{ |
|
|
"title": "Hard: List Palindrome Checker", |
|
|
"concepts_covered": ["Lists", "Operators"], |
|
|
"problem_statement": "Check if the list `data = [1, 2, 3, 2, 1]` is a palindrome (reads the same forwards and backward). Store your boolean result (`True` or `False`) in a variable called `is_list_palindrome`.", |
|
|
"starter_code": "data = [1, 2, 3, 2, 1]\nis_list_palindrome = False\n\n# Your code here", |
|
|
"check_type": "variable", "check_variable": "is_list_palindrome", "expected_result": True, |
|
|
"background_knowledge": "Just like strings, lists can also be reversed using slicing `[::-1]`. You can compare the original list to its reversed version to see if they are identical." |
|
|
} |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
def make_api_call(system_prompt, user_prompt, timeout=120.0): |
|
|
""" |
|
|
A single, robust function for all API calls. |
|
|
""" |
|
|
if not api_key: |
|
|
return {"success": False, "error": "API Key is not configured."} |
|
|
try: |
|
|
response = client.chat.completions.create( |
|
|
model=MODEL_NAME, |
|
|
messages=[ |
|
|
{"role": "system", "content": system_prompt}, |
|
|
{"role": "user", "content": user_prompt} |
|
|
], |
|
|
timeout=timeout |
|
|
) |
|
|
if response.choices and response.choices[0].message and response.choices[0].message.content: |
|
|
return {"success": True, "content": response.choices[0].message.content} |
|
|
else: |
|
|
return {"success": False, "error": "The AI returned an empty response."} |
|
|
except Exception as e: |
|
|
return {"success": False, "error": f"An API error occurred: {e}"} |
|
|
|
|
|
def get_new_explanation(lesson_title, prev_exp, question, answer): |
|
|
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." |
|
|
system_prompt = "You are a helpful Python tutor." |
|
|
result = make_api_call(system_prompt, user_prompt) |
|
|
return result["content"] if result["success"] else f"Error getting explanation: {result['error']}" |
|
|
|
|
|
|
|
|
def get_socratic_hint(problem, user_code, actual_result, error_message=None): |
|
|
system_prompt = "You are a Socratic Python tutor. Your goal is to help a student solve a problem by asking guiding questions, never by giving the direct answer. Analyze the user's code and their incorrect result or error. Identify the logical flaw. Then, formulate a short, encouraging hint that makes them think about that specific flaw. DO NOT write any code. DO NOT give away the solution. Ask a question that leads them to the right way of thinking." |
|
|
|
|
|
if error_message: |
|
|
user_prompt = f"The problem is:\n---\n{problem['problem_statement']}\n---\nThe student's code is:\n---\n{user_code}\n---\nThis code failed to run and produced this error: `{error_message}`.\nPlease provide a Socratic hint to help them fix the error." |
|
|
else: |
|
|
user_prompt = f"The problem is:\n---\n{problem['problem_statement']}\n---\nThe student's code is:\n---\n{user_code}\n---\nThis code produced the result `{actual_result}` instead of the expected `{problem['expected_result']}`.\nPlease provide a Socratic hint." |
|
|
|
|
|
result = make_api_call(system_prompt, user_prompt) |
|
|
return result["content"] if result["success"] else f"Error getting hint: {result['error']}" |
|
|
|
|
|
def execute_practice_code(user_code): |
|
|
try: |
|
|
local_scope = {} |
|
|
exec(user_code, {}, local_scope) |
|
|
return {"success": True, "scope": local_scope, "error": None} |
|
|
except Exception: |
|
|
error_string = traceback.format_exc() |
|
|
return {"success": False, "scope": None, "error": error_string} |
|
|
|
|
|
def get_code_coaching(problem, user_code): |
|
|
system_prompt = "You are an expert Python developer acting as a code coach. A student has written the following correct code. Your job is to refactor it to be more efficient, readable, or 'Pythonic' (the way a professional would write it). Then, explain your changes clearly using markdown. First show the improved code in a block, then explain the key improvements in a bulleted list." |
|
|
user_prompt = f"The original problem was:\n---\n{problem['problem_statement']}\n---\nHere is the student's correct solution:\n---\n```python\n{user_code}\n```\n\nPlease refactor this code and explain the improvements." |
|
|
|
|
|
result = make_api_call(system_prompt, user_prompt) |
|
|
return result["content"] if result["success"] else f"Error getting coaching feedback: {result['error']}" |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as demo: |
|
|
time_str = "Sunday, August 24, 2025 at 02:15 PM PKT" |
|
|
|
|
|
gr.Markdown(f"# Welcome to the Python Buddy \n*Current Location: Jhang, Punjab, Pakistan. Current Time: {time_str}*") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.TabItem("🎓 Learn"): |
|
|
with gr.Row(): |
|
|
start_vars_btn = gr.Button("Variables") |
|
|
start_lists_btn = gr.Button("Lists") |
|
|
start_loops_btn = gr.Button("Loops") |
|
|
start_ops_btn = gr.Button("Operators") |
|
|
start_strings_btn = gr.Button("Strings") |
|
|
start_dsa_btn = gr.Button("DSA - Stacks") |
|
|
|
|
|
lesson_index = gr.State(0) |
|
|
failed_attempts = gr.State(0) |
|
|
|
|
|
progress_bar = gr.Markdown("") |
|
|
|
|
|
lesson_progress = gr.Markdown(f"### Module: {curriculum[0]['module']} - Lesson 1 / {len(curriculum)}") |
|
|
explanation_box = gr.Markdown(curriculum[0]['explanation']) |
|
|
with gr.Column() as check_ui: |
|
|
check_question = gr.Markdown(f"**Question:** {curriculum[0]['check']['question']}") |
|
|
mcq_input = gr.Radio(choices=curriculum[0]['check']['options'], label="Select your answer") |
|
|
writing_input = gr.Textbox(label="Type your answer here", visible=False) |
|
|
submit_button = gr.Button("Submit Answer") |
|
|
lesson_feedback_box = gr.Markdown("") |
|
|
next_button = gr.Button("➡️ Continue", visible=False) |
|
|
success_message = gr.Markdown("🎉 **Congratulations! You have finished all learning modules!** 🎉", visible=False) |
|
|
|
|
|
|
|
|
with gr.TabItem("💻 Practice"): |
|
|
gr.Markdown("## Practice Arena\nTest your combined knowledge with these coding challenges.") |
|
|
|
|
|
practice_attempts = gr.State(0) |
|
|
problem_titles = [p["title"] for p in practice_problems] |
|
|
|
|
|
practice_problem_selector = gr.Dropdown(choices=problem_titles, label="Choose a Problem", value=None) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
practice_problem_statement = gr.Markdown(visible=False) |
|
|
practice_code_input = gr.Code(language="python", label="Your Code", visible=False) |
|
|
with gr.Column(scale=1): |
|
|
|
|
|
coach_feedback_box = gr.Markdown(visible=False) |
|
|
|
|
|
submit_practice_button = gr.Button("Run Code", visible=False) |
|
|
hint_button = gr.Button("🤔 Get a Hint", visible=False) |
|
|
coach_button = gr.Button("🤖 Coach Me: Improve My Code", visible=False) |
|
|
practice_feedback_box = gr.Markdown("") |
|
|
|
|
|
|
|
|
def update_ui_for_lesson(new_lesson_index): |
|
|
lesson = curriculum[new_lesson_index] |
|
|
mcq_visible = lesson['check']['type'] in ['mcq', 'logical'] |
|
|
|
|
|
total_lessons = len(curriculum) |
|
|
completed_lessons = new_lesson_index |
|
|
percentage = int((completed_lessons / total_lessons) * 100) |
|
|
bar_length = 30 |
|
|
filled_length = int(bar_length * percentage / 100) |
|
|
bar = '█' * filled_length + '░' * (bar_length - filled_length) |
|
|
progress_text = f"**Progress:** {bar} {percentage}% ({completed_lessons}/{total_lessons} Lessons)" |
|
|
|
|
|
return { |
|
|
lesson_index: new_lesson_index, failed_attempts: 0, |
|
|
lesson_progress: f"### Module: {lesson['module']} - Lesson {new_lesson_index + 1} / {len(curriculum)}", |
|
|
explanation_box: lesson['explanation'], check_ui: gr.update(visible=True), |
|
|
check_question: f"**Question:** {lesson['check']['question']}", |
|
|
mcq_input: gr.update(visible=mcq_visible, choices=lesson['check'].get('options', []), value=None), |
|
|
writing_input: gr.update(visible=not mcq_visible, value=""), |
|
|
lesson_feedback_box: "", next_button: gr.update(visible=False), |
|
|
progress_bar: progress_text |
|
|
} |
|
|
|
|
|
def process_answer(current_lesson_index, mcq_answer, writing_answer, attempts, explanation_text): |
|
|
lesson = curriculum[current_lesson_index] |
|
|
user_answer = writing_answer if lesson['check']['type'] == 'writing' else mcq_answer |
|
|
is_correct = str(user_answer).strip().lower().replace(".", "").replace("()", "") == str(lesson['check']['answer']).strip().lower() |
|
|
if is_correct: |
|
|
return { lesson_feedback_box: "✅ **Correct!**", check_ui: gr.update(visible=False), next_button: gr.update(visible=True), failed_attempts: 0 } |
|
|
else: |
|
|
new_attempts = attempts + 1 |
|
|
if new_attempts >= 10: |
|
|
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 } |
|
|
new_explanation = get_new_explanation(lesson['title'], explanation_text, lesson['check']['question'], user_answer) |
|
|
feedback = f"❌ Not quite (Attempt {new_attempts}/10). Read the new explanation above." |
|
|
return { explanation_box: new_explanation, lesson_feedback_box: feedback, failed_attempts: new_attempts } |
|
|
|
|
|
def on_next_lesson(current_lesson_index): |
|
|
new_index = current_lesson_index + 1 |
|
|
if new_index < len(curriculum): |
|
|
return update_ui_for_lesson(new_index) |
|
|
else: |
|
|
total_lessons = len(curriculum) |
|
|
bar = '█' * 30 |
|
|
progress_text = f"**Progress:** {bar} 100% ({total_lessons}/{total_lessons} Lessons)" |
|
|
return { |
|
|
lesson_progress: "### All Modules Complete! ###", explanation_box: "", |
|
|
success_message: gr.update(visible=True), next_button: gr.update(visible=False), |
|
|
check_ui: gr.update(visible=False), lesson_index: new_index, |
|
|
progress_bar: progress_text |
|
|
} |
|
|
|
|
|
def start_module(module_name): |
|
|
for i, lesson in enumerate(curriculum): |
|
|
if lesson['module'] == module_name: |
|
|
return update_ui_for_lesson(i) |
|
|
return {} |
|
|
|
|
|
|
|
|
def select_practice_problem(problem_title): |
|
|
if not problem_title: |
|
|
return { |
|
|
practice_problem_statement: gr.update(visible=False), practice_code_input: gr.update(visible=False), |
|
|
submit_practice_button: gr.update(visible=False), hint_button: gr.update(visible=False), |
|
|
coach_button: gr.update(visible=False), coach_feedback_box: gr.update(visible=False), |
|
|
practice_feedback_box: "", practice_attempts: 0 |
|
|
} |
|
|
problem = next((p for p in practice_problems if p["title"] == problem_title), None) |
|
|
if problem: |
|
|
|
|
|
concepts_text = f"**Concepts Tested:** {', '.join(problem['concepts_covered'])}" |
|
|
return { |
|
|
practice_problem_statement: gr.update(visible=True, value=f"{concepts_text}\n\n---\n\n{problem['problem_statement']}"), |
|
|
practice_code_input: gr.update(visible=True, value=problem["starter_code"]), |
|
|
submit_practice_button: gr.update(visible=True), hint_button: gr.update(visible=False), |
|
|
coach_button: gr.update(visible=False), coach_feedback_box: gr.update(visible=False, value=""), |
|
|
practice_feedback_box: "", practice_attempts: 0 |
|
|
} |
|
|
return {} |
|
|
|
|
|
def process_practice_answer(problem_title, user_code): |
|
|
problem = next((p for p in practice_problems if p["title"] == problem_title), None) |
|
|
result = execute_practice_code(user_code) |
|
|
|
|
|
|
|
|
attempts = 0 |
|
|
|
|
|
if not result['success']: |
|
|
return { |
|
|
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```", |
|
|
hint_button: gr.update(visible=True), coach_button: gr.update(visible=False), |
|
|
practice_attempts: attempts |
|
|
} |
|
|
|
|
|
var_name = problem['check_variable'] |
|
|
expected_value = problem['expected_result'] |
|
|
actual_value = result['scope'].get(var_name, f"Variable '{var_name}' not found!") |
|
|
|
|
|
if actual_value == expected_value: |
|
|
return { |
|
|
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.", |
|
|
hint_button: gr.update(visible=False), coach_button: gr.update(visible=True), |
|
|
practice_attempts: 0 |
|
|
} |
|
|
else: |
|
|
return { |
|
|
practice_feedback_box: f"❌ **Incorrect Result.** Your code ran, but `{var_name}` was `{actual_value}` instead of `{expected_value}`. Try again, or ask for a hint.", |
|
|
hint_button: gr.update(visible=True), coach_button: gr.update(visible=False), |
|
|
practice_attempts: attempts |
|
|
} |
|
|
|
|
|
def provide_hint(problem_title, user_code, attempts): |
|
|
new_attempts = attempts + 1 |
|
|
if new_attempts > MAX_HINTS: |
|
|
return { |
|
|
practice_feedback_box: f"**Hint Limit Reached ({MAX_HINTS}/{MAX_HINTS}).**", |
|
|
hint_button: gr.update(visible=False), |
|
|
practice_attempts: new_attempts |
|
|
} |
|
|
|
|
|
problem = next((p for p in practice_problems if p["title"] == problem_title), None) |
|
|
result = execute_practice_code(user_code) |
|
|
|
|
|
if new_attempts == 1 and result['success']: |
|
|
hint = problem.get("background_knowledge", "No background knowledge available.") |
|
|
else: |
|
|
actual_value = "an error" |
|
|
if result['success']: |
|
|
actual_value = result['scope'].get(problem['check_variable'], "Not Found") |
|
|
hint = get_socratic_hint(problem, user_code, actual_value, error_message=result['error']) |
|
|
|
|
|
return { |
|
|
practice_feedback_box: f"**(Hint {new_attempts}/{MAX_HINTS})** {hint}", |
|
|
hint_button: gr.update(visible=True), |
|
|
practice_attempts: new_attempts |
|
|
} |
|
|
|
|
|
def coach_code(problem_title, user_code): |
|
|
problem = next((p for p in practice_problems if p["title"] == problem_title), None) |
|
|
coaching_text = get_code_coaching(problem, user_code) |
|
|
return { |
|
|
coach_feedback_box: gr.update(visible=True, value=coaching_text), |
|
|
coach_button: gr.update(interactive=False) |
|
|
} |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
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]) |
|
|
next_button.click(fn=on_next_lesson, inputs=[lesson_index], outputs=all_learning_components + [success_message]) |
|
|
|
|
|
start_vars_btn.click(fn=lambda: start_module("Mastering Variables"), inputs=None, outputs=all_learning_components) |
|
|
start_lists_btn.click(fn=lambda: start_module("Mastering Lists"), inputs=None, outputs=all_learning_components) |
|
|
start_loops_btn.click(fn=lambda: start_module("Mastering Loops"), inputs=None, outputs=all_learning_components) |
|
|
start_ops_btn.click(fn=lambda: start_module("Mastering Operators"), inputs=None, outputs=all_learning_components) |
|
|
start_strings_btn.click(fn=lambda: start_module("Mastering Strings"), inputs=None, outputs=all_learning_components) |
|
|
start_dsa_btn.click(fn=lambda: start_module("Mastering DSA - Stacks"), inputs=None, outputs=all_learning_components) |
|
|
|
|
|
practice_problem_selector.change( |
|
|
fn=select_practice_problem, |
|
|
inputs=[practice_problem_selector], |
|
|
outputs=[practice_problem_statement, practice_code_input, submit_practice_button, hint_button, coach_button, coach_feedback_box, practice_feedback_box, practice_attempts] |
|
|
) |
|
|
submit_practice_button.click( |
|
|
fn=process_practice_answer, |
|
|
inputs=[practice_problem_selector, practice_code_input], |
|
|
outputs=[practice_feedback_box, hint_button, coach_button, practice_attempts] |
|
|
) |
|
|
hint_button.click( |
|
|
fn=provide_hint, |
|
|
inputs=[practice_problem_selector, practice_code_input, practice_attempts], |
|
|
outputs=[practice_feedback_box, hint_button, practice_attempts] |
|
|
) |
|
|
coach_button.click( |
|
|
fn=coach_code, |
|
|
inputs=[practice_problem_selector, practice_code_input], |
|
|
outputs=[coach_feedback_box, coach_button] |
|
|
) |
|
|
|
|
|
demo.load(lambda: update_ui_for_lesson(0), outputs=all_learning_components) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(debug=True) |
|
|
|