ziyan14 commited on
Commit
72effac
·
verified ·
1 Parent(s): 3a0883e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +542 -0
app.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Python Code Evaluator — SE-Group1
3
+ COS60011 Technology Design Project
4
+ """
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # IMPORTS (all at top)
8
+ # ---------------------------------------------------------------------------
9
+ import os
10
+ import re
11
+ import ast
12
+ import time
13
+ from collections import deque
14
+ from dotenv import load_dotenv
15
+ import gradio as gr
16
+ from google import genai
17
+
18
+ load_dotenv()
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # MODULE 3 — Pre-processing Module
23
+ # ---------------------------------------------------------------------------
24
+ def build_prompt(description: str, code: str) -> str:
25
+ prompt = f"""<start_of_turn>user
26
+ ### ROLE
27
+ You are an expert Python code reviewer. Your sole task is to determine whether the
28
+ provided Python code correctly implements the behaviour described in the user's
29
+ requirements description.
30
+ ### CONTEXT
31
+ Developers sometimes write code that does not fully satisfy the requirements they
32
+ were given. You will analyse the semantic relationship between a natural-language
33
+ description and a Python code snippet, then produce a structured evaluation report.
34
+ ### INPUT DATA
35
+ **Requirements Description:**
36
+ {description.strip()}
37
+ **Python Code:**
38
+ ```python
39
+ {code.strip()}
40
+ ```
41
+ ### TASK
42
+ 1. Read the requirements description carefully.
43
+ 2. Analyse the Python code line by line.
44
+ 3. Determine whether the code fulfils ALL requirements stated in the description.
45
+ 4. Estimate an accuracy percentage (0–100) reflecting how completely the code
46
+ matches the description.
47
+ 5. List any specific requirements that are missing or incorrectly implemented.
48
+ ### CONSTRAINTS
49
+ - Do NOT execute the code.
50
+ - Base your evaluation solely on static code analysis and logical reasoning.
51
+ - Keep feedback concise, clear, and actionable.
52
+ - Your response MUST follow the output format exactly.
53
+ ### OUTPUT FORMAT
54
+ Respond only with the following structure — no extra text before or after:
55
+ RESULT: <PASS or FAIL>
56
+ ACCURACY: <integer 0-100>%
57
+ SUMMARY: <one sentence overall assessment>
58
+ ISSUES:
59
+ - <issue 1, or "None" if code fully matches the description>
60
+ - <issue 2>
61
+ ...
62
+ <end_of_turn>
63
+ <start_of_turn>model
64
+ """
65
+ return prompt
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # MODULE 4 — Generation Module
70
+ # ---------------------------------------------------------------------------
71
+ MODEL_ID = "gemma-4-31b-it"
72
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
73
+
74
+ def generate_response(prompt: str) -> str:
75
+ if not GOOGLE_API_KEY:
76
+ return (
77
+ "⚠️ Error: API Key not found! "
78
+ "Please configure GOOGLE_API_KEY in Settings → Variables and secrets."
79
+ )
80
+ try:
81
+ client = genai.Client(api_key=GOOGLE_API_KEY)
82
+ response = client.models.generate_content(model=MODEL_ID, contents=prompt)
83
+ return response.text
84
+ except Exception as e:
85
+ return f"❌ An error occurred during generation: {str(e)}"
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # MODULE 5 — Output Module
90
+ # ---------------------------------------------------------------------------
91
+ def parse_output(raw: str) -> dict:
92
+ if "<start_of_turn>model" in raw:
93
+ raw = raw.split("<start_of_turn>model")[-1]
94
+
95
+ result_match = re.search(r"RESULT:\s*(PASS|FAIL)", raw, re.IGNORECASE)
96
+ accuracy_match = re.search(r"ACCURACY:\s*(\d{1,3})%?", raw, re.IGNORECASE)
97
+ summary_match = re.search(r"SUMMARY:\s*(.+)", raw, re.IGNORECASE)
98
+ issues_match = re.search(r"ISSUES:\s*([\s\S]+)", raw, re.IGNORECASE)
99
+
100
+ result = result_match.group(1).upper() if result_match else "UNKNOWN"
101
+ accuracy = int(accuracy_match.group(1)) if accuracy_match else -1
102
+ summary = summary_match.group(1).strip() if summary_match else "Could not extract summary."
103
+
104
+ if issues_match:
105
+ raw_issues = issues_match.group(1).strip()
106
+ issues = [
107
+ line.lstrip("-•* ").strip()
108
+ for line in raw_issues.splitlines()
109
+ if line.strip() and line.strip() not in ("-", "•")
110
+ ]
111
+ else:
112
+ issues = ["Could not extract issues from model response."]
113
+
114
+ return {
115
+ "result": result,
116
+ "accuracy": accuracy,
117
+ "summary": summary,
118
+ "issues": issues,
119
+ "raw": raw.strip(),
120
+ }
121
+
122
+
123
+ def format_for_display(parsed: dict) -> tuple[str, str, str]:
124
+ emoji = "✅" if parsed["result"] == "PASS" else ("❌" if parsed["result"] == "FAIL" else "⚠️")
125
+ verdict = f"{emoji} {parsed['result']}"
126
+ acc_str = f"{parsed['accuracy']}%" if parsed["accuracy"] >= 0 else "N/A"
127
+ metrics = f"Accuracy: {acc_str}\n\nSummary: {parsed['summary']}"
128
+ issues_text = "\n".join(f"• {issue}" for issue in parsed["issues"])
129
+ return verdict, metrics, issues_text
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # MODULE 2 — Validation & Flow Management Module
134
+ # ---------------------------------------------------------------------------
135
+ MIN_DESCRIPTION_CHARS = 20
136
+ MAX_DESCRIPTION_CHARS = 3000
137
+ MIN_CODE_CHARS = 10
138
+ MAX_CODE_CHARS = 8000
139
+ MAX_CODE_LINES = 300
140
+
141
+ FORBIDDEN_PATTERNS = [
142
+ r"ignore (all |previous |above )?instructions",
143
+ r"disregard (all |previous |above )?instructions",
144
+ r"you are now",
145
+ r"act as (a |an )?",
146
+ r"<\s*(script|iframe|object|embed)",
147
+ r"system\s*prompt",
148
+ r"jailbreak",
149
+ ]
150
+
151
+ PYTHON_KEYWORDS = {
152
+ "def", "class", "import", "from", "return", "if", "else", "elif",
153
+ "for", "while", "try", "except", "with", "lambda", "yield", "pass",
154
+ "raise", "assert", "in", "not", "and", "or", "True", "False", "None",
155
+ "print", "len", "range", "self",
156
+ }
157
+
158
+ class RateLimiter:
159
+ def __init__(self, max_calls: int = 5, window_seconds: int = 60):
160
+ self.max_calls = max_calls
161
+ self.window_seconds = window_seconds
162
+ self._timestamps: deque = deque()
163
+
164
+ def is_allowed(self) -> tuple[bool, str]:
165
+ now = time.time()
166
+ while self._timestamps and now - self._timestamps[0] > self.window_seconds:
167
+ self._timestamps.popleft()
168
+ if len(self._timestamps) >= self.max_calls:
169
+ wait = int(self.window_seconds - (now - self._timestamps[0])) + 1
170
+ return False, (
171
+ f"⏳ Rate limit reached — {self.max_calls} requests in "
172
+ f"{self.window_seconds}s. Please wait ~{wait}s and try again."
173
+ )
174
+ self._timestamps.append(now)
175
+ return True, ""
176
+
177
+ _rate_limiter = RateLimiter(max_calls=5, window_seconds=60)
178
+
179
+ def _check_forbidden(text: str) -> str | None:
180
+ lower = text.lower()
181
+ for pattern in FORBIDDEN_PATTERNS:
182
+ if re.search(pattern, lower):
183
+ return (
184
+ "Input contains disallowed content. "
185
+ "Please remove prompt-injection or HTML patterns and try again."
186
+ )
187
+ return None
188
+
189
+ def _looks_like_python(code: str) -> tuple[bool, str]:
190
+ tokens = set(re.findall(r"[A-Za-z_]\w*", code))
191
+ if not tokens.intersection(PYTHON_KEYWORDS):
192
+ return False, (
193
+ "🐍 The code doesn't appear to be Python — no recognisable Python "
194
+ "keywords found (e.g. def, class, import, return). "
195
+ "Please submit Python code only."
196
+ )
197
+ try:
198
+ ast.parse(code)
199
+ except SyntaxError as exc:
200
+ line_hint = f" (line {exc.lineno})" if exc.lineno else ""
201
+ return False, (
202
+ f"🐍 Python syntax error{line_hint}: {exc.msg}. "
203
+ "Please fix the syntax error before evaluating."
204
+ )
205
+ return True, ""
206
+
207
+ def validate_inputs(description: str, code: str) -> list[str]:
208
+ errors: list[str] = []
209
+
210
+ if not description or not description.strip():
211
+ errors.append("📋 Requirements description is required.")
212
+ if not code or not code.strip():
213
+ errors.append("🐍 Python code is required.")
214
+ if errors:
215
+ return errors
216
+
217
+ desc, code_ = description.strip(), code.strip()
218
+
219
+ if len(desc) < MIN_DESCRIPTION_CHARS:
220
+ errors.append(f"📋 Description too short ({len(desc)} chars) — minimum is {MIN_DESCRIPTION_CHARS} characters.")
221
+ if len(code_) < MIN_CODE_CHARS:
222
+ errors.append(f"🐍 Code too short ({len(code_)} chars) — minimum is {MIN_CODE_CHARS} characters.")
223
+ if len(desc) > MAX_DESCRIPTION_CHARS:
224
+ errors.append(f"📋 Description too long ({len(desc):,} chars) — max is {MAX_DESCRIPTION_CHARS:,} characters.")
225
+ if len(code_) > MAX_CODE_CHARS:
226
+ errors.append(f"🐍 Code too long ({len(code_):,} chars) — max is {MAX_CODE_CHARS:,} characters.")
227
+ if len(code_.splitlines()) > MAX_CODE_LINES:
228
+ errors.append(f"🐍 Code has too many lines ({len(code_.splitlines())}) — max is {MAX_CODE_LINES} lines.")
229
+
230
+ if err := _check_forbidden(desc):
231
+ errors.append(f"📋 {err}")
232
+ if err := _check_forbidden(code_):
233
+ errors.append(f"🐍 {err}")
234
+
235
+ if not errors:
236
+ is_python, py_error = _looks_like_python(code_)
237
+ if not is_python:
238
+ errors.append(py_error)
239
+
240
+ if not errors:
241
+ allowed, rate_msg = _rate_limiter.is_allowed()
242
+ if not allowed:
243
+ errors.append(rate_msg)
244
+
245
+ return errors
246
+
247
+ def validate_and_evaluate(description: str, code: str):
248
+ errors = validate_inputs(description, code)
249
+ if errors:
250
+ return "", "", "", "\n".join(f"{i+1}. {e}" for i, e in enumerate(errors))
251
+
252
+ if not GOOGLE_API_KEY:
253
+ return "", "", "", "❌ GOOGLE_API_KEY secret not set in Space Settings."
254
+
255
+ prompt = build_prompt(description, code)
256
+ try:
257
+ raw_output = generate_response(prompt)
258
+ except Exception as exc:
259
+ return "", "", "", f"❌ Model error: {exc}"
260
+
261
+ parsed = parse_output(raw_output)
262
+ verdict, metrics, issues = format_for_display(parsed)
263
+ return verdict, metrics, issues, ""
264
+
265
+
266
+ # ---------------------------------------------------------------------------
267
+ # MODULE 1 — UI Module
268
+ # ---------------------------------------------------------------------------
269
+ CUSTOM_CSS = """
270
+ @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');
271
+ *, *::before, *::after { box-sizing: border-box; }
272
+ body, .gradio-container {
273
+ font-family: 'DM Sans', sans-serif !important;
274
+ background: #0D0F14 !important;
275
+ color: #E8EAF0 !important;
276
+ }
277
+ .gradio-container {
278
+ max-width: 1140px !important;
279
+ margin: 0 auto !important;
280
+ padding: 32px 24px !important;
281
+ }
282
+ .eval-header {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 16px;
286
+ padding: 0 0 28px;
287
+ border-bottom: 1px solid #2E3140;
288
+ margin-bottom: 28px;
289
+ }
290
+ .eval-header .logo {
291
+ width: 44px; height: 44px;
292
+ border-radius: 10px;
293
+ background: linear-gradient(135deg, #534AB7 0%, #1D9E75 100%);
294
+ display: flex; align-items: center; justify-content: center;
295
+ flex-shrink: 0;
296
+ font-size: 22px; line-height: 1;
297
+ }
298
+ .eval-header h1 {
299
+ font-size: 20px !important;
300
+ font-weight: 600 !important;
301
+ letter-spacing: -0.3px !important;
302
+ color: #E8EAF0 !important;
303
+ margin: 0 !important;
304
+ }
305
+ .eval-header .model-badge {
306
+ margin-left: auto;
307
+ font-family: 'Space Mono', monospace;
308
+ font-size: 10px;
309
+ background: #1E2028;
310
+ border: 1px solid #3A3E52;
311
+ color: #6A6E80;
312
+ padding: 4px 10px;
313
+ border-radius: 4px;
314
+ letter-spacing: 1.5px;
315
+ white-space: nowrap;
316
+ }
317
+ .gradio-textbox textarea,
318
+ .gradio-code textarea,
319
+ .gradio-textbox input {
320
+ background: #161820 !important;
321
+ border: 1px solid #2E3140 !important;
322
+ border-radius: 10px !important;
323
+ color: #E8EAF0 !important;
324
+ font-family: 'DM Sans', sans-serif !important;
325
+ font-size: 13.5px !important;
326
+ line-height: 1.7 !important;
327
+ padding: 14px 16px !important;
328
+ transition: border-color 0.15s !important;
329
+ resize: vertical !important;
330
+ }
331
+ .gradio-textbox textarea:focus,
332
+ .gradio-code textarea:focus {
333
+ border-color: #534AB7 !important;
334
+ outline: none !important;
335
+ box-shadow: 0 0 0 3px rgba(83, 74, 183, 0.15) !important;
336
+ }
337
+ .gradio-textbox textarea::placeholder {
338
+ color: #4A4E60 !important;
339
+ }
340
+ #code-input textarea {
341
+ font-family: 'Space Mono', monospace !important;
342
+ font-size: 12.5px !important;
343
+ line-height: 1.65 !important;
344
+ }
345
+ .gradio-textbox label span,
346
+ .gradio-code label span {
347
+ font-family: 'DM Sans', sans-serif !important;
348
+ font-size: 13px !important;
349
+ font-weight: 700 !important;
350
+ color: #E8EAF0 !important;
351
+ text-transform: uppercase !important;
352
+ letter-spacing: 0.8px !important;
353
+ }
354
+ #eval-btn {
355
+ background: #534AB7 !important;
356
+ border: none !important;
357
+ color: #fff !important;
358
+ font-family: 'DM Sans', sans-serif !important;
359
+ font-size: 14px !important;
360
+ font-weight: 500 !important;
361
+ padding: 12px 32px !important;
362
+ border-radius: 8px !important;
363
+ cursor: pointer !important;
364
+ transition: background 0.15s, transform 0.1s !important;
365
+ letter-spacing: -0.1px !important;
366
+ }
367
+ #eval-btn:hover { background: #7F77DD !important; transform: translateY(-1px) !important; }
368
+ #eval-btn:active { transform: translateY(0) !important; }
369
+ #clear-btn {
370
+ background: transparent !important;
371
+ border: 1px solid #3A3E52 !important;
372
+ color: #9DA0B0 !important;
373
+ font-family: 'DM Sans', sans-serif !important;
374
+ font-size: 13px !important;
375
+ padding: 12px 20px !important;
376
+ border-radius: 8px !important;
377
+ cursor: pointer !important;
378
+ transition: border-color 0.15s, color 0.15s !important;
379
+ }
380
+ #clear-btn:hover { border-color: #7F77DD !important; color: #E8EAF0 !important; }
381
+ .results-heading {
382
+ font-size: 11px !important;
383
+ font-weight: 500 !important;
384
+ color: #6A6E80 !important;
385
+ text-transform: uppercase !important;
386
+ letter-spacing: 1px !important;
387
+ padding: 0 0 16px !important;
388
+ border-bottom: 1px solid #2E3140 !important;
389
+ margin-bottom: 20px !important;
390
+ }
391
+ #verdict-out textarea {
392
+ background: #161820 !important; border: 1px solid #2E3140 !important;
393
+ border-radius: 10px !important; font-family: 'Space Mono', monospace !important;
394
+ font-size: 26px !important; font-weight: 700 !important;
395
+ text-align: center !important; padding: 20px !important;
396
+ color: #E8EAF0 !important; cursor: default !important;
397
+ }
398
+ #accuracy-out textarea {
399
+ background: #161820 !important; border: 1px solid #2E3140 !important;
400
+ border-radius: 10px !important; font-family: 'Space Mono', monospace !important;
401
+ font-size: 26px !important; font-weight: 700 !important;
402
+ text-align: center !important; padding: 20px !important;
403
+ color: #E8EAF0 !important; cursor: default !important;
404
+ }
405
+ #summary-out textarea {
406
+ background: #161820 !important; border: 1px solid #2E3140 !important;
407
+ border-radius: 10px !important; font-family: 'DM Sans', sans-serif !important;
408
+ font-size: 13.5px !important; line-height: 1.7 !important;
409
+ padding: 16px !important; color: #C0C3D0 !important; cursor: default !important;
410
+ }
411
+ #issues-out textarea {
412
+ background: #161820 !important; border: 1px solid #2E3140 !important;
413
+ border-radius: 10px !important; font-family: 'DM Sans', sans-serif !important;
414
+ font-size: 13.5px !important; line-height: 1.8 !important;
415
+ padding: 16px !important; color: #C0C3D0 !important;
416
+ cursor: default !important; min-height: 120px !important;
417
+ }
418
+ #error-out textarea {
419
+ background: #1A0E0E !important; border: 1px solid #5a2020 !important;
420
+ border-radius: 10px !important; font-family: 'DM Sans', sans-serif !important;
421
+ font-size: 13.5px !important; padding: 14px 16px !important;
422
+ color: #F0997B !important; cursor: default !important;
423
+ }
424
+ .divider { height: 1px; background: #2E3140; margin: 20px 0; }
425
+ footer { display: none !important; }
426
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
427
+ ::-webkit-scrollbar-track { background: transparent; }
428
+ ::-webkit-scrollbar-thumb { background: #3A3E52; border-radius: 3px; }
429
+ ::-webkit-scrollbar-thumb:hover { background: #534AB7; }
430
+ """
431
+
432
+ HEADER_HTML = """
433
+ <div class="eval-header">
434
+ <div class="logo">🔍</div>
435
+ <div><h1>Python Code Evaluator</h1></div>
436
+ <div class="model-badge">GEMMA-4 · 31B-IT</div>
437
+ </div>
438
+ """
439
+
440
+ RESULTS_HEADING_HTML = """
441
+ <div class="divider"></div>
442
+ <div class="results-heading">⬡ &nbsp; Evaluation Results</div>
443
+ """
444
+
445
+ INPUT_HINT_HTML = """
446
+ <div style="font-size:12px;color:#4A4E60;margin-top:-4px;padding-bottom:4px;">
447
+ Tip — paste your description and code above, then click
448
+ <strong style="color:#7F77DD">Evaluate</strong>.
449
+ </div>
450
+ """
451
+
452
+ def build_ui(evaluate_fn):
453
+
454
+ def _split_metrics(metrics_str: str):
455
+ acc, summary = "", ""
456
+ if not metrics_str:
457
+ return acc, summary
458
+ for line in metrics_str.splitlines():
459
+ if line.startswith("Accuracy:"):
460
+ acc = line.replace("Accuracy:", "").strip()
461
+ elif line.startswith("Summary:"):
462
+ summary = line.replace("Summary:", "").strip()
463
+ return acc, summary
464
+
465
+ def _ui_evaluate(description: str, code: str):
466
+ verdict, metrics, issues, error = evaluate_fn(description, code)
467
+ accuracy, summary = _split_metrics(metrics)
468
+ if "PASS" in verdict.upper():
469
+ verdict_display = "✅ PASS"
470
+ elif "FAIL" in verdict.upper():
471
+ verdict_display = "❌ FAIL"
472
+ else:
473
+ verdict_display = verdict or ""
474
+ return verdict_display, accuracy, summary, issues, error
475
+
476
+ with gr.Blocks(css=CUSTOM_CSS, title="Python Code Evaluator") as demo:
477
+
478
+ gr.HTML(HEADER_HTML)
479
+
480
+ with gr.Row(equal_height=True):
481
+ description_input = gr.Textbox(
482
+ label="Requirements Description",
483
+ placeholder=(
484
+ "Describe what the Python code is supposed to do…\n\n"
485
+ "Example: Write a function that accepts a list of integers "
486
+ "and returns the sum of all even numbers."
487
+ ),
488
+ lines=10, max_lines=20, elem_id="desc-input",
489
+ )
490
+ code_input = gr.Textbox(
491
+ label="Python Code",
492
+ placeholder="# Paste your Python code here…\n\ndef example(data):\n pass\n",
493
+ lines=10, max_lines=30, elem_id="code-input",
494
+ )
495
+
496
+ gr.HTML(INPUT_HINT_HTML)
497
+
498
+ with gr.Row():
499
+ eval_btn = gr.Button("Evaluate", variant="primary", elem_id="eval-btn", scale=0)
500
+ clear_btn = gr.Button("Clear", variant="secondary", elem_id="clear-btn", scale=0)
501
+
502
+ gr.HTML(RESULTS_HEADING_HTML)
503
+
504
+ with gr.Row(equal_height=True):
505
+ verdict_out = gr.Textbox(label="Verdict", interactive=False, elem_id="verdict-out", scale=1)
506
+ accuracy_out = gr.Textbox(label="Accuracy", interactive=False, elem_id="accuracy-out", scale=1)
507
+ summary_out = gr.Textbox(label="Summary", interactive=False, elem_id="summary-out", scale=2, lines=3)
508
+
509
+ issues_out = gr.Textbox(label="Issues Detected", interactive=False, lines=5, elem_id="issues-out")
510
+ error_out = gr.Textbox(label="Status / Errors", interactive=False, visible=True, elem_id="error-out")
511
+
512
+ gr.HTML("""
513
+ <div style="text-align:right;font-size:11px;color:#4A4E60;margin-top:8px;">
514
+ Press <kbd style="background:#1E2028;border:1px solid #3A3E52;border-radius:4px;
515
+ padding:2px 6px;font-family:'Space Mono',monospace;font-size:10px;color:#9DA0B0;">
516
+ Ctrl+Enter</kbd> to evaluate
517
+ </div>
518
+ """)
519
+
520
+ outputs = [verdict_out, accuracy_out, summary_out, issues_out, error_out]
521
+ eval_btn.click(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
522
+ description_input.submit(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
523
+ code_input.submit(fn=_ui_evaluate, inputs=[description_input, code_input], outputs=outputs)
524
+
525
+ def _clear():
526
+ return "", "", "", "", "", ""
527
+
528
+ clear_btn.click(
529
+ fn=_clear, inputs=[],
530
+ outputs=[description_input, code_input,
531
+ verdict_out, accuracy_out, summary_out, issues_out],
532
+ )
533
+
534
+ return demo
535
+
536
+
537
+ # ---------------------------------------------------------------------------
538
+ # ENTRY POINT
539
+ # ---------------------------------------------------------------------------
540
+ if __name__ == "__main__":
541
+ app = build_ui(validate_and_evaluate)
542
+ app.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)