import os import gradio as gr from openai import OpenAI import subprocess import tempfile client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # --- Core logic --- def analyze_code(language, code): error_output = "" if language.lower() == "python": with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as tmp: tmp.write(code.encode("utf-8")) tmp.flush() try: subprocess.run( ["python3", tmp.name], check=True, capture_output=True, text=True, timeout=5, ) except subprocess.CalledProcessError as e: error_output = e.stderr except Exception as e: error_output = str(e) else: error_output = "Automatic execution only supported for Python. Please paste your error manually." if not error_output.strip(): error_output = "✅ No runtime error detected. Let's review for logic or syntax improvements." return help_with_code(language, code, error_output) def help_with_code(language, code, error): prompt = f""" You are a skilled coding instructor identifying skill gaps in students learning {language}. --- CODE START --- {code} --- CODE END --- --- ERROR --- {error} --- END ERROR --- Your task: 1. Identify my coding skill gaps (syntax, logic, structure, or language fundamentals). 2. Explain clearly what went wrong and why. 3. Suggest one or two short, focused practice exercises to improve that specific skill. 4. Do NOT rewrite or fix the code. """ try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.4, ) return response.choices[0].message.content.strip() except Exception as e: err_msg = str(e) # ✅ Handle inactive billing error cleanly if "billing_not_active" in err_msg or "Error code: 429" in err_msg: return ( "⚠️ Please contact an RC staff member — the billing is currently not active." ) # Generic fallback return "⚠️ An unexpected error occurred. Please try again later or verify your API key." # --- Gradio UI --- with gr.Blocks( theme="soft", css=""" /* === Apply theme colors immediately === */ body, .gradio-container, .dark, [data-theme="dark"] { background-color: var(--body-background-fill) !important; color: var(--body-text-color) !important; transition: none !important; } /* === Base Theme Variables (Light Mode) === */ :root { --body-background-fill: #ffffff; --body-text-color: #1a1a1a; --codebox-background: #f8f8f8; --codebox-border: #EDEBEB; --scrollbar-thumb: #c1c1c1; --scrollbar-track: #f8f8f8; } /* 🌙 Dark Mode Theme */ :root .dark, [data-theme="dark"] { --body-background-fill: #1e1e1f !important; --body-text-color: #e5e5e5 !important; --codebox-background: #2a2a2b !important; --codebox-border: #3a3a3b !important; --scrollbar-thumb: #555 !important; --scrollbar-track: #2a2a2b !important; } /* 🧱 Code boxes, dropdowns, textareas unified */ #code-box, select, textarea, pre, code { background-color: var(--codebox-background) !important; border: 1px solid var(--codebox-border) !important; border-radius: 6px !important; color: var(--body-text-color) !important; transition: none !important; } /* ✨ Consistent scrollbar theme */ #code-box::-webkit-scrollbar, textarea::-webkit-scrollbar, pre::-webkit-scrollbar { width: 8px; height: 8px; } #code-box::-webkit-scrollbar-thumb, textarea::-webkit-scrollbar-thumb, pre::-webkit-scrollbar-thumb { background-color: var(--scrollbar-thumb); border-radius: 10px; } #code-box::-webkit-scrollbar-track, textarea::-webkit-scrollbar-track, pre::-webkit-scrollbar-track { background-color: var(--scrollbar-track); } /* 🎯 Submit button */ #submit-btn { background-color: #e40014 !important; color: white !important; border: none !important; border-radius: 6px !important; transition: background-color 0.2s ease-in-out; margin-top: 6px !important; width: 100%; } #submit-btn:hover { background-color: #c00010 !important; } /* 🧩 Compact layout */ .gr-block, .gr-form, .gr-column, .gr-row { gap: 4px !important; padding: 0 !important; margin: 0 !important; } .gr-markdown { margin: 0 !important; padding: 2px !important; line-height: 1.4 !important; } /* ✅ Feedback area spacing */ .gr-markdown p { margin-top: 0 !important; margin-bottom: 4px !important; } /* Prevent reflow on feedback render */ .gr-column > *:not(:last-child) { margin-bottom: 4px !important; } """ ) as demo: with gr.Row(equal_height=True): with gr.Column(scale=2): # Removed markdown labels to reduce vertical spacing language = gr.Dropdown( ["JavaScript", "HTML", "CSS", "Python"], value="Python", show_label=True, label="Language", ) code_input = gr.Code( language="python", lines=22, show_label=True, label="Your Code", elem_id="code-box", ) analyze_button = gr.Button("Submit", variant="secondary", elem_id="submit-btn") with gr.Column(scale=1): output = gr.Markdown("*Feedback will show here!*") analyze_button.click(analyze_code, inputs=[language, code_input], outputs=output) demo.launch()