DhyandevP commited on
Commit
02cd281
·
verified ·
1 Parent(s): 2980b7c

Upload 6 files

Browse files
Files changed (3) hide show
  1. API.txt +185 -0
  2. index.html +267 -0
  3. 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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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 — set as an environment secret, never hardcoded.
46
- HUGGINGFACE_API_KEY: str | None = os.getenv("HUGGINGFACE_API_KEY")
 
47
 
48
- if not HUGGINGFACE_API_KEY:
49
  logger.warning(
50
- "HUGGINGFACE_API_KEY is not set. "
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 the HuggingFace OpenAI-compatible gateway.
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=HUGGINGFACE_API_KEY or "missing-key",
60
- base_url="https://api-inference.huggingface.co/v1/",
61
  )
62
 
63
- MODEL_ID: str = "Qwen/Qwen2.5-Coder-32B-Instruct"
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