File size: 7,269 Bytes
5d2b004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a5c4a3
5d2b004
 
 
 
 
 
 
 
3a4d366
 
 
 
 
 
 
 
 
 
 
 
 
 
5d2b004
 
 
 
 
 
 
 
 
 
 
 
37f97ec
5d2b004
 
 
 
 
 
37f97ec
5d2b004
 
 
 
 
 
 
3a4d366
 
 
 
 
 
 
37f97ec
 
 
 
 
 
3a4d366
37f97ec
5d2b004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f97ec
5d2b004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
"""
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)