""" Gradio web interface for AI Examiner Agent. Run: python app.py """ import gradio as gr from agent import ExaminerAgent _agent: ExaminerAgent | None = None def init_exam(api_key: str): """Called when the user clicks 'Start Exam'. Resets chat completely.""" global _agent if not api_key.strip(): return [{ "role": "assistant", "content": "Please enter your Groq API key first." }], gr.update(interactive=False) try: _agent = ExaminerAgent(api_key.strip()) opening = _agent.start() return [{"role": "assistant", "content": opening}], gr.update(interactive=True) except Exception as e: _agent = None err = str(e) if "401" in err or "invalid_api_key" in err or "Invalid API Key" in err: msg = "Invalid API key. Please check your Groq API key at console.groq.com and try again." elif "429" in err or "rate_limit" in err: msg = "Rate limit reached. Your daily token quota is exhausted. Please wait until tomorrow or upgrade your Groq plan." elif "403" in err: msg = "Access denied. Your API key does not have permission to use this model." elif "404" in err: msg = "Model not found. Please contact support." elif "connection" in err.lower() or "timeout" in err.lower(): msg = "Connection error. Please check your internet connection and try again." else: msg = f"Error: {e}" return [{"role": "assistant", "content": msg}], gr.update(interactive=False) def user_message(message: str, history: list): """Called when the student sends a message.""" global _agent if not message.strip(): return history, "" if _agent is None: return history + [ {"role": "user", "content": message}, {"role": "assistant", "content": "Please click Start Exam first."}, ], "" # Block messages after exam is finished if _agent.exam_finished: return history + [ {"role": "user", "content": message}, {"role": "assistant", "content": "The exam is already finished. Click Start Exam to begin a new session."}, ], "" history = history + [{"role": "user", "content": message}] try: reply = _agent.chat(message) except Exception as e: err = str(e) if "429" in err or "rate_limit" in err: reply = "Rate limit reached. Daily quota exhausted — please try again tomorrow." elif "401" in err or "invalid_api_key" in err: reply = "Invalid API key. Please restart and enter a valid Groq key." elif "connection" in err.lower() or "timeout" in err.lower(): reply = "Connection error. Please check your internet and try again." elif "tool_use_failed" in err or "Failed to call a function" in err: # Leaked function call that wasn't caught — restart agent turn try: reply = _agent.chat("Please continue.") except Exception: reply = "Model error. Please try sending your message again." else: reply = f"Error: {e}" history = history + [{"role": "assistant", "content": reply}] return history, "" # ─── UI ────────────────────────────────────────────────────────────────────── CSS = """ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap'); body, .gradio-container { background: #0d0f14 !important; font-family: 'Syne', sans-serif !important; } .title-block { text-align: center; padding: 2rem 1rem 1rem; } .title-block h1 { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 2.6rem; letter-spacing: -1px; background: linear-gradient(135deg, #e2ff5d 0%, #00ffc2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0; } .title-block p { color: #8b95a8; font-family: 'Space Mono', monospace; font-size: 0.82rem; margin-top: 0.4rem; } .gr-button-primary { background: linear-gradient(135deg, #e2ff5d, #00ffc2) !important; color: #0d0f14 !important; font-family: 'Space Mono', monospace !important; font-weight: 700 !important; border: none !important; border-radius: 6px !important; } .gr-button-primary:hover { filter: brightness(1.1) !important; } label { color: #8b95a8 !important; font-family: 'Space Mono', monospace !important; font-size: 0.78rem !important; } input, textarea { background: #141820 !important; border: 1px solid #2a3040 !important; color: #e8ecf4 !important; border-radius: 6px !important; } .info-box { background: #141820; border: 1px solid #2a3040; border-radius: 8px; padding: 1rem 1.2rem; font-family: 'Space Mono', monospace; font-size: 0.75rem; color: #5a6478; line-height: 1.7; } .info-box strong { color: #e2ff5d; } """ with gr.Blocks(title="AI Examiner Agent") as demo: gr.HTML("""

⬡ AI Examiner Agent

NLP course · oral exam simulation · powered by AI

""") with gr.Row(): with gr.Column(scale=1, min_width=260): gr.HTML("""
How it works
1. Paste your Groq API key
2. Click Start Exam
3. Tell the bot your name & email
4. Answer NLP questions
5. Get your score & feedback

Demo students
test@test.com / test
""") api_key = gr.Textbox( label="Groq API Key", placeholder="gsk_...", type="password", lines=1, ) start_btn = gr.Button("▶ Start Exam", variant="primary") with gr.Column(scale=3): chatbot = gr.Chatbot( label="Exam Chat", height=520, show_label=False, layout="bubble", ) with gr.Row(): msg_input = gr.Textbox( placeholder="Type your answer here…", show_label=False, lines=1, scale=5, interactive=False, ) send_btn = gr.Button("Send →", scale=1, variant="primary") # Start Exam — clears chat history completely, creates new agent start_btn.click( fn=init_exam, inputs=[api_key], outputs=[chatbot, msg_input], ) send_btn.click( fn=user_message, inputs=[msg_input, chatbot], outputs=[chatbot, msg_input], ) msg_input.submit( fn=user_message, inputs=[msg_input, chatbot], outputs=[chatbot, msg_input], ) if __name__ == "__main__": demo.launch(share=False, css=CSS)