Spaces:
Sleeping
Sleeping
Download index.html from DhyandevP/CodeEvaluator: direct link, hf CLI and curl.
- Browser
- Download file 23.5 kB
-
https://huggingface.co/spaces/DhyandevP/CodeEvaluator/resolve/main/index.html
- Command line
-
hf download hf://spaces/DhyandevP/CodeEvaluator/index.html
-
curl -L -o index.html https://huggingface.co/spaces/DhyandevP/CodeEvaluator/resolve/main/index.html
23.5 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Code Evaluator</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: system-ui, sans-serif; | |
| background: #000; | |
| color: #e5e5e5; | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 24px; | |
| } | |
| .box { | |
| background: #111; | |
| border: 1px solid #333; | |
| border-radius: 12px; | |
| padding: 36px; | |
| width: 100%; | |
| max-width: 640px; | |
| } | |
| h1 { font-size: 1.4rem; font-weight: 700; margin-bottom: 6px; color: #fff; } | |
| .sub { color: #777; font-size: 0.875rem; margin-bottom: 28px; } | |
| label { | |
| display: block; | |
| font-size: 0.78rem; | |
| font-weight: 600; | |
| color: #999; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 6px; | |
| } | |
| input[type="text"], input[type="password"], textarea, .file-area { | |
| width: 100%; | |
| background: #000; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| color: #e5e5e5; | |
| font-size: 0.875rem; | |
| padding: 10px 14px; | |
| outline: none; | |
| margin-bottom: 16px; | |
| font-family: monospace; | |
| } | |
| textarea { resize: vertical; min-height: 80px; } | |
| input[type="text"]:focus, input[type="password"]:focus, textarea:focus { border-color: #888; } | |
| /* ── Pick links inside drop zone ────────────────────────────── */ | |
| .pick-links { | |
| display: flex; | |
| gap: 16px; | |
| justify-content: center; | |
| margin-top: 8px; | |
| } | |
| .pick-link { | |
| color: #888; | |
| font-size: 0.76rem; | |
| cursor: pointer; | |
| text-decoration: underline; | |
| transition: color 0.2s; | |
| } | |
| .pick-link:hover { color: #ddd; } | |
| /* ── Drop zone ──────────────────────────────────────────────── */ | |
| .file-area { | |
| cursor: pointer; | |
| text-align: center; | |
| padding: 24px; | |
| border-style: dashed; | |
| color: #777; | |
| transition: border-color 0.2s, color 0.2s; | |
| position: relative; | |
| } | |
| .file-area:hover, .file-area.drag { border-color: #aaa; color: #ccc; } | |
| .drop-icon { font-size: 1.6rem; margin-bottom: 6px; display: block; } | |
| .drop-label { font-size: 0.82rem; } | |
| /* ── File preview ───────────────────────────────────────────── */ | |
| #filePreview { | |
| display: none; | |
| max-height: 160px; | |
| overflow-y: auto; | |
| background: #0a0a0a; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| padding: 10px 14px; | |
| margin-bottom: 16px; | |
| font-family: monospace; | |
| font-size: 0.78rem; | |
| color: #aaa; | |
| line-height: 1.6; | |
| } | |
| #filePreview .preview-header { | |
| color: #ccc; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| } | |
| #filePreview .preview-file { color: #888; } | |
| #filePreview .preview-file .fsize { color: #555; } | |
| #filePreview .preview-total { | |
| border-top: 1px solid #222; | |
| margin-top: 6px; | |
| padding-top: 6px; | |
| color: #999; | |
| font-weight: 600; | |
| } | |
| /* ── Submit button ──────────────────────────────────────────── */ | |
| button#submitBtn { | |
| width: 100%; | |
| background: #fff; | |
| color: #000; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 12px; | |
| font-size: 0.95rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| button#submitBtn:hover:not(:disabled) { background: #ddd; } | |
| button#submitBtn:disabled { opacity: 0.35; cursor: not-allowed; } | |
| /* ── Status / Report ────────────────────────────────────────── */ | |
| #status { | |
| margin-top: 20px; | |
| padding: 14px; | |
| border-radius: 8px; | |
| font-size: 0.875rem; | |
| display: none; | |
| background: #0a0a0a; | |
| border: 1px solid #333; | |
| } | |
| .tag { | |
| display: inline-block; | |
| padding: 2px 10px; | |
| border-radius: 99px; | |
| font-size: 0.75rem; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| } | |
| .tag-processing { background: rgba(255,255,255,0.08); color: #aaa; } | |
| .tag-completed { background: rgba(255,255,255,0.1); color: #fff; } | |
| .tag-failed { background: rgba(255,255,255,0.06); color: #888; } | |
| .meta-row { display: flex; gap: 10px; align-items: center; margin: 8px 0; flex-wrap: wrap; } | |
| .lang-badge { | |
| display: inline-block; padding: 3px 12px; border-radius: 6px; | |
| font-size: 0.78rem; font-weight: 700; | |
| background: rgba(255,255,255,0.08); color: #ccc; | |
| } | |
| .score-row { display: flex; gap: 16px; margin: 12px 0; } | |
| .score-block { flex: 1; background: #111; border: 1px solid #333; border-radius: 8px; padding: 12px; text-align: center; } | |
| .score-num { font-size: 1.8rem; font-weight: 700; color: #fff; } | |
| .score-lbl { font-size: 0.7rem; color: #777; text-transform: uppercase; } | |
| .section-title { font-size: 0.78rem; font-weight: 700; color: #999; text-transform: uppercase; letter-spacing: 0.05em; margin: 14px 0 6px; } | |
| .spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.15); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; vertical-align: middle; margin-right: 6px; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .job-id { font-family: monospace; font-size: 0.72rem; color: #555; margin-top: 8px; word-break: break-all; } | |
| /* ── Question cards ─────────────────────────────────────────── */ | |
| .q-card { | |
| background: #111; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| padding: 12px 14px; | |
| margin-bottom: 8px; | |
| } | |
| .q-header { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| margin-bottom: 6px; | |
| } | |
| .q-badge { | |
| flex-shrink: 0; | |
| display: inline-block; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 0.72rem; | |
| font-weight: 700; | |
| margin-top: 1px; | |
| } | |
| .q-badge-pass { background: rgba(74,222,128,0.15); color: #4ade80; } | |
| .q-badge-fail { background: rgba(248,113,113,0.15); color: #f87171; } | |
| .q-text { font-size: 0.84rem; color: #e5e5e5; font-weight: 600; line-height: 1.4; } | |
| .q-feedback { font-size: 0.8rem; color: #999; line-height: 1.5; margin-top: 4px; } | |
| /* ── Code quality block ─────────────────────────────────────── */ | |
| .quality-block { | |
| padding: 10px 14px; | |
| background: rgba(255,255,255,0.03); | |
| border: 1px solid #333; | |
| border-radius: 6px; | |
| font-size: 0.82rem; | |
| color: #ccc; | |
| line-height: 1.5; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="box"> | |
| <h1>⚡ Code Evaluator</h1> | |
| <p class="sub">Upload a source code file, project folder, or .zip archive for AI grading.</p> | |
| <label>API Base URL</label> | |
| <input type="text" id="apiUrl" placeholder="https://dhyandevp-codeevaluator.hf.space" /> | |
| <label>X-API-Key</label> | |
| <input type="password" id="apiKey" placeholder="ek-..." /> | |
| <label>Question / Objective (optional)</label> | |
| <textarea id="questionObjective" placeholder="Describe what the submitted code should accomplish…"></textarea> | |
| <label>Upload</label> | |
| <div class="file-area" id="dropZone"> | |
| <input type="file" id="fileInput" multiple accept=".py,.js,.ts,.jsx,.tsx,.c,.cpp,.h,.java,.go,.rs,.rb,.php,.swift,.cs,.html,.css,.scss,.json,.yaml,.yml,.toml,.xml,.md,.txt,.sql,.sh,.vue,.svelte,.zip,.tar.gz,.tgz" style="display:none" /> | |
| <input type="file" id="folderInput" webkitdirectory directory multiple style="display:none" /> | |
| <span class="drop-icon">📂</span> | |
| <span class="drop-label">Drop files, a folder, or an archive here</span> | |
| <div class="pick-links"> | |
| <span class="pick-link" id="pickFiles">Choose file(s)</span> | |
| <span class="pick-link" id="pickFolder">Choose folder</span> | |
| </div> | |
| </div> | |
| <div id="filePreview"></div> | |
| <button id="submitBtn" onclick="submit()" disabled>Submit for Evaluation</button> | |
| <div id="status"></div> | |
| </div> | |
| <script> | |
| /* ────────────────────────────────────────────────────────────────── | |
| * State | |
| * ────────────────────────────────────────────────────────────────── */ | |
| let selectedFiles = []; // Array of File objects (with optional ._relativePath) | |
| let pollTimer = null; | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Initialisation | |
| * ────────────────────────────────────────────────────────────────── */ | |
| window.onload = () => { | |
| const origin = location.origin; | |
| document.getElementById('apiUrl').value = | |
| (origin.startsWith('http') && !origin.includes('localhost') && !origin.includes('file:')) | |
| ? origin | |
| : 'https://dhyandevp-codeevaluator.hf.space'; | |
| }; | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Pick links — trigger the hidden file inputs | |
| * ────────────────────────────────────────────────────────────────── */ | |
| document.getElementById('pickFiles').addEventListener('click', e => { | |
| e.stopPropagation(); | |
| document.getElementById('fileInput').click(); | |
| }); | |
| document.getElementById('pickFolder').addEventListener('click', e => { | |
| e.stopPropagation(); | |
| document.getElementById('folderInput').click(); | |
| }); | |
| document.getElementById('fileInput').addEventListener('change', function () { onFilesSelected(this); }); | |
| document.getElementById('folderInput').addEventListener('change', function () { onFilesSelected(this); }); | |
| /* ────────────────────────────────────────────────────────────────── | |
| * File selection handler (works for both inputs) | |
| * ────────────────────────────────────────────────────────────────── */ | |
| function onFilesSelected(input) { | |
| const files = Array.from(input.files || []); | |
| if (!files.length) return; | |
| selectedFiles = files.map(f => { | |
| f._relativePath = f.webkitRelativePath || f.name; | |
| return f; | |
| }); | |
| renderPreview(); | |
| document.getElementById('submitBtn').disabled = false; | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Auto-detect upload mode from selected files | |
| * ────────────────────────────────────────────────────────────────── */ | |
| function _detectMode() { | |
| if (selectedFiles.length === 1) { | |
| const name = (selectedFiles[0].name || '').toLowerCase(); | |
| if (name.endsWith('.zip') || name.endsWith('.tar.gz') || name.endsWith('.tgz')) { | |
| return 'archive'; | |
| } | |
| return 'single'; | |
| } | |
| return 'multi'; | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Drag & drop — auto-detects folders vs files | |
| * ────────────────────────────────────────────────────────────────── */ | |
| const dz = document.getElementById('dropZone'); | |
| dz.addEventListener('dragover', e => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dz.classList.add('drag'); | |
| }); | |
| dz.addEventListener('dragleave', () => dz.classList.remove('drag')); | |
| dz.addEventListener('drop', async e => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dz.classList.remove('drag'); | |
| // Try recursive directory read first (works for dropped folders) | |
| const items = Array.from(e.dataTransfer.items || []); | |
| const allFiles = []; | |
| for (const item of items) { | |
| const entry = item.webkitGetAsEntry ? item.webkitGetAsEntry() : null; | |
| if (entry && entry.isDirectory) { | |
| const files = await readEntryRecursive(entry, ''); | |
| allFiles.push(...files); | |
| } else if (entry && entry.isFile) { | |
| const file = await new Promise((resolve, reject) => entry.file(resolve, reject)); | |
| file._relativePath = file.name; | |
| allFiles.push(file); | |
| } | |
| } | |
| // Fallback to dataTransfer.files if webkitGetAsEntry didn't work | |
| if (!allFiles.length) { | |
| const dropped = Array.from(e.dataTransfer.files || []); | |
| dropped.forEach(f => { f._relativePath = f.name; }); | |
| allFiles.push(...dropped); | |
| } | |
| if (allFiles.length) { | |
| selectedFiles = allFiles; | |
| renderPreview(); | |
| document.getElementById('submitBtn').disabled = false; | |
| } | |
| }); | |
| /** | |
| * Recursively read a dropped directory entry using the FileSystem API. | |
| */ | |
| async function readEntryRecursive(entry, basePath) { | |
| if (entry.isFile) { | |
| const file = await new Promise((resolve, reject) => entry.file(resolve, reject)); | |
| file._relativePath = basePath + file.name; | |
| return [file]; | |
| } | |
| if (entry.isDirectory) { | |
| const reader = entry.createReader(); | |
| // readEntries can return partial results, so loop until empty | |
| let allEntries = []; | |
| let batch; | |
| do { | |
| batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject)); | |
| allEntries.push(...batch); | |
| } while (batch.length > 0); | |
| const files = []; | |
| for (const child of allEntries) { | |
| const childFiles = await readEntryRecursive(child, basePath + entry.name + '/'); | |
| files.push(...childFiles); | |
| } | |
| return files; | |
| } | |
| return []; | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * File preview panel | |
| * ────────────────────────────────────────────────────────────────── */ | |
| function renderPreview() { | |
| const el = document.getElementById('filePreview'); | |
| if (!selectedFiles.length) { | |
| el.style.display = 'none'; | |
| return; | |
| } | |
| let totalBytes = 0; | |
| const lines = selectedFiles.map(f => { | |
| totalBytes += f.size; | |
| const name = f._relativePath || f.webkitRelativePath || f.name; | |
| return `<div class="preview-file"> ${esc(name)} <span class="fsize">${formatSize(f.size)}</span></div>`; | |
| }); | |
| el.innerHTML = | |
| `<div class="preview-header">Selected Files (${selectedFiles.length}):</div>` + | |
| lines.join('') + | |
| `<div class="preview-total">Total: ${formatSize(totalBytes)}</div>`; | |
| el.style.display = 'block'; | |
| } | |
| function formatSize(bytes) { | |
| if (bytes < 1024) return bytes + ' B'; | |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; | |
| return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Submission | |
| * ────────────────────────────────────────────────────────────────── */ | |
| async function submit() { | |
| const url = document.getElementById('apiUrl').value.trim().replace(/\/$/, ''); | |
| const apiKey = document.getElementById('apiKey').value.trim(); | |
| const qObj = document.getElementById('questionObjective').value.trim(); | |
| if (!url || !apiKey || !selectedFiles.length) return; | |
| if (pollTimer) clearInterval(pollTimer); | |
| document.getElementById('submitBtn').disabled = true; | |
| showStatus('processing', null, null, 'Submitting…'); | |
| const form = new FormData(); | |
| const mode = _detectMode(); | |
| if (mode === 'single') { | |
| // Single source file — use 'file' field for backwards compatibility | |
| form.append('file', selectedFiles[0], selectedFiles[0].name); | |
| } else if (mode === 'archive') { | |
| // Auto-detected .zip / .tar.gz / .tgz | |
| form.append('files', selectedFiles[0], selectedFiles[0].name); | |
| } else { | |
| // Multiple files (folder pick, multi-select, or dropped folder) | |
| for (const f of selectedFiles) { | |
| const relPath = f._relativePath || f.webkitRelativePath || f.name; | |
| form.append('files', f, relPath); | |
| } | |
| } | |
| if (qObj) form.append('question_objective', qObj); | |
| try { | |
| const res = await fetch(`${url}/submit`, { method: 'POST', headers: { 'X-API-Key': apiKey }, body: form }); | |
| const data = await res.json(); | |
| if (!res.ok) { | |
| if (res.status === 413) { | |
| showError('⚠️ Payload Too Large — The uploaded content exceeds the server\'s size limit. ' + | |
| 'For .zip archives, the total uncompressed source code must be under 1 MB. ' + | |
| 'Try removing non-essential files (images, binaries, dependencies) and re-upload.'); | |
| } else { | |
| showError(`Error ${res.status}: ${data.detail}`); | |
| } | |
| return; | |
| } | |
| const jobId = data.job_id; | |
| showStatus('processing', jobId, null, 'Evaluating your code…'); | |
| pollTimer = setInterval(() => poll(url, apiKey, jobId), 3000); | |
| } catch (e) { | |
| showError('Cannot reach the API. Check the URL and your internet connection.'); | |
| } | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Polling | |
| * ────────────────────────────────────────────────────────────────── */ | |
| async function poll(url, apiKey, jobId) { | |
| try { | |
| const res = await fetch(`${url}/report/${jobId}`, { headers: { 'X-API-Key': apiKey } }); | |
| const data = await res.json(); | |
| if (data.status === 'Processing') return; | |
| clearInterval(pollTimer); | |
| document.getElementById('submitBtn').disabled = false; | |
| if (data.status === 'Completed') { | |
| showStatus('completed', jobId, data.report, null); | |
| } else { | |
| showError(`Failed: ${data.error?.error_message || 'Unknown error'}`); | |
| } | |
| } catch (_) { /* keep polling */ } | |
| } | |
| /* ────────────────────────────────────────────────────────────────── | |
| * Result rendering | |
| * ────────────────────────────────────────────────────────────────── */ | |
| function showStatus(state, jobId, report, message) { | |
| const el = document.getElementById('status'); | |
| el.style.display = 'block'; | |
| const tagClass = { processing: 'tag-processing', completed: 'tag-completed', failed: 'tag-failed' }[state]; | |
| const tagLabel = { processing: '⏳ Processing', completed: '✅ Completed', failed: '❌ Failed' }[state]; | |
| let html = `<span class="tag ${tagClass}">${tagLabel}</span>`; | |
| if (state === 'processing') { | |
| html += `<br/><span class="spinner"></span>${message || 'Working…'}`; | |
| } | |
| if (state === 'completed' && report) { | |
| const r = report; | |
| /* ── Language badge ────────────────────────────────────────── */ | |
| html += `<div class="meta-row">`; | |
| html += `<span class="lang-badge">${esc(r.language || 'Unknown')}</span>`; | |
| html += `</div>`; | |
| /* ── Score ─────────────────────────────────────────────────── */ | |
| html += `<div class="score-row"> | |
| <div class="score-block"> | |
| <div class="score-num">${r.score}</div> | |
| <div class="score-lbl">Score / 100</div> | |
| </div> | |
| </div>`; | |
| /* ── Code Quality ──────────────────────────────────────────── */ | |
| if (r.code_quality) { | |
| html += `<div class="section-title">Code Quality</div>`; | |
| html += `<div class="quality-block">${esc(r.code_quality)}</div>`; | |
| } | |
| /* ── Questions ─────────────────────────────────────────────── */ | |
| if (r.questions && r.questions.length > 0) { | |
| html += `<div class="section-title">Questions (${r.questions.length})</div>`; | |
| r.questions.forEach(q => { | |
| const badgeClass = q.is_correct ? 'q-badge-pass' : 'q-badge-fail'; | |
| const badgeText = q.is_correct ? '✓ PASS' : '✗ FAIL'; | |
| html += `<div class="q-card"> | |
| <div class="q-header"> | |
| <span class="q-badge ${badgeClass}">${badgeText}</span> | |
| <span class="q-text">${esc(q.question)}</span> | |
| </div> | |
| <div class="q-feedback">${esc(q.feedback)}</div> | |
| </div>`; | |
| }); | |
| } | |
| } | |
| if (jobId) html += `<div class="job-id">job: ${jobId}</div>`; | |
| el.innerHTML = html; | |
| } | |
| function showError(msg) { | |
| clearInterval(pollTimer); | |
| document.getElementById('submitBtn').disabled = false; | |
| const el = document.getElementById('status'); | |
| el.style.display = 'block'; | |
| el.innerHTML = `<span class="tag tag-failed">❌ Failed</span><br/>${esc(msg)}`; | |
| } | |
| function esc(s) { | |
| return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); | |
| } | |
| </script> | |
| </body> | |
| </html> | |