Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- API.txt +185 -0
- index.html +267 -0
- main.py +9 -10
API.txt
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
================================================================================
|
| 2 |
+
CODE EVALUATOR API — Usage Guide
|
| 3 |
+
================================================================================
|
| 4 |
+
|
| 5 |
+
BASE URL
|
| 6 |
+
--------
|
| 7 |
+
https://dhyandevp-codeevaluator.hf.space
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
AUTHENTICATION
|
| 11 |
+
--------------
|
| 12 |
+
All protected endpoints require the header:
|
| 13 |
+
X-API-Key: <your-key>
|
| 14 |
+
|
| 15 |
+
Contact the administrator to receive a valid API key.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
--------------------------------------------------------------------------------
|
| 19 |
+
ENDPOINT 1 — Submit a Python script
|
| 20 |
+
--------------------------------------------------------------------------------
|
| 21 |
+
|
| 22 |
+
Method : POST
|
| 23 |
+
Path : /submit
|
| 24 |
+
Auth : Required (X-API-Key header)
|
| 25 |
+
|
| 26 |
+
Request body (multipart/form-data):
|
| 27 |
+
file — the .py file to evaluate (max 100 KB)
|
| 28 |
+
|
| 29 |
+
Response (200 OK):
|
| 30 |
+
{
|
| 31 |
+
"job_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
| 32 |
+
"status": "Processing"
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
Error responses:
|
| 36 |
+
401 { "detail": "Unauthorised: invalid or missing X-API-Key." }
|
| 37 |
+
413 { "detail": "File exceeds the 100 KB size limit." }
|
| 38 |
+
|
| 39 |
+
cURL example:
|
| 40 |
+
curl -X POST https://dhyandevp-codeevaluator.hf.space/submit \
|
| 41 |
+
-H "X-API-Key: ek-your-key" \
|
| 42 |
+
-F "file=@script.py"
|
| 43 |
+
|
| 44 |
+
Python example:
|
| 45 |
+
import requests
|
| 46 |
+
|
| 47 |
+
resp = requests.post(
|
| 48 |
+
"https://dhyandevp-codeevaluator.hf.space/submit",
|
| 49 |
+
headers={"X-API-Key": "ek-your-key"},
|
| 50 |
+
files={"file": open("script.py", "rb")},
|
| 51 |
+
)
|
| 52 |
+
job_id = resp.json()["job_id"]
|
| 53 |
+
print("Job ID:", job_id)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
--------------------------------------------------------------------------------
|
| 57 |
+
ENDPOINT 2 — Retrieve the grading report
|
| 58 |
+
--------------------------------------------------------------------------------
|
| 59 |
+
|
| 60 |
+
Method : GET
|
| 61 |
+
Path : /report/{job_id}
|
| 62 |
+
Auth : Required (X-API-Key header)
|
| 63 |
+
|
| 64 |
+
Path parameter:
|
| 65 |
+
job_id — the UUID returned by /submit
|
| 66 |
+
|
| 67 |
+
Poll this endpoint every 3–5 seconds until status is "Completed" or "Failed".
|
| 68 |
+
|
| 69 |
+
Response when still running (200):
|
| 70 |
+
{
|
| 71 |
+
"job_id": "f47ac10b-...",
|
| 72 |
+
"status": "Processing",
|
| 73 |
+
"message": "Your submission is being evaluated. Retry in a few seconds."
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
Response when done (200):
|
| 77 |
+
{
|
| 78 |
+
"job_id": "f47ac10b-...",
|
| 79 |
+
"status": "Completed",
|
| 80 |
+
"report": {
|
| 81 |
+
"score": 78,
|
| 82 |
+
"percentage": 81.5,
|
| 83 |
+
"mistakes": [
|
| 84 |
+
"Missing docstring on calculate(): add a description of parameters and return value.",
|
| 85 |
+
"Variable 'x' is poorly named; rename to something descriptive like 'total_count'."
|
| 86 |
+
]
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
Response on failure (200):
|
| 91 |
+
{
|
| 92 |
+
"job_id": "f47ac10b-...",
|
| 93 |
+
"status": "Failed",
|
| 94 |
+
"error": {
|
| 95 |
+
"error_type": "AuthenticationError",
|
| 96 |
+
"error_message": "Invalid API key."
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
Response when job not found (404):
|
| 101 |
+
{ "detail": "Job 'f47ac10b-...' not found." }
|
| 102 |
+
|
| 103 |
+
cURL example:
|
| 104 |
+
curl https://dhyandevp-codeevaluator.hf.space/report/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
|
| 105 |
+
-H "X-API-Key: ek-your-key"
|
| 106 |
+
|
| 107 |
+
Python polling example:
|
| 108 |
+
import time, requests
|
| 109 |
+
|
| 110 |
+
headers = {"X-API-Key": "ek-your-key"}
|
| 111 |
+
url = f"https://dhyandevp-codeevaluator.hf.space/report/{job_id}"
|
| 112 |
+
|
| 113 |
+
while True:
|
| 114 |
+
resp = requests.get(url, headers=headers)
|
| 115 |
+
data = resp.json()
|
| 116 |
+
|
| 117 |
+
if data["status"] == "Completed":
|
| 118 |
+
report = data["report"]
|
| 119 |
+
print("Score :", report["score"])
|
| 120 |
+
print("Precision :", report["percentage"])
|
| 121 |
+
print("Mistakes :")
|
| 122 |
+
for m in report["mistakes"]:
|
| 123 |
+
print(" -", m)
|
| 124 |
+
break
|
| 125 |
+
|
| 126 |
+
elif data["status"] == "Failed":
|
| 127 |
+
print("Evaluation failed:", data["error"]["error_message"])
|
| 128 |
+
break
|
| 129 |
+
|
| 130 |
+
print("Still processing, retrying in 5 seconds…")
|
| 131 |
+
time.sleep(5)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
--------------------------------------------------------------------------------
|
| 135 |
+
ENDPOINT 3 — Health check (no auth required)
|
| 136 |
+
--------------------------------------------------------------------------------
|
| 137 |
+
|
| 138 |
+
Method : GET
|
| 139 |
+
Path : /health
|
| 140 |
+
|
| 141 |
+
Response (200):
|
| 142 |
+
{ "status": "ok" }
|
| 143 |
+
|
| 144 |
+
Use this to verify the service is running before submitting.
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
--------------------------------------------------------------------------------
|
| 148 |
+
REPORT FIELDS EXPLAINED
|
| 149 |
+
--------------------------------------------------------------------------------
|
| 150 |
+
|
| 151 |
+
score (integer, 0–100)
|
| 152 |
+
Overall benchmark. Combines correctness, efficiency, readability,
|
| 153 |
+
and best-practice adherence.
|
| 154 |
+
|
| 155 |
+
90–100 Near-perfect, production-ready
|
| 156 |
+
70–89 Good, minor issues only
|
| 157 |
+
50–69 Adequate, notable defects
|
| 158 |
+
30–49 Poor, significant bugs or anti-patterns
|
| 159 |
+
0–29 Critical failure, non-functional code
|
| 160 |
+
|
| 161 |
+
percentage (float, 0.0–100.0)
|
| 162 |
+
Execution precision — how accurately the code fulfils its apparent
|
| 163 |
+
intent. May differ from score when code runs correctly but is
|
| 164 |
+
structured poorly.
|
| 165 |
+
|
| 166 |
+
mistakes (array of strings)
|
| 167 |
+
Itemised list of every defect found: architectural issues, logic
|
| 168 |
+
errors, syntax bugs, missing optimisations, poor naming, missing
|
| 169 |
+
error handling, security vulnerabilities, or style violations.
|
| 170 |
+
Each entry names the problem and suggests a fix.
|
| 171 |
+
Empty array [] means no issues were found.
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
--------------------------------------------------------------------------------
|
| 175 |
+
NOTES
|
| 176 |
+
--------------------------------------------------------------------------------
|
| 177 |
+
|
| 178 |
+
- Only .py files are accepted.
|
| 179 |
+
- Maximum file size: 100 KB.
|
| 180 |
+
- Typical grading time: 15–45 seconds depending on code length and server load.
|
| 181 |
+
- The service is asynchronous. Always poll /report after /submit.
|
| 182 |
+
- Keep your API key secret. Do not commit it to version control.
|
| 183 |
+
- Interactive Swagger docs: https://dhyandevp-codeevaluator.hf.space/docs
|
| 184 |
+
|
| 185 |
+
================================================================================
|
index.html
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Code Evaluator</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
+
body {
|
| 10 |
+
font-family: system-ui, sans-serif;
|
| 11 |
+
background: #0f1117;
|
| 12 |
+
color: #e2e8f0;
|
| 13 |
+
min-height: 100vh;
|
| 14 |
+
display: flex;
|
| 15 |
+
align-items: center;
|
| 16 |
+
justify-content: center;
|
| 17 |
+
padding: 24px;
|
| 18 |
+
}
|
| 19 |
+
.box {
|
| 20 |
+
background: #1a1f2e;
|
| 21 |
+
border: 1px solid #2d3748;
|
| 22 |
+
border-radius: 12px;
|
| 23 |
+
padding: 36px;
|
| 24 |
+
width: 100%;
|
| 25 |
+
max-width: 520px;
|
| 26 |
+
}
|
| 27 |
+
h1 { font-size: 1.4rem; font-weight: 700; margin-bottom: 6px; }
|
| 28 |
+
.sub { color: #64748b; font-size: 0.875rem; margin-bottom: 28px; }
|
| 29 |
+
label {
|
| 30 |
+
display: block;
|
| 31 |
+
font-size: 0.78rem;
|
| 32 |
+
font-weight: 600;
|
| 33 |
+
color: #94a3b8;
|
| 34 |
+
text-transform: uppercase;
|
| 35 |
+
letter-spacing: 0.05em;
|
| 36 |
+
margin-bottom: 6px;
|
| 37 |
+
}
|
| 38 |
+
input, .file-area {
|
| 39 |
+
width: 100%;
|
| 40 |
+
background: #0f1117;
|
| 41 |
+
border: 1px solid #2d3748;
|
| 42 |
+
border-radius: 8px;
|
| 43 |
+
color: #e2e8f0;
|
| 44 |
+
font-size: 0.875rem;
|
| 45 |
+
padding: 10px 14px;
|
| 46 |
+
outline: none;
|
| 47 |
+
margin-bottom: 16px;
|
| 48 |
+
font-family: monospace;
|
| 49 |
+
}
|
| 50 |
+
input:focus { border-color: #7c3aed; }
|
| 51 |
+
.file-area {
|
| 52 |
+
cursor: pointer;
|
| 53 |
+
text-align: center;
|
| 54 |
+
padding: 24px;
|
| 55 |
+
border-style: dashed;
|
| 56 |
+
color: #64748b;
|
| 57 |
+
transition: border-color 0.2s, color 0.2s;
|
| 58 |
+
position: relative;
|
| 59 |
+
}
|
| 60 |
+
.file-area:hover, .file-area.drag { border-color: #7c3aed; color: #a78bfa; }
|
| 61 |
+
.file-area input[type=file] {
|
| 62 |
+
position: absolute; inset: 0; opacity: 0; cursor: pointer;
|
| 63 |
+
width: 100%; height: 100%; margin: 0; padding: 0; border: none;
|
| 64 |
+
background: none;
|
| 65 |
+
}
|
| 66 |
+
#fileName { font-size: 0.82rem; color: #a78bfa; margin-top: 6px; }
|
| 67 |
+
button {
|
| 68 |
+
width: 100%;
|
| 69 |
+
background: #7c3aed;
|
| 70 |
+
color: #fff;
|
| 71 |
+
border: none;
|
| 72 |
+
border-radius: 8px;
|
| 73 |
+
padding: 12px;
|
| 74 |
+
font-size: 0.95rem;
|
| 75 |
+
font-weight: 600;
|
| 76 |
+
cursor: pointer;
|
| 77 |
+
transition: background 0.2s;
|
| 78 |
+
}
|
| 79 |
+
button:hover:not(:disabled) { background: #6d28d9; }
|
| 80 |
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 81 |
+
#status {
|
| 82 |
+
margin-top: 20px;
|
| 83 |
+
padding: 14px;
|
| 84 |
+
border-radius: 8px;
|
| 85 |
+
font-size: 0.875rem;
|
| 86 |
+
display: none;
|
| 87 |
+
background: #0f1117;
|
| 88 |
+
border: 1px solid #2d3748;
|
| 89 |
+
}
|
| 90 |
+
.tag {
|
| 91 |
+
display: inline-block;
|
| 92 |
+
padding: 2px 10px;
|
| 93 |
+
border-radius: 99px;
|
| 94 |
+
font-size: 0.75rem;
|
| 95 |
+
font-weight: 700;
|
| 96 |
+
margin-bottom: 8px;
|
| 97 |
+
}
|
| 98 |
+
.tag-processing { background: rgba(245,158,11,0.15); color: #f59e0b; }
|
| 99 |
+
.tag-completed { background: rgba(16,185,129,0.15); color: #10b981; }
|
| 100 |
+
.tag-failed { background: rgba(244,63,94,0.15); color: #f43f5e; }
|
| 101 |
+
.score-row { display: flex; gap: 16px; margin: 12px 0; }
|
| 102 |
+
.score-block { flex: 1; background: #1a1f2e; border: 1px solid #2d3748; border-radius: 8px; padding: 12px; text-align: center; }
|
| 103 |
+
.score-num { font-size: 1.8rem; font-weight: 700; }
|
| 104 |
+
.score-lbl { font-size: 0.7rem; color: #64748b; text-transform: uppercase; }
|
| 105 |
+
.mistakes { margin-top: 12px; }
|
| 106 |
+
.mistake { padding: 8px 12px; background: rgba(244,63,94,0.06); border: 1px solid rgba(244,63,94,0.15); border-radius: 6px; font-size: 0.82rem; color: #cbd5e1; margin-bottom: 6px; line-height: 1.5; }
|
| 107 |
+
.no-mistakes { color: #10b981; font-size: 0.875rem; }
|
| 108 |
+
.spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.2); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; vertical-align: middle; margin-right: 6px; }
|
| 109 |
+
@keyframes spin { to { transform: rotate(360deg); } }
|
| 110 |
+
.job-id { font-family: monospace; font-size: 0.72rem; color: #475569; margin-top: 8px; word-break: break-all; }
|
| 111 |
+
</style>
|
| 112 |
+
</head>
|
| 113 |
+
<body>
|
| 114 |
+
<div class="box">
|
| 115 |
+
<h1>⚡ Code Evaluator</h1>
|
| 116 |
+
<p class="sub">Upload a Python script and receive an AI grading report.</p>
|
| 117 |
+
|
| 118 |
+
<label>API Base URL</label>
|
| 119 |
+
<input type="text" id="apiUrl" placeholder="https://dhyandevp-codeevaluator.hf.space" />
|
| 120 |
+
|
| 121 |
+
<label>X-API-Key</label>
|
| 122 |
+
<input type="password" id="apiKey" placeholder="ek-..." />
|
| 123 |
+
|
| 124 |
+
<label>Python File (.py)</label>
|
| 125 |
+
<div class="file-area" id="dropZone">
|
| 126 |
+
<input type="file" id="fileInput" accept=".py" onchange="onFile(this)" />
|
| 127 |
+
📂 Click to choose a .py file or drag it here
|
| 128 |
+
<div id="fileName"></div>
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
<button id="submitBtn" onclick="submit()" disabled>Submit for Evaluation</button>
|
| 132 |
+
|
| 133 |
+
<div id="status"></div>
|
| 134 |
+
</div>
|
| 135 |
+
|
| 136 |
+
<script>
|
| 137 |
+
let pollTimer = null;
|
| 138 |
+
|
| 139 |
+
// Auto-fill URL if the page is served from the Space itself
|
| 140 |
+
window.onload = () => {
|
| 141 |
+
const origin = location.origin;
|
| 142 |
+
document.getElementById('apiUrl').value =
|
| 143 |
+
(origin.startsWith('http') && !origin.includes('localhost') && !origin.includes('file:'))
|
| 144 |
+
? origin
|
| 145 |
+
: 'https://dhyandevp-codeevaluator.hf.space';
|
| 146 |
+
};
|
| 147 |
+
|
| 148 |
+
function onFile(input) {
|
| 149 |
+
const f = input.files[0];
|
| 150 |
+
document.getElementById('fileName').textContent = f ? f.name : '';
|
| 151 |
+
document.getElementById('submitBtn').disabled = !f;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// Drag-and-drop styling
|
| 155 |
+
const dz = document.getElementById('dropZone');
|
| 156 |
+
dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('drag'); });
|
| 157 |
+
dz.addEventListener('dragleave', () => dz.classList.remove('drag'));
|
| 158 |
+
dz.addEventListener('drop', e => {
|
| 159 |
+
e.preventDefault(); dz.classList.remove('drag');
|
| 160 |
+
const f = e.dataTransfer.files[0];
|
| 161 |
+
if (f) {
|
| 162 |
+
document.getElementById('fileInput').files = e.dataTransfer.files;
|
| 163 |
+
onFile(document.getElementById('fileInput'));
|
| 164 |
+
}
|
| 165 |
+
});
|
| 166 |
+
|
| 167 |
+
async function submit() {
|
| 168 |
+
const url = document.getElementById('apiUrl').value.trim().replace(/\/$/, '');
|
| 169 |
+
const apiKey = document.getElementById('apiKey').value.trim();
|
| 170 |
+
const file = document.getElementById('fileInput').files[0];
|
| 171 |
+
|
| 172 |
+
if (!url || !apiKey || !file) return;
|
| 173 |
+
if (pollTimer) clearInterval(pollTimer);
|
| 174 |
+
|
| 175 |
+
document.getElementById('submitBtn').disabled = true;
|
| 176 |
+
showStatus('processing', null, null, null, 'Submitting…');
|
| 177 |
+
|
| 178 |
+
const form = new FormData();
|
| 179 |
+
form.append('file', file);
|
| 180 |
+
|
| 181 |
+
try {
|
| 182 |
+
const res = await fetch(`${url}/submit`, { method: 'POST', headers: { 'X-API-Key': apiKey }, body: form });
|
| 183 |
+
const data = await res.json();
|
| 184 |
+
if (!res.ok) { showError(`Error ${res.status}: ${data.detail}`); return; }
|
| 185 |
+
|
| 186 |
+
const jobId = data.job_id;
|
| 187 |
+
showStatus('processing', jobId, null, null, 'Evaluating your code…');
|
| 188 |
+
pollTimer = setInterval(() => poll(url, apiKey, jobId), 3000);
|
| 189 |
+
} catch (e) {
|
| 190 |
+
showError('Cannot reach the API. Check the URL and your internet connection.');
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
async function poll(url, apiKey, jobId) {
|
| 195 |
+
try {
|
| 196 |
+
const res = await fetch(`${url}/report/${jobId}`, { headers: { 'X-API-Key': apiKey } });
|
| 197 |
+
const data = await res.json();
|
| 198 |
+
if (data.status === 'Processing') return;
|
| 199 |
+
clearInterval(pollTimer);
|
| 200 |
+
document.getElementById('submitBtn').disabled = false;
|
| 201 |
+
if (data.status === 'Completed') {
|
| 202 |
+
showStatus('completed', jobId, data.report.score, data.report.percentage, null, data.report.mistakes);
|
| 203 |
+
} else {
|
| 204 |
+
showError(`Failed: ${data.error?.error_message || 'Unknown error'}`);
|
| 205 |
+
}
|
| 206 |
+
} catch (_) { /* keep polling */ }
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function showStatus(state, jobId, score, pct, message, mistakes) {
|
| 210 |
+
const el = document.getElementById('status');
|
| 211 |
+
el.style.display = 'block';
|
| 212 |
+
|
| 213 |
+
const tagClass = { processing: 'tag-processing', completed: 'tag-completed', failed: 'tag-failed' }[state];
|
| 214 |
+
const tagLabel = { processing: '⏳ Processing', completed: '✅ Completed', failed: '❌ Failed' }[state];
|
| 215 |
+
|
| 216 |
+
let html = `<span class="tag ${tagClass}">${tagLabel}</span>`;
|
| 217 |
+
|
| 218 |
+
if (state === 'processing') {
|
| 219 |
+
html += `<br/><span class="spinner"></span>${message || 'Working…'}`;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
if (state === 'completed') {
|
| 223 |
+
const sc = colorFor(score), pc = colorFor(pct);
|
| 224 |
+
html += `
|
| 225 |
+
<div class="score-row">
|
| 226 |
+
<div class="score-block">
|
| 227 |
+
<div class="score-num" style="color:${sc}">${score}</div>
|
| 228 |
+
<div class="score-lbl">Score / 100</div>
|
| 229 |
+
</div>
|
| 230 |
+
<div class="score-block">
|
| 231 |
+
<div class="score-num" style="color:${pc}">${pct.toFixed(1)}%</div>
|
| 232 |
+
<div class="score-lbl">Precision</div>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
<div class="mistakes">`;
|
| 236 |
+
if (!mistakes || mistakes.length === 0) {
|
| 237 |
+
html += '<div class="no-mistakes">✅ No issues found — excellent code!</div>';
|
| 238 |
+
} else {
|
| 239 |
+
mistakes.forEach(m => { html += `<div class="mistake">• ${esc(m)}</div>`; });
|
| 240 |
+
}
|
| 241 |
+
html += '</div>';
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
if (jobId) html += `<div class="job-id">job: ${jobId}</div>`;
|
| 245 |
+
el.innerHTML = html;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
function showError(msg) {
|
| 249 |
+
clearInterval(pollTimer);
|
| 250 |
+
document.getElementById('submitBtn').disabled = false;
|
| 251 |
+
const el = document.getElementById('status');
|
| 252 |
+
el.style.display = 'block';
|
| 253 |
+
el.innerHTML = `<span class="tag tag-failed">❌ Failed</span><br/>${esc(msg)}`;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
function colorFor(v) {
|
| 257 |
+
if (v >= 70) return '#10b981';
|
| 258 |
+
if (v >= 40) return '#f59e0b';
|
| 259 |
+
return '#f43f5e';
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
function esc(s) {
|
| 263 |
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
| 264 |
+
}
|
| 265 |
+
</script>
|
| 266 |
+
</body>
|
| 267 |
+
</html>
|
main.py
CHANGED
|
@@ -42,25 +42,24 @@ if not YOUR_CUSTOM_API_KEY:
|
|
| 42 |
"Add it to your deployment secrets and restart."
|
| 43 |
)
|
| 44 |
|
| 45 |
-
# LLM provider API key —
|
| 46 |
-
|
|
|
|
| 47 |
|
| 48 |
-
if not
|
| 49 |
logger.warning(
|
| 50 |
-
"
|
| 51 |
"All /submit requests will fail during LLM inference. "
|
| 52 |
"Add the secret in your deployment environment and restart."
|
| 53 |
)
|
| 54 |
|
| 55 |
-
# AsyncOpenAI client pointed at
|
| 56 |
-
# Swap base_url and api_key env var to switch providers (e.g. OpenRouter)
|
| 57 |
-
# without touching any other part of the code.
|
| 58 |
openai_client = AsyncOpenAI(
|
| 59 |
-
api_key=
|
| 60 |
-
base_url="https://
|
| 61 |
)
|
| 62 |
|
| 63 |
-
MODEL_ID: str = "
|
| 64 |
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
# Database
|
|
|
|
| 42 |
"Add it to your deployment secrets and restart."
|
| 43 |
)
|
| 44 |
|
| 45 |
+
# LLM provider API key — loaded from the OPENROUTER_API_KEY environment secret.
|
| 46 |
+
# Set this in your deployment's secrets panel. Never hardcode it here.
|
| 47 |
+
OPENROUTER_API_KEY: str | None = os.getenv("OPENROUTER_API_KEY")
|
| 48 |
|
| 49 |
+
if not OPENROUTER_API_KEY:
|
| 50 |
logger.warning(
|
| 51 |
+
"OPENROUTER_API_KEY is not set. "
|
| 52 |
"All /submit requests will fail during LLM inference. "
|
| 53 |
"Add the secret in your deployment environment and restart."
|
| 54 |
)
|
| 55 |
|
| 56 |
+
# AsyncOpenAI client pointed at OpenRouter's OpenAI-compatible gateway.
|
|
|
|
|
|
|
| 57 |
openai_client = AsyncOpenAI(
|
| 58 |
+
api_key=OPENROUTER_API_KEY or "missing-key",
|
| 59 |
+
base_url="https://openrouter.ai/api/v1",
|
| 60 |
)
|
| 61 |
|
| 62 |
+
MODEL_ID: str = "qwen/qwen-2.5-coder-32b-instruct"
|
| 63 |
|
| 64 |
# ---------------------------------------------------------------------------
|
| 65 |
# Database
|