sarun25 commited on
Commit
3c2a6c6
·
1 Parent(s): de8152b

feat: integrate ui, validation, prompt, api and output

Browse files
Files changed (2) hide show
  1. app.py +231 -206
  2. ui_module.py +322 -0
app.py CHANGED
@@ -1,236 +1,261 @@
1
- """
2
- Python Code Evaluator — SE-Group1
3
- COS60011 Technology Design Project
4
- Implements the 5-module architecture from the design document:
5
- 1. UI Module — Gradio web interface
6
- 2. Validation & Flow — Input validation and routing
7
- 3. Pre-processing — 6-Element structured prompt builder
8
- 4. Generation — Gemma-3 LLM via Hugging Face
9
- 5. Output — Parse raw LLM output into structured results
10
- """
11
-
12
  import os
13
  import re
14
- import gradio as gr
 
 
 
 
 
 
15
  from google import genai
 
 
 
 
 
16
 
17
  # ---------------------------------------------------------------------------
18
- # MODULE 3 — Pre-processing Module (6-Element Framework)
 
 
19
  # ---------------------------------------------------------------------------
20
- def build_prompt(description: str, code: str) -> str:
21
- """
22
- Constructs a structured prompt using the 6-Element Framework:
23
- 1. Role — Who the LLM is
24
- 2. Context — Background information
25
- 3. Input Data — The user's description and code
26
- 4. Task — What the LLM must do
27
- 5. Constraints— Boundaries for the response
28
- 6. Output — Expected format
29
- """
30
- prompt = f"""<start_of_turn>user
31
- ### ROLE
32
- You are an expert Python code reviewer. Your sole task is to determine whether the
33
- provided Python code correctly implements the behaviour described in the user's
34
- requirements description.
35
- ### CONTEXT
36
- Developers sometimes write code that does not fully satisfy the requirements they
37
- were given. You will analyse the semantic relationship between a natural-language
38
- description and a Python code snippet, then produce a structured evaluation report.
39
- ### INPUT DATA
40
- **Requirements Description:**
41
- {description.strip()}
42
- **Python Code:**
43
- ```python
44
- {code.strip()}
45
- ```
46
- ### TASK
47
- 1. Read the requirements description carefully.
48
- 2. Analyse the Python code line by line.
49
- 3. Determine whether the code fulfils ALL requirements stated in the description.
50
- 4. Estimate an accuracy percentage (0–100) reflecting how completely the code
51
- matches the description.
52
- 5. List any specific requirements that are missing or incorrectly implemented.
53
- ### CONSTRAINTS
54
- - Do NOT execute the code.
55
- - Base your evaluation solely on static code analysis and logical reasoning.
56
- - Keep feedback concise, clear, and actionable.
57
- - Your response MUST follow the output format exactly.
58
- ### OUTPUT FORMAT
59
- Respond only with the following structure — no extra text before or after:
60
- RESULT: <PASS or FAIL>
61
- ACCURACY: <integer 0-100>%
62
- SUMMARY: <one sentence overall assessment>
63
- ISSUES:
64
- - <issue 1, or "None" if code fully matches the description>
65
- - <issue 2>
66
- ...
67
- <end_of_turn>
68
- <start_of_turn>model
69
- """
70
- return prompt
71
-
72
-
73
  # ---------------------------------------------------------------------------
74
- # MODULE 5 Output Module
75
  # ---------------------------------------------------------------------------
76
- def parse_output(raw: str) -> dict:
77
- """
78
- Extracts structured fields from the LLM's raw text response.
79
- Returns a dict with keys: result, accuracy, summary, issues.
80
- Falls back gracefully if parsing fails.
81
- """
82
- if "<start_of_turn>model" in raw:
83
- raw = raw.split("<start_of_turn>model")[-1]
84
-
85
- result_match = re.search(r"RESULT:\s*(PASS|FAIL)", raw, re.IGNORECASE)
86
- accuracy_match = re.search(r"ACCURACY:\s*(\d{1,3})%?", raw, re.IGNORECASE)
87
- summary_match = re.search(r"SUMMARY:\s*(.+)", raw, re.IGNORECASE)
88
- issues_match = re.search(r"ISSUES:\s*([\s\S]+)", raw, re.IGNORECASE)
89
-
90
- result = result_match.group(1).upper() if result_match else "UNKNOWN"
91
- accuracy = int(accuracy_match.group(1)) if accuracy_match else -1
92
- summary = summary_match.group(1).strip() if summary_match else "Could not extract summary."
93
-
94
- if issues_match:
95
- raw_issues = issues_match.group(1).strip()
96
- issues = [
97
- line.lstrip("-•* ").strip()
98
- for line in raw_issues.splitlines()
99
- if line.strip() and line.strip() not in ("-", "•")
100
- ]
101
- else:
102
- issues = ["Could not extract issues from model response."]
103
-
104
- return {
105
- "result": result,
106
- "accuracy": accuracy,
107
- "summary": summary,
108
- "issues": issues,
109
- "raw": raw.strip(),
110
  }
