Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import openai
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
from io import StringIO
|
| 6 |
+
import traceback
|
| 7 |
+
import datetime
|
| 8 |
+
import pytz
|
| 9 |
+
import time
|
| 10 |
+
import random
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
### --- CONSTANTS & CONFIGURATION --- ###
|
| 14 |
+
MODEL_NAME = "openai/gpt-5-chat-latest"
|
| 15 |
+
MAX_HINTS = 15
|
| 16 |
+
# Set timezone to Pakistan Standard Time (PKT)
|
| 17 |
+
PAKISTAN_TZ = pytz.timezone('Asia/Karachi')
|
| 18 |
+
|
| 19 |
+
### --- ROBUST API CLIENT SETUP --- ###
|
| 20 |
+
api_key = None
|
| 21 |
+
try:
|
| 22 |
+
# Preferred method for cloud environments like Google Colab
|
| 23 |
+
from google.colab import userdata
|
| 24 |
+
api_key = userdata.get('AIML_API_KEY')
|
| 25 |
+
print("Successfully found API key in Google Colab Secrets.")
|
| 26 |
+
except (ImportError, KeyError):
|
| 27 |
+
# Fallback for local development
|
| 28 |
+
api_key = os.getenv("AIML_API_KEY")
|
| 29 |
+
if api_key:
|
| 30 |
+
print("Successfully found API key in environment variables.")
|
| 31 |
+
|
| 32 |
+
if not api_key:
|
| 33 |
+
print("FATAL ERROR: API Key not found. Please set the 'AIML_API_KEY' in your environment or secrets.")
|
| 34 |
+
|
| 35 |
+
# Initialize the OpenAI client with the custom base URL and your API key
|
| 36 |
+
client = openai.OpenAI(
|
| 37 |
+
base_url="https://api.aimlapi.com/v1",
|
| 38 |
+
api_key=api_key,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
### --- HELPER & AI FUNCTIONS --- ###
|
| 43 |
+
|
| 44 |
+
def make_api_call(system_prompt, user_prompt, timeout=120.0):
|
| 45 |
+
"""
|
| 46 |
+
A single, robust function for all API calls.
|
| 47 |
+
"""
|
| 48 |
+
if not api_key:
|
| 49 |
+
return {"success": False, "error": "API Key is not configured."}
|
| 50 |
+
try:
|
| 51 |
+
response = client.chat.completions.create(
|
| 52 |
+
model=MODEL_NAME,
|
| 53 |
+
messages=[
|
| 54 |
+
{"role": "system", "content": system_prompt},
|
| 55 |
+
{"role": "user", "content": user_prompt}
|
| 56 |
+
],
|
| 57 |
+
timeout=timeout
|
| 58 |
+
)
|
| 59 |
+
if response.choices and response.choices[0].message and response.choices[0].message.content:
|
| 60 |
+
return {"success": True, "content": response.choices[0].message.content}
|
| 61 |
+
else:
|
| 62 |
+
return {"success": False, "error": "The AI returned an empty response."}
|
| 63 |
+
except Exception as e:
|
| 64 |
+
return {"success": False, "error": f"An API error occurred: {e}"}
|
| 65 |
+
|
| 66 |
+
def generate_dynamic_curriculum(topic):
|
| 67 |
+
"""
|
| 68 |
+
Asks the AI to generate a full curriculum and practice problems as a JSON object.
|
| 69 |
+
"""
|
| 70 |
+
system_prompt = """
|
| 71 |
+
You are an expert Python programming instructor and curriculum designer. Your task is to generate a complete, interactive learning module based on a user-provided topic. You MUST respond with ONLY a valid JSON object and nothing else. Do not include any introductory text, markdown formatting like ```json, or any explanations outside of the JSON structure.
|
| 72 |
+
|
| 73 |
+
The JSON object must have two top-level keys: "curriculum" and "practice_problems".
|
| 74 |
+
|
| 75 |
+
1. **curriculum**: This must be a JSON list of 2 to 3 lesson objects. Each lesson object must have:
|
| 76 |
+
- "module": A string with the name of the overall topic (e.g., "Mastering Dictionaries").
|
| 77 |
+
- "title": A string for the specific lesson title (e.g., "Lesson 1: What is a Dictionary?").
|
| 78 |
+
- "explanation": A concise, simple explanation of the concept (1-3 sentences).
|
| 79 |
+
- "check": A JSON object containing:
|
| 80 |
+
- "type": The string "mcq".
|
| 81 |
+
- "question": A string with a clear question.
|
| 82 |
+
- "options": A JSON list of 3 string options.
|
| 83 |
+
- "answer": A string that exactly matches one of the options.
|
| 84 |
+
|
| 85 |
+
2. **practice_problems**: This must be a JSON list containing exactly ONE practice problem object. This object must have:
|
| 86 |
+
- "title": A string (e.g., "Practice: Word Frequency Counter").
|
| 87 |
+
- "problem_statement": A clear problem description.
|
| 88 |
+
- "starter_code": A string of Python code for the user to start with.
|
| 89 |
+
- "check_type": The string "variable".
|
| 90 |
+
- "check_variable": The name of the variable to check for the final answer.
|
| 91 |
+
- "expected_result": The correct final value of the check_variable (can be a number, string, list, or dictionary).
|
| 92 |
+
- "background_knowledge": A brief hint about the overall approach.
|
| 93 |
+
"""
|
| 94 |
+
user_prompt = f"Generate a learning module for the Python topic: '{topic}'"
|
| 95 |
+
|
| 96 |
+
result = make_api_call(system_prompt, user_prompt)
|
| 97 |
+
|
| 98 |
+
if result["success"]:
|
| 99 |
+
if not result["content"] or not result["content"].strip():
|
| 100 |
+
return {"success": False, "error": "The AI returned an empty response. This might be a temporary issue. Please try again."}
|
| 101 |
+
try:
|
| 102 |
+
json_string = result["content"].strip()
|
| 103 |
+
if json_string.startswith("```json"):
|
| 104 |
+
json_string = json_string[7:-3].strip()
|
| 105 |
+
|
| 106 |
+
data = json.loads(json_string)
|
| 107 |
+
if "curriculum" in data and "practice_problems" in data:
|
| 108 |
+
return {"success": True, "data": data}
|
| 109 |
+
else:
|
| 110 |
+
return {"success": False, "error": "AI response was missing the required 'curriculum' or 'practice_problems' keys."}
|
| 111 |
+
except json.JSONDecodeError as e:
|
| 112 |
+
return {"success": False, "error": f"AI returned invalid JSON. Error: {e}\n\nRaw response received:\n{result['content']}"}
|
| 113 |
+
else:
|
| 114 |
+
return {"success": False, "error": result["error"]}
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def get_socratic_hint(problem, user_code, actual_result, error_message=None):
|
| 118 |
+
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."
|
| 119 |
+
|
| 120 |
+
if error_message:
|
| 121 |
+
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."
|
| 122 |
+
else:
|
| 123 |
+
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."
|
| 124 |
+
|
| 125 |
+
result = make_api_call(system_prompt, user_prompt)
|
| 126 |
+
return result["content"] if result["success"] else f"Error getting hint: {result['error']}"
|
| 127 |
+
|
| 128 |
+
def execute_practice_code(user_code):
|
| 129 |
+
try:
|
| 130 |
+
local_scope = {}
|
| 131 |
+
exec(user_code, {}, local_scope)
|
| 132 |
+
return {"success": True, "scope": local_scope, "error": None}
|
| 133 |
+
except Exception:
|
| 134 |
+
error_string = traceback.format_exc()
|
| 135 |
+
return {"success": False, "scope": None, "error": error_string}
|
| 136 |
+
|
| 137 |
+
def get_code_coaching(problem, user_code):
|
| 138 |
+
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."
|
| 139 |
+
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."
|
| 140 |
+
|
| 141 |
+
result = make_api_call(system_prompt, user_prompt)
|
| 142 |
+
return result["content"] if result["success"] else f"Error getting coaching feedback: {result['error']}"
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
### --- GRADIO APP WITH DYNAMIC TUTOR --- ###
|
| 146 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 147 |
+
time_str = "Thursday, August 21, 2025 at 09:30 PM PKT"
|
| 148 |
+
|
| 149 |
+
gr.Markdown(f"# Welcome to the AI Python Master\n*Current Location: Jhang, Punjab, Pakistan. Current Time: {time_str}*")
|
| 150 |
+
|
| 151 |
+
# --- STATE MANAGEMENT ---
|
| 152 |
+
dynamic_curriculum = gr.State([])
|
| 153 |
+
dynamic_practice_problems = gr.State([])
|
| 154 |
+
lesson_index = gr.State(0)
|
| 155 |
+
practice_attempts = gr.State(0)
|
| 156 |
+
|
| 157 |
+
# --- UI for Topic Selection ---
|
| 158 |
+
with gr.Column(visible=True) as topic_selection_ui:
|
| 159 |
+
gr.Markdown("## What Python concept do you want to master today?")
|
| 160 |
+
topic_input = gr.Textbox(label="Enter a Topic", placeholder="e.g., Python Lists, Dictionaries, For Loops, etc.")
|
| 161 |
+
generate_lesson_btn = gr.Button("Generate Lesson Plan")
|
| 162 |
+
generation_status = gr.Markdown("")
|
| 163 |
+
|
| 164 |
+
# --- UI for Dynamic Learning ---
|
| 165 |
+
with gr.Column(visible=False) as learning_ui:
|
| 166 |
+
# --- Learn Section ---
|
| 167 |
+
progress_bar = gr.Markdown("")
|
| 168 |
+
lesson_progress = gr.Markdown("")
|
| 169 |
+
explanation_box = gr.Markdown("")
|
| 170 |
+
with gr.Column() as check_ui:
|
| 171 |
+
check_question = gr.Markdown("")
|
| 172 |
+
mcq_input = gr.Radio(label="Select your answer")
|
| 173 |
+
submit_button = gr.Button("Submit Answer")
|
| 174 |
+
lesson_feedback_box = gr.Markdown("")
|
| 175 |
+
next_button = gr.Button("➡️ Continue", visible=False)
|
| 176 |
+
|
| 177 |
+
# --- Practice Section ---
|
| 178 |
+
with gr.Column(visible=False) as practice_ui:
|
| 179 |
+
gr.Markdown("--- \n ## Practice Arena")
|
| 180 |
+
with gr.Row():
|
| 181 |
+
with gr.Column(scale=2):
|
| 182 |
+
practice_problem_statement = gr.Markdown("")
|
| 183 |
+
practice_code_input = gr.Code(language="python", label="Your Code")
|
| 184 |
+
with gr.Column(scale=1):
|
| 185 |
+
coach_feedback_box = gr.Markdown(visible=False)
|
| 186 |
+
|
| 187 |
+
submit_practice_button = gr.Button("Run Code")
|
| 188 |
+
hint_button = gr.Button("🤔 Get a Hint", visible=False)
|
| 189 |
+
coach_button = gr.Button("🤖 Coach Me: Improve My Code", visible=False)
|
| 190 |
+
practice_feedback_box = gr.Markdown("")
|
| 191 |
+
|
| 192 |
+
success_message = gr.Markdown("🎉 **Congratulations! You have finished this module!** 🎉", visible=False)
|
| 193 |
+
start_new_topic_btn = gr.Button("Learn Another Topic", visible=False)
|
| 194 |
+
|
| 195 |
+
# --- LOGIC FOR DYNAMIC TUTOR ---
|
| 196 |
+
def generate_and_start_lesson(topic, progress=gr.Progress()):
|
| 197 |
+
progress(0, desc="Asking the AI to design your lesson... This may take a moment.")
|
| 198 |
+
|
| 199 |
+
result = generate_dynamic_curriculum(topic)
|
| 200 |
+
|
| 201 |
+
if not result["success"]:
|
| 202 |
+
return {
|
| 203 |
+
topic_selection_ui: gr.update(visible=True),
|
| 204 |
+
generation_status: gr.update(visible=True, value=f"❌ **Error:** {result['error']}"),
|
| 205 |
+
learning_ui: gr.update(visible=False)
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
new_curriculum = result["data"]["curriculum"]
|
| 209 |
+
new_practice = result["data"]["practice_problems"]
|
| 210 |
+
|
| 211 |
+
first_lesson_ui = update_ui_for_lesson(0, new_curriculum)
|
| 212 |
+
|
| 213 |
+
return {
|
| 214 |
+
dynamic_curriculum: new_curriculum,
|
| 215 |
+
dynamic_practice_problems: new_practice,
|
| 216 |
+
lesson_index: 0,
|
| 217 |
+
practice_attempts: 0,
|
| 218 |
+
learning_ui: gr.update(visible=True),
|
| 219 |
+
topic_selection_ui: gr.update(visible=False),
|
| 220 |
+
generation_status: gr.update(visible=False),
|
| 221 |
+
**first_lesson_ui
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
def update_ui_for_lesson(index, current_curriculum):
|
| 225 |
+
lesson = current_curriculum[index]
|
| 226 |
+
check_data = lesson.get("check", {}) # Safer access
|
| 227 |
+
|
| 228 |
+
total_lessons = len(current_curriculum)
|
| 229 |
+
completed_lessons = index
|
| 230 |
+
percentage = int((completed_lessons / total_lessons) * 100)
|
| 231 |
+
bar_length = 30
|
| 232 |
+
filled_length = int(bar_length * percentage / 100)
|
| 233 |
+
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
| 234 |
+
progress_text = f"**Progress:** {bar} {percentage}% ({completed_lessons}/{total_lessons} Lessons)"
|
| 235 |
+
|
| 236 |
+
return {
|
| 237 |
+
lesson_index: index,
|
| 238 |
+
lesson_progress: f"### Module: {lesson.get('module', 'Module')} - Lesson {index + 1} / {total_lessons}",
|
| 239 |
+
explanation_box: lesson.get('explanation', ''),
|
| 240 |
+
check_ui: gr.update(visible=True),
|
| 241 |
+
check_question: f"**Question:** {check_data.get('question', '')}",
|
| 242 |
+
mcq_input: gr.update(choices=check_data.get('options', []), value=None),
|
| 243 |
+
lesson_feedback_box: "",
|
| 244 |
+
next_button: gr.update(visible=False),
|
| 245 |
+
progress_bar: progress_text
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
def process_answer(index, mcq_answer, current_curriculum):
|
| 249 |
+
lesson = current_curriculum[index]
|
| 250 |
+
check_data = lesson.get("check", {})
|
| 251 |
+
is_correct = str(mcq_answer).strip().lower() == str(check_data.get('answer', '')).strip().lower()
|
| 252 |
+
if is_correct:
|
| 253 |
+
return { lesson_feedback_box: "✅ **Correct!**", check_ui: gr.update(visible=False), next_button: gr.update(visible=True) }
|
| 254 |
+
else:
|
| 255 |
+
return { lesson_feedback_box: "❌ Not quite. Try again.", check_ui: gr.update(visible=True), next_button: gr.update(visible=False) }
|
| 256 |
+
|
| 257 |
+
def on_next_lesson(index, current_curriculum, current_practice):
|
| 258 |
+
new_index = index + 1
|
| 259 |
+
if new_index < len(current_curriculum):
|
| 260 |
+
return update_ui_for_lesson(new_index, current_curriculum)
|
| 261 |
+
else:
|
| 262 |
+
# End of lessons, show practice problem
|
| 263 |
+
practice_problem = current_practice[0]
|
| 264 |
+
total_lessons = len(current_curriculum)
|
| 265 |
+
bar = '█' * 30
|
| 266 |
+
progress_text = f"**Progress:** {bar} 100% ({total_lessons}/{total_lessons} Lessons)"
|
| 267 |
+
|
| 268 |
+
return {
|
| 269 |
+
lesson_progress: "### All Lessons Complete! Now for the Practice Arena.",
|
| 270 |
+
explanation_box: "",
|
| 271 |
+
check_ui: gr.update(visible=False),
|
| 272 |
+
next_button: gr.update(visible=False),
|
| 273 |
+
practice_ui: gr.update(visible=True),
|
| 274 |
+
practice_problem_statement: practice_problem["problem_statement"],
|
| 275 |
+
practice_code_input: practice_problem["starter_code"],
|
| 276 |
+
progress_bar: progress_text,
|
| 277 |
+
lesson_index: new_index
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
def process_practice_answer(user_code, current_practice):
|
| 281 |
+
problem = current_practice[0]
|
| 282 |
+
result = execute_practice_code(user_code)
|
| 283 |
+
|
| 284 |
+
attempts = 0
|
| 285 |
+
if not result['success']:
|
| 286 |
+
return {
|
| 287 |
+
practice_feedback_box: f"❌ **Code Error:**\n\n```\n{result['error']}\n```",
|
| 288 |
+
hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
|
| 289 |
+
practice_attempts: attempts
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
var_name = problem['check_variable']
|
| 293 |
+
expected_value = problem['expected_result']
|
| 294 |
+
actual_value = result['scope'].get(var_name, f"Variable '{var_name}' not found!")
|
| 295 |
+
|
| 296 |
+
if str(actual_value) == str(expected_value):
|
| 297 |
+
return {
|
| 298 |
+
practice_feedback_box: f"✅ **Success!** The variable `{var_name}` has the correct value. Great job! Now, let the AI Code Coach show you how to improve it.",
|
| 299 |
+
hint_button: gr.update(visible=False), coach_button: gr.update(visible=True),
|
| 300 |
+
practice_attempts: 0
|
| 301 |
+
}
|
| 302 |
+
else:
|
| 303 |
+
return {
|
| 304 |
+
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.",
|
| 305 |
+
hint_button: gr.update(visible=True), coach_button: gr.update(visible=False),
|
| 306 |
+
practice_attempts: attempts
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
def provide_hint(user_code, current_practice, attempts):
|
| 310 |
+
new_attempts = attempts + 1
|
| 311 |
+
problem = current_practice[0]
|
| 312 |
+
|
| 313 |
+
if new_attempts > MAX_HINTS:
|
| 314 |
+
return {
|
| 315 |
+
practice_feedback_box: f"**Hint Limit Reached ({MAX_HINTS}/{MAX_HINTS}).**",
|
| 316 |
+
hint_button: gr.update(visible=False),
|
| 317 |
+
practice_attempts: new_attempts
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
result = execute_practice_code(user_code)
|
| 321 |
+
|
| 322 |
+
if new_attempts == 1 and result['success']:
|
| 323 |
+
hint = problem.get("background_knowledge", "No background knowledge available.")
|
| 324 |
+
else:
|
| 325 |
+
actual_value = "an error"
|
| 326 |
+
if result['success']:
|
| 327 |
+
actual_value = result['scope'].get(problem['check_variable'], "Not Found")
|
| 328 |
+
hint = get_socratic_hint(problem, user_code, actual_value, error_message=result['error'])
|
| 329 |
+
|
| 330 |
+
return {
|
| 331 |
+
practice_feedback_box: f"**(Hint {new_attempts}/{MAX_HINTS})** {hint}",
|
| 332 |
+
hint_button: gr.update(visible=True),
|
| 333 |
+
practice_attempts: new_attempts
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
def coach_code(user_code, current_practice):
|
| 337 |
+
problem = current_practice[0]
|
| 338 |
+
coaching_text = get_code_coaching(problem, user_code)
|
| 339 |
+
return {
|
| 340 |
+
coach_feedback_box: gr.update(visible=True, value=coaching_text),
|
| 341 |
+
coach_button: gr.update(interactive=False),
|
| 342 |
+
success_message: gr.update(visible=True),
|
| 343 |
+
start_new_topic_btn: gr.update(visible=True)
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
def reset_to_start():
|
| 347 |
+
return {
|
| 348 |
+
topic_selection_ui: gr.update(visible=True),
|
| 349 |
+
learning_ui: gr.update(visible=False),
|
| 350 |
+
practice_ui: gr.update(visible=False),
|
| 351 |
+
success_message: gr.update(visible=False),
|
| 352 |
+
start_new_topic_btn: gr.update(visible=False),
|
| 353 |
+
coach_feedback_box: gr.update(visible=False, value=""),
|
| 354 |
+
coach_button: gr.update(visible=False, interactive=True),
|
| 355 |
+
hint_button: gr.update(visible=False),
|
| 356 |
+
practice_feedback_box: "",
|
| 357 |
+
generation_status: ""
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
# --- EVENT LISTENERS ---
|
| 361 |
+
generate_lesson_btn.click(
|
| 362 |
+
fn=generate_and_start_lesson,
|
| 363 |
+
inputs=[topic_input],
|
| 364 |
+
outputs=[topic_selection_ui, generation_status, dynamic_curriculum, dynamic_practice_problems, lesson_index, practice_attempts, learning_ui, lesson_progress, explanation_box, check_ui, check_question, mcq_input, lesson_feedback_box, next_button, progress_bar]
|
| 365 |
+
)
|
| 366 |
+
submit_button.click(
|
| 367 |
+
fn=process_answer,
|
| 368 |
+
inputs=[lesson_index, mcq_input, dynamic_curriculum],
|
| 369 |
+
outputs=[lesson_feedback_box, check_ui, next_button]
|
| 370 |
+
)
|
| 371 |
+
next_button.click(
|
| 372 |
+
fn=on_next_lesson,
|
| 373 |
+
inputs=[lesson_index, dynamic_curriculum, dynamic_practice_problems],
|
| 374 |
+
outputs=[lesson_progress, explanation_box, check_ui, next_button, practice_ui, practice_problem_statement, practice_code_input, progress_bar, lesson_index]
|
| 375 |
+
)
|
| 376 |
+
submit_practice_button.click(
|
| 377 |
+
fn=process_practice_answer,
|
| 378 |
+
inputs=[practice_code_input, dynamic_practice_problems],
|
| 379 |
+
outputs=[practice_feedback_box, hint_button, coach_button, practice_attempts]
|
| 380 |
+
)
|
| 381 |
+
hint_button.click(
|
| 382 |
+
fn=provide_hint,
|
| 383 |
+
inputs=[practice_code_input, dynamic_practice_problems, practice_attempts],
|
| 384 |
+
outputs=[practice_feedback_box, hint_button, practice_attempts]
|
| 385 |
+
)
|
| 386 |
+
coach_button.click(
|
| 387 |
+
fn=coach_code,
|
| 388 |
+
inputs=[practice_code_input, dynamic_practice_problems],
|
| 389 |
+
outputs=[coach_feedback_box, coach_button, success_message, start_new_topic_btn]
|
| 390 |
+
)
|
| 391 |
+
start_new_topic_btn.click(
|
| 392 |
+
fn=reset_to_start,
|
| 393 |
+
inputs=None,
|
| 394 |
+
outputs=[topic_selection_ui, learning_ui, practice_ui, success_message, start_new_topic_btn, coach_feedback_box, coach_button, hint_button, practice_feedback_box, generation_status]
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
if __name__ == "__main__":
|
| 398 |
+
demo.launch(debug=True)
|