Lab6 / app.py
Denysyk's picture
Update app.py
37f97ec verified
"""
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("""
<div class="title-block">
<h1>⬑ AI Examiner Agent</h1>
<p>NLP course Β· oral exam simulation Β· powered by AI</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1, min_width=260):
gr.HTML("""
<div class="info-box">
<strong>How it works</strong><br>
1. Paste your Groq API key<br>
2. Click <em>Start Exam</em><br>
3. Tell the bot your name &amp; email<br>
4. Answer NLP questions<br>
5. Get your score &amp; feedback
<br><br>
<strong>Demo students</strong><br>
test@test.com / test
</div>
""")
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)