111
-
112
-
113
- def format_for_display(parsed: dict) -> tuple[str, str, str]:
114
- """
115
- Converts the parsed dict into three Gradio-friendly strings.
116
- """
117
- emoji = "✅" if parsed["result"] == "PASS" else ("❌" if parsed["result"] == "FAIL" else "⚠️")
118
- verdict = f"{emoji} {parsed['result']}"
119
-
120
- acc_str = f"{parsed['accuracy']}%" if parsed["accuracy"] >= 0 else "N/A"
121
- metrics = f"Accuracy: {acc_str}\n\nSummary: {parsed['summary']}"
122
-
123
- issues_text = "\n".join(f"• {issue}" for issue in parsed["issues"])
124
- return verdict, metrics, issues_text
125
-
126
-
127
  # ---------------------------------------------------------------------------
128
- # MODULE 4Generation Module (inference call)
129
  # ---------------------------------------------------------------------------
130
- MODEL_ID= "gemma-4-31b-it" # LLM Model
131
- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # 1. Fetch the API Key from the environment (configured in Settings -> Secrets)
132
-
133
- def generate_response(prompt):
134
- # Check if the API Key is provided
135
- if not GOOGLE_API_KEY:
136
- return "⚠️ Error: API Key not found! Please configure GOOGLE_API_KEY in the Settings -> Variables and secrets page of your Space."
137
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  try:
139
- # 2. Initialise the Client using the new SDK
140
- client = genai.Client(api_key=GOOGLE_API_KEY)
141
-
142
- # 3. Send the prompt to Google AI Studio for processing
143
- response = client.models.generate_content(
144
- model=MODEL_ID,
145
- contents=prompt
146
  )
147
- return response.text
148
- except Exception as e:
149
- return f"❌ An error occurred: {str(e)}"
150
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  # ---------------------------------------------------------------------------
152
- # MODULE 2 Validation & Flow Management Module
 
 
 
 
 
 
 
 
 
153
  # ---------------------------------------------------------------------------
 
 
154
  def validate_and_evaluate(description: str, code: str):
155
- """
156
- Entry point called by the UI module.
157
- Returns (verdict, metrics, issues, error_message).
158
- """
159
- # --- Validation ---
160
- if not description or not description.strip():
161
- return "", "", "", "⚠️ Please provide a requirements description."
162
- if not code or not code.strip():
163
- return "", "", "", "⚠️ Please provide Python code to evaluate."
164
-
165
- # --- Check token is available ---
166
- if not GOOGLE_API_KEY:
167
- return "", "", "", "❌ GOOGLE_API_KEY secret is not set. Add it in Space Settings → Variables and secrets."
168
-
169
- # --- Pre-processing ---
170
  prompt = build_prompt(description, code)
171
-
172
- # --- Generation ---
173
  try:
174
  raw_output = generate_response(prompt)
175
  except Exception as exc:
176
  return "", "", "", f"❌ Model error: {exc}"
177
-
178
- # --- Output ---
179
- parsed = parse_output(raw_output)
180
- verdict, metrics, issues = format_for_display(parsed)
181
-
182
  return verdict, metrics, issues, ""
183
-
184
-
185
  # ---------------------------------------------------------------------------
186
- # MODULE 1UI Module (Gradio)
187
  # ---------------------------------------------------------------------------
188
- with gr.Blocks(title="Python Code Evaluator — SE-Group1") as demo:
 
 
 
189
 
190
- gr.Markdown(
191
- """
192
- # 🐍 Python Code Evaluator
193
- **COS60011 — SE-Group1** | Powered by Gemma-3 via Hugging Face
194
- Enter a **requirements description** and your **Python code**.
195
- The system will evaluate whether the code correctly implements the described behaviour.
196
- """
197
- )
198
 
