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 ### --- CONSTANTS & CONFIGURATION --- ### MODEL_NAME = "openai/gpt-5-chat-latest" MAX_HINTS = 15 # Set timezone to Pakistan Standard Time (PKT) PAKISTAN_TZ = pytz.timezone('Asia/Karachi') ### --- ROBUST API CLIENT SETUP --- ### api_key = None try: # Preferred method for cloud environments like Google Colab from google.colab import userdata api_key = userdata.get('AIML_API_KEY') print("Successfully found API key in Google Colab Secrets.") except (ImportError, KeyError): # Fallback for local development 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.") # Initialize the OpenAI client with the custom base URL and your API key client = openai.OpenAI( base_url="https://api.aimlapi.com/v1", api_key=api_key, ) ### --- DATA: FULL 6-MODULE CURRICULUM (EXPANDED) --- ### curriculum = [ # MODULE 1: Variables {"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 2: Lists {"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 3: Loops {"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 4: Operators {"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 5: Strings {"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 6: Stacks {"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"}}, ] ### --- DATA: DEDICATED PRACTICE PROBLEMS (EXPANDED & ALIGNED) --- ### practice_problems = [ # --- Foundations (Baby Steps) --- { "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: `\" \"`." }, # --- Easy (Combining Concepts) --- { "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." }, # --- Medium (More Complex Logic) --- { "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." } ] ### --- HELPER & AI FUNCTIONS --- ### 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']}" ### --- GRADIO APP WITH TABS --- ### 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(): # --- TAB 1: LEARN --- 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) # --- TAB 2: PRACTICE --- 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): # Dedicated box for coaching feedback 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("") # --- LOGIC FOR LEARN TAB --- 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 {} # --- LOGIC FOR PRACTICE TAB --- 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: # NEW: Display the concepts covered 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) # Always reset hint counter on a new submission 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) # Disable button after use } # --- EVENT LISTENERS --- 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)