199
- with gr.Row():
200
- with gr.Column(scale=1):
201
- description_input = gr.Textbox(
202
- label="📋 Requirements Description",
203
- placeholder="Describe what the Python code should do...",
204
- lines=8,
205
- )
206
- code_input = gr.Code(
207
- label="🐍 Python Code",
208
- language="python",
209
- lines=15,
210
- value='def add(a, b):\n return a + b\n',
211
- )
212
- submit_btn = gr.Button("▶ Evaluate", variant="primary", size="lg")
213
 
214
- with gr.Column(scale=1):
215
- error_output = gr.Textbox(label="⚠️ Validation Error", visible=True, interactive=False)
216
- verdict_output = gr.Textbox(label="🏁 Verdict", interactive=False, lines=1)
217
- metrics_output = gr.Textbox(label="📊 Metrics & Summary", interactive=False, lines=4)
218
- issues_output = gr.Textbox(label="🔍 Issues Found", interactive=False, lines=8)
 
 
219
 
220
- submit_btn.click(
221
- fn=validate_and_evaluate,
222
- inputs=[description_input, code_input],
223
- outputs=[verdict_output, metrics_output, issues_output, error_output],
224
- )
225
 
226
- gr.Markdown(
227
- """
228
- ---
229
- > **Note:** This tool performs *static analysis* only it does not execute the code.
230
- > Results should be treated as supplementary feedback, not a replacement for unit testing.
231
- """
232
- )
233
 
234
- if __name__ == "__main__":
235
- demo.launch(share=False, theme=gr.themes.Soft(primary_hue="blue"))
236
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
+ import ast
4
+ import sys
5
+ import json
6
+ import time
7
+ from collections import deque
8
+
9
+ from dotenv import load_dotenv
10
  from google import genai
11
+ from google.genai import types
12
+
13
+ load_dotenv()
14
+
15
+ from ui_module import *
16
 
17
  # ---------------------------------------------------------------------------
18
+ # Initialization:
19
+ # 1. Set up the Google API client
20
+ # 2. Define the response schema
21
  # ---------------------------------------------------------------------------
22
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
23
+ if not GOOGLE_API_KEY:
24
+ sys.exit("API Key not found! Please configure GOOGLE_API_KEY in Settings → Variables and secrets.")
25
+ client = genai.Client(api_key=GOOGLE_API_KEY)
26
+ LLM_MODEL = os.environ.get("LLM_MODEL")
27
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  # ---------------------------------------------------------------------------
29
+ # MODULE 5 - Output Schema Definition
30
  # ---------------------------------------------------------------------------
31
+ response_schema = types.Schema(
32
+ type=types.Type.OBJECT,
33
+ required=["result", "accuracy", "summary", "issues"],
34
+ properties={
35
+
36
+ # → VERDICT card: "Pass" or "Fail"
37
+ "result": types.Schema(
38
+ type=types.Type.STRING,
39
+ enum=["Pass", "Fail"]
40
+ ),
41
+
42
+ # → ACCURACY card: 0–100 integer (renders as "96%")
43
+ "accuracy": types.Schema(
44
+ type=types.Type.INTEGER,
45
+ ),
46
+
47
+ # SUMMARY card: short prose explanation
48
+ "summary": types.Schema(
49
+ type=types.Type.STRING,
50
+ ),
51
+
52
+ # → ISSUES DETECTED list: each bullet point
53
+ "issues": types.Schema(
54
+ type=types.Type.STRING,
55
+ )
 
 
 
 
 
 
 
 
 
56
  }
57
+ )
58
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  # ---------------------------------------------------------------------------
60
+ # MODULE 2Validation & Flow Management Module
61
  # ---------------------------------------------------------------------------
62
+ MIN_DESCRIPTION_CHARS = 20
63
+ MAX_DESCRIPTION_CHARS = 3000
64
+ MIN_CODE_CHARS = 10
65
+ MAX_CODE_CHARS = 8000
66
+ MAX_CODE_LINES = 300
67
+
68
+ FORBIDDEN_PATTERNS = [
69
+ r"ignore (all |previous |above )?instructions",
70
+ r"disregard (all |previous |above )?instructions",
71
+ r"you are now",
72
+ r"act as (a |an )?",
73
+ r"<\s*(script|iframe|object|embed)",
74
+ r"system\s*prompt",
75
+ r"jailbreak",
76
+ ]
77
+
78
+ PYTHON_KEYWORDS = {
79
+ "def", "class", "import", "from", "return", "if", "else", "elif",
80
+ "for", "while", "try", "except", "with", "lambda", "yield", "pass",
81
+ "raise", "assert", "in", "not", "and", "or", "True", "False", "None",
82
+ "print", "len", "range", "self",
83
+ }
84
+
85
+ class RateLimiter:
86
+ def __init__(self, max_calls: int = 5, window_seconds: int = 60):
87
+ self.max_calls = max_calls
88
+ self.window_seconds = window_seconds
89
+ self._timestamps: deque = deque()
90
+
91
+ def is_allowed(self) -> tuple[bool, str]:
92
+ now = time.time()
93
+ while self._timestamps and now - self._timestamps[0] > self.window_seconds:
94
+ self._timestamps.popleft()
95
+ if len(self._timestamps) >= self.max_calls:
96
+ wait = int(self.window_seconds - (now - self._timestamps[0])) + 1
97
+ return False, (
98
+ f"⏳ Rate limit reached — {self.max_calls} requests in "
99
+ f"{self.window_seconds}s. Please wait ~{wait}s and try again."
100
+ )
101
+ self._timestamps.append(now)
102
+ return True, ""
103
+
104
+ _rate_limiter = RateLimiter(max_calls=5, window_seconds=60)
105
+
106
+ def _check_forbidden(text: str) -> str | None:
107
+ lower = text.lower()
108
+ for pattern in FORBIDDEN_PATTERNS:
109
+ if re.search(pattern, lower):
110
+ return (
111
+ "Input contains disallowed content. "
112
+ "Please remove prompt-injection or HTML patterns and try again."
113
+ )
114
+ return None
115
+
116
+ def _looks_like_python(code: str) -> tuple[bool, str]:
117
+ tokens = set(re.findall(r"[A-Za-z_]\w*", code))
118
+ if not tokens.intersection(PYTHON_KEYWORDS):
119
+ return False, (
120
+ "🐍 The code doesn't appear to be Python — no recognisable Python "
121
+ "keywords found (e.g. def, class, import, return). "
122
+ "Please submit Python code only."
123
+ )
124
  try:
125
+ ast.parse(code)
126
+ except SyntaxError as exc:
127
+ line_hint = f" (line {exc.lineno})" if exc.lineno else ""
128
+ return False, (
129
+ f"🐍 Python syntax error{line_hint}: {exc.msg}. "
130
+ "Please fix the syntax error before evaluating."
 
131
  )
132
+ return True, ""
133
+
134
+ def validate_inputs(description: str, code: str) -> list[str]:
135
+ errors: list[str] = []
136
+
137
+ if not description or not description.strip():
138
+ errors.append("📋 Requirements description is required.")
139
+ if not code or not code.strip():
140
+ errors.append("🐍 Python code is required.")
141
+ if errors:
142
+ return errors
143
+
144
+ desc, code_ = description.strip(), code.strip()
145
+
146
+ if len(desc) < MIN_DESCRIPTION_CHARS:
147
+ errors.append(f"📋 Description too short ({len(desc)} chars) — minimum is {MIN_DESCRIPTION_CHARS} characters.")
148
+ if len(code_) < MIN_CODE_CHARS:
149
+ errors.append(f"🐍 Code too short ({len(code_)} chars) — minimum is {MIN_CODE_CHARS} characters.")
150
+ if len(desc) > MAX_DESCRIPTION_CHARS:
151
+ errors.append(f"📋 Description too long ({len(desc):,} chars) — max is {MAX_DESCRIPTION_CHARS:,} characters.")
152
+ if len(code_) > MAX_CODE_CHARS:
153
+ errors.append(f"🐍 Code too long ({len(code_):,} chars) — max is {MAX_CODE_CHARS:,} characters.")
154
+ if len(code_.splitlines()) > MAX_CODE_LINES:
155
+ errors.append(f"🐍 Code has too many lines ({len(code_.splitlines())}) — max is {MAX_CODE_LINES} lines.")
156
+
157
+ if err := _check_forbidden(desc):
158
+ errors.append(f"📋 {err}")
159
+ if err := _check_forbidden(code_):
160
+ errors.append(f"🐍 {err}")
161
+
162
+ if not errors:
163
+ is_python, py_error = _looks_like_python(code_)
164
+ if not is_python:
165
+ errors.append(py_error)
166
+
167
+ if not errors:
168
+ allowed, rate_msg = _rate_limiter.is_allowed()
169
+ if not allowed:
170
+ errors.append(rate_msg)
171
+
172
+ return errors
173
+
174
  # ---------------------------------------------------------------------------
175
+ # Format the evaluation results for display
176
+ # ----------------------------------------------------------------------------
177
+ def format_for_display(raw_output: dict) -> tuple[str, str, str]:
178
+ emoji = "✅" if raw_output["result"].upper() == "PASS" else ("❌" if raw_output["result"].upper() == "FAIL" else "⚠️")
179
+ verdict = f"{emoji} {raw_output['result']}"
180
+ acc_str = f"{raw_output['accuracy']}%" if raw_output["accuracy"] >= 0 else "N/A"
181
+ metrics = f"Accuracy: {acc_str}\n\nSummary: {raw_output['summary']}"
182
+ issues_text = raw_output["issues"]
183
+ return verdict, metrics, issues_text
184
+
185
  # ---------------------------------------------------------------------------
186
+ # Core function
187
+ # ----------------------------------------------------------------------------
188
  def validate_and_evaluate(description: str, code: str):
189
+ errors = validate_inputs(description, code)
190
+ if errors:
191
+ return "", "", "", "\n".join(f"{i+1}. {e}" for i, e in enumerate(errors))
 
 
 
 
 
 
 
 
 
 
 
 
192
  prompt = build_prompt(description, code)
 
 
193
  try:
194
  raw_output = generate_response(prompt)
195
  except Exception as exc:
196
  return "", "", "", f"❌ Model error: {exc}"
197
+
198
+ verdict, metrics, issues = format_for_display(raw_output)
 
 
 
199
  return verdict, metrics, issues, ""
200
+
 
201
  # ---------------------------------------------------------------------------
202
+ # MODULE 3Build the prompt for code evaluation
203
  # ---------------------------------------------------------------------------
204
+ def build_prompt(description, code):
205
+ prompt = f"""
206
+ #ROLE
207
+ You are a Python code reviewer. Your goal is to determine if the provided Python code strictly complies with the requirements mentioned by the user.
208
 
209
+ #CONTEXT
210
+ Developers may create code that does not entirely meet the specified requirements. You will examine the relationship between a natural-language description and a Python code sample and create a structured evaluation report.
 
 
 
 
 
 
211
 
212
+ #INPUT DATA
213
+ Description: {description}
214
+ Code to review:
215
+ ```python
216
+ {code}
217
+ ```
 
 
 
 
 
 
 
 
218
 
219
+ #TASK
220
+ 1. Carefully read the requirements description.
221
+ 2. Examine each line of the Python code.
222
+ 3. Determine whether the code fulfils ALL requirements stated in the description.
223
+ 4. Estimate the accuracy in percentages to assess how accurately the code matches the description.
224
+ 5. List specific requirements that are either absent or incorrectly applied.
225
+ 6. Base your evaluation solely on static code analysis and logical reasoning.
226
 
227
+ #CONSTRAINTS
228
+ 1. Do not execute the code.
229
+ 2. The output must be in the specified format.
230
+ 3. The output should be clear and concise.
 
231
 
232
+ #OUTPUT FORMAT (to be strictly followed without any changes)
233
+ - Result: either "Pass" or "Fail" (Pass if the code is functional and matches the description, Fail if it has bugs or doesn't match).
234
+ - Accuracy: an integer from 0 to 100 representing how well the code matches the description and is bug-free.
235
+ - Summary: a short prose explanation of the result (e.g. "Code correctly handles all described requirements including edge cases.").
236
+ - Issues: The value should be a single string containing a numbered list. For each item, include the severity (Error, Warning, or Info) followed by a dash and description of the issue (e.g. "Missing type hints on function signature"). List each issue on a new line starting with a number. Use newline characters (\n) to separate each line.
237
+ ...
 
238
 
239
+ """
240
+ return prompt
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # MODULE 4 — Generation Module
245
+ # ---------------------------------------------------------------------------
246
+ def generate_response(prompt: str) -> str:
247
+ response = client.models.generate_content(
248
+ model=LLM_MODEL,
249
+ contents=prompt,
250
+ config=types.GenerateContentConfig(
251
+ response_mime_type="application/json",
252
+ response_schema=response_schema,
253
+ )
254
+ )
255
+ return json.loads(response.text)
256
+
257
+ # Build the Gradio UI and connect it to the validation and evaluation function
258
+ app = build_ui(validate_and_evaluate)
259
+
260
+ # Launch the web application
261
+ app.launch()
ui_module.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+
4
+ # ---------------------------------------------------------------------------
5
+ # MODULE 1 — UI Module
6
+ # ---------------------------------------------------------------------------
7
+ CUSTOM_CSS = """
8
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;1,400&display=swap');
9
+ *, *::before, *::after { box-sizing: border-box; }
10
+ body, .gradio-container {
11
+ font-family: 'DM Sans', sans-serif !important;
12
+ background: #0D0F14 !important;
13
+ color: #E8EAF0 !important;
14
+ }
15
+ .gradio-container {
16
+ max-width: 1140px !important;
17
+ margin: 0 auto !important;
18
+ padding: 32px 24px !important;
19
+ }
20
+ .eval-header {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 16px;
24
+ padding: 0 0 28px;
25
+ border-bottom: 1px solid #2E3140;
26
+ margin-bottom: 28px;
27
+ }
28
+ .eval-header .logo {
29
+ width: 44px; height: 44px;
30
+ border-radius: 10px;
31
+ background: linear-gradient(135deg, #534AB7 0%, #1D9E75 100%);
32
+ display: flex; align-items: center; justify-content: center;
33
+ flex-shrink: 0;
34
+ font-size: 22px; line-height: 1;
35
+ }
36
+ .eval-header h1 {
37
+ font-size: 20px !important;
38
+ font-weight: 600 !important;
39
+ letter-spacing: -0.3px !important;
40
+ color: #E8EAF0 !important;
41
+ margin: 0 !important;
42
+ }
43
+ .eval-header .model-badge {
44
+ margin-left: auto;
45
+ font-family: 'Space Mono', monospace;
46
+ font-size: 10px;
47
+ background: #1E2028;
48
+ border: 1px solid #3A3E52;
49
+ color: #6A6E80;
50
+ padding: 4px 10px;
51
+ border-radius: 4px;
52
+ letter-spacing: 1.5px;
53
+ white-space: nowrap;
54
+ }
55
+ .gradio-textbox textarea,
56
+ .gradio-code textarea,
57
+ .gradio-textbox input {
58
+ background: #161820 !important;
59
+ border: 1px solid #2E3140 !important;
60
+ border-radius: 10px !important;
61
+ color: #E8EAF0 !important;
62
+ font-family: 'DM Sans', sans-serif !important;
63
+ font-size: 13.5px !important;
64
+ line-height: 1.7 !important;
65
+ padding: 14px 16px !important;
66
+ transition: border-color 0.15s !important;
67
+ resize: vertical !important;
68
+ }
69
+ .gradio-textbox textarea:focus,
70
+ .gradio-code textarea:focus {
71
+ border-color: #534AB7 !important;
72
+ outline: none !important;
73
+ box-shadow: 0 0 0 3px rgba(83, 74, 183, 0.15) !important;
74
+ }
75
+ .gradio-textbox textarea::placeholder {
76
+ color: #4A4E60 !important;
77
+ }
78
+ #code-input textarea {
79
+ font-family: 'Space Mono', monospace !important;
80
+ font-size: 12.5px !important;
81
+ line-height: 1.65 !important;
82
+ }
83
+ .gradio-textbox label span,
84
+ .gradio-code label span {
85
+ font-family: 'DM Sans', sans-serif !important;
86
+ font-size: 13px !important;
87
+ font-weight: 700 !important;
88
+ color: #E8EAF0 !important;
89
+ text-transform: uppercase !important;
90
+ letter-spacing: 0.8px !important;
91
+ }
92
+ #eval-btn {
93
+ background: #534AB7 !important;
94
+ border: none !important;
95
+ color: #fff !important;
96
+ font-family: 'DM Sans', sans-serif !important;
97
+ font-size: 14px !important;
98
+ font-weight: 500 !important;
99
+ padding: 12px 32px !important;
100
+ border-radius: 8px !important;
101
+ cursor: pointer !important;
102
+ transition: background 0.15s, transform 0.1s !important;
103
+ letter-spacing: -0.1px !important;
104
+ }
105
+ #eval-btn:hover { background: #7F77DD !important; transform: translateY(-1px) !important; }
106
+ #eval-btn:active { transform: translateY(0) !important; }
107
+ #clear-btn {
108
+ background: transparent !important;
109
+ border: 1px solid #3A3E52 !important;
110
+ color: #9DA0B0 !important;
111
+ font-family: 'DM Sans', sans-serif !important;
112
+ font-size: 13px !important;
113
+ padding: 12px 20px !important;
114
+ border-radius: 8px !important;
115
+ cursor: pointer !important;
116
+ transition: border-color 0.15s, color 0.15s !important;
117
+ }
118
+ #clear-btn:hover { border-color: #7F77DD !important; color: #E8EAF0 !important; }
119
+ .results-heading {
120
+ font-size: 11px !important;
121
+ font-weight: 500 !important;
122
+ color: #6A6E80 !important;
123
+ text-transform: uppercase !important;
124
+ letter-spacing: 1px !important;
125
+ padding: 0 0 16px !important;
126
+ border-bottom: 1px solid #2E3140 !important;
127
+ margin-bottom: 20px !important;
128
+ }
129
+ #verdict-out textarea {
130
+ background: #161820 !important; border: 1px solid #2E3140 !important;
131
+ border-radius: 10px !important; font-family: 'Space Mono', monospace !important;
132
+ font-size: 26px !important; font-weight: 700 !important;
133
+ text-align: center !important; padding: 20px !important;
134
+ color: #E8EAF0 !important; cursor: default !important;
135
+ }
136
+ #accuracy-out textarea {
137
+ background: #161820 !important; border: 1px solid #2E3140 !important;
138
+ border-radius: 10px !important; font-family: 'Space Mono', monospace !important;
139
+ font-size: 26px !important; font-weight: 700 !important;
140
+ text-align: center !important; padding: 20px !important;
141
+ color: #E8EAF0 !important; cursor: default !important;
142
+ }
143
+ #summary-out textarea {
144
+ background: #161820 !important; border: 1px solid #2E3140 !important;
145
+ border-radius: 10px !important; font-family: 'DM Sans', sans-serif !important;
146
+ font-size: 13.5px !important; line-height: 1.7 !important;
147
+ padding: 16px !important; color: #C0C3D0 !important; cursor: default !important;
148
+ }
149
+ #issues-out textarea {
150
+ background: #161820 !important; border: 1px solid #2E3140 !important;
151
+ border-radius: 10px !important; font-family: 'DM Sans', sans-serif !important;
152
+ font-size: 13.5px !important; line-height: 1.8 !important;
153
+ padding: 16px !important; color: #C0C3D0 !important;
154
+ cursor: default !important; min-height: 120px !important;
155
+ }
156
+ #error-out textarea {
157
+ background: #1A0E0E !important;
158
+ border: 1px solid #5a2020 !important;
159
+ border-radius: 10px !important;
160
+ font-family: 'DM Sans', sans-serif !important;
161
+ font-size: 13.5px !important;
162
+ padding: 14px 16px !important;
163
+ color: #F0997B !important;
164
+ cursor: default !important;
165
+ min-height: 120px !important;
166
+ width: 100% !important;
167
+ }
168
+ .divider { height: 1px; background: #2E3140; margin: 20px 0; }
169
+ footer { display: none !important; }
170
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
171
+ ::-webkit-scrollbar-track { background: transparent; }
172
+ ::-webkit-scrollbar-thumb { background: #3A3E52; border-radius: 3px; }
173
+ ::-webkit-scrollbar-thumb:hover { background: #534AB7; }
174
+ """
175
+
176
+ HEADER_HTML = """
177
+ <div class="eval-header">
178
+ <div class="logo">🔍</div>
179
+ <div><h1>Python Code Evaluator</h1></div>
180
+ <div class="model-badge">GEMMA-4 · 31B-IT</div>
181
+ </div>
182
+ """
183
+
184
+ RESULTS_HEADING_HTML = """
185
+ <div class="divider"></div>
186
+ <div class="results-heading">⬡ &nbsp; Evaluation Results</div>
187
+ """
188
+
189
+ INPUT_HINT_HTML = """
190
+ <div style="font-size:12px;color:#4A4E60;margin-top:-4px;padding-bottom:4px;">
191
+ Tip — paste your description and code above, then click
192
+ <strong style="color:#7F77DD">Evaluate</strong>.
193
+ </div>
194
+ """
195
+
196
+ def build_ui(evaluate_fn):
197
+
198
+ def _split_metrics(metrics_str: str):
199
+ acc, summary = "", ""
200
+ if not metrics_str:
201
+ return acc, summary
202
+ for line in metrics_str.splitlines():
203
+ if line.startswith("Accuracy:"):
204
+ acc = line.replace("Accuracy:", "").strip()
205
+ elif line.startswith("Summary:"):
206
+ summary = line.replace("Summary:", "").strip()
207
+ return acc, summary
208
+
209
+ def _ui_evaluate(description: str, code: str):
210
+ verdict, metrics, issues, error = evaluate_fn(description, code)
211
+ accuracy, summary = _split_metrics(metrics)
212
+
213
+ if error:
214
+ # Error occurred — show only the error box, hide all result boxes
215
+ return (
216
+ gr.update(value="", visible=False), # verdict
217
+ gr.update(value="", visible=False), # accuracy
218
+ gr.update(value="", visible=False), # summary
219
+ gr.update(value="", visible=False), # issues
220
+ gr.update(value=error, visible=True), # error ← only this shows
221
+ )
222
+
223
+ # No error — show all result boxes, hide the error box
224
+ if "PASS" in verdict.upper():
225
+ verdict_display = "✅ PASS"
226
+ elif "FAIL" in verdict.upper():
227
+ verdict_display = "❌ FAIL"
228
+ else:
229
+ verdict_display = verdict or ""
230
+
231
+ return (
232
+ gr.update(value=verdict_display, visible=True), # verdict
233
+ gr.update(value=accuracy, visible=True), # accuracy
234
+ gr.update(value=summary, visible=True), # summary
235
+ gr.update(value=issues, visible=True), # issues
236
+ gr.update(value="", visible=False), # error ← hidden when no error
237
+ )
238
+
239
+ with gr.Blocks(title="Python Code Evaluator") as ui:
240
+
241
+ gr.HTML(HEADER_HTML)
242
+
243
+ with gr.Row(equal_height=True):
244
+ description_input = gr.Textbox(
245
+ label="Requirements Description",
246
+ placeholder=(
247
+ "Describe what the Python code is supposed to do…\n\n"
248
+ "Example: Write a function that accepts a list of integers "
249
+ "and returns the sum of all even numbers. It should handle "
250
+ "empty lists by returning 0 and ignore non-integer values."
251
+ ),
252
+ lines=10,
253
+ max_lines=20,
254
+ elem_id="desc-input",
255
+ )
256
+ code_input = gr.Textbox(
257
+ label="Python Code",
258
+ placeholder=(
259
+ "# Paste your Python code here…\n\n"
260
+ "# Note: Ensure proper indentation for accurate evaluation.\n\n"
261
+ "def example(numbers):\n"
262
+ " total = 0\n"
263
+ " for item in numbers:\n"
264
+ " if item % 2 == 0:\n"
265
+ " total += item\n"
266
+ " return total\n"
267
+ ),
268
+ lines=10,
269
+ max_lines=30,
270
+ elem_id="code-input",
271
+ )
272
+
273
+ gr.HTML(INPUT_HINT_HTML)
274
+
275
+ with gr.Row():
276
+ eval_btn = gr.Button("Evaluate", variant="primary",
277
+ elem_id="eval-btn", scale=0)
278
+ clear_btn = gr.Button("Clear", variant="secondary",
279
+ elem_id="clear-btn", scale=0)
280
+
281
+ gr.HTML(RESULTS_HEADING_HTML)
282
+
283
+ with gr.Row(equal_height=True):
284
+ verdict_out = gr.Textbox(label="Verdict", interactive=False, elem_id="verdict-out", scale=1)
285
+ accuracy_out = gr.Textbox(label="Accuracy", interactive=False, elem_id="accuracy-out", scale=1)
286
+ summary_out = gr.Textbox(label="Summary", interactive=False, elem_id="summary-out", scale=2, lines=3)
287
+
288
+ issues_out = gr.Textbox(label="Issues Detected", interactive=False, lines=5, elem_id="issues-out")
289
+
290
+ with gr.Row():
291
+ error_out = gr.Textbox(
292
+ label="Status / Errors",
293
+ interactive=False,
294
+ visible=True,
295
+ elem_id="error-out",
296
+ scale=1,
297
+ )
298
+
299
+ gr.HTML("""
300
+ <div style="text-align:right;font-size:11px;color:#4A4E60;margin-top:8px;">
301
+ Press <kbd style="background:#1E2028;border:1px solid #3A3E52;border-radius:4px;
302
+ padding:2px 6px;font-family:'Space Mono',monospace;font-size:10px;color:#9DA0B0;">
303
+ Ctrl+Enter</kbd> inside any input to evaluate
304
+ </div>
305
+ """)
306
+
307
+ outputs = [verdict_out, accuracy_out, summary_out, issues_out, error_out]
308
+ eval_btn.click(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
309
+ description_input.submit(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
310
+ code_input.submit(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
311
+
312
+ def _clear():
313
+ return "", "", "", "", "", ""
314
+
315
+ clear_btn.click(
316
+ fn=_clear,
317
+ inputs=[],
318
+ outputs=[description_input, code_input,
319
+ verdict_out, accuracy_out, summary_out, issues_out],
320
+ )
321
+
322
+ return ui