Download static/app.js from Gehan77/CV: direct link, hf CLI and curl.
- Browser
- Download file 4.82 kB
-
https://huggingface.co/spaces/Gehan77/CV/resolve/main/static/app.js
- Command line
-
hf download hf://spaces/Gehan77/CV/static/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Gehan77/CV/resolve/main/static/app.js
4.82 kB
| const $ = (id) => document.getElementById(id); | |
| const cvFile = $("cvFile"); | |
| const dropzone = $("dropzone"); | |
| const fileLabel = $("fileLabel"); | |
| const extractButton = $("extractButton"); | |
| const scoreButton = $("scoreButton"); | |
| const status = $("status"); | |
| const preview = $("preview"); | |
| const result = $("result"); | |
| let selectedFile = null; | |
| function setStatus(message, type = "") { | |
| status.textContent = message; | |
| status.className = `status ${type}`.trim(); | |
| } | |
| function value(id) { | |
| return $(id).value.trim(); | |
| } | |
| function setSelectedFile(file) { | |
| if (!file) return; | |
| selectedFile = file; | |
| fileLabel.textContent = file.name; | |
| extractButton.disabled = false; | |
| setStatus("File selected. Click Extract CV details to fill the form."); | |
| } | |
| function applyCandidate(candidate) { | |
| const mapping = { | |
| candidateRole: candidate.role, | |
| candidateSeniority: candidate.seniority, | |
| yearsExperience: candidate.years_experience, | |
| candidateIndustry: candidate.industry, | |
| education: candidate.education, | |
| candidateSkills: candidate.skills, | |
| summary: candidate.summary, | |
| experienceBullets: candidate.experience_bullets, | |
| }; | |
| for (const [id, fieldValue] of Object.entries(mapping)) $(id).value = fieldValue || ""; | |
| } | |
| async function extractCv() { | |
| if (!selectedFile) return; | |
| extractButton.disabled = true; | |
| setStatus("Uploading CV to the FastAPI service for extraction…"); | |
| try { | |
| const body = new FormData(); | |
| body.append("file", selectedFile); | |
| const response = await fetch("/api/extract-cv", { method: "POST", body }); | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.detail || "Could not read this CV."); | |
| preview.value = data.preview || ""; | |
| applyCandidate(data.candidate || {}); | |
| const filled = Object.values(data.candidate || {}).filter(Boolean).length; | |
| const warning = data.warnings?.length ? ` ${data.warnings.join(" ")}` : ""; | |
| setStatus(`Extracted ${filled} of 8 candidate fields. Please review them before scoring.${warning}`, data.warnings?.length ? "" : "success"); | |
| } catch (error) { | |
| setStatus(error.message || "Could not read this CV.", "error"); | |
| } finally { | |
| extractButton.disabled = false; | |
| } | |
| } | |
| function buildPayload() { | |
| return { | |
| job: { | |
| job_title: value("jobTitle"), | |
| seniority: value("jobSeniority"), | |
| industry: value("jobIndustry"), | |
| must_have_skills: value("mustHaveSkills"), | |
| nice_to_have_skills: value("niceToHaveSkills"), | |
| description: value("jobDescription"), | |
| responsibilities: value("responsibilities"), | |
| requirements: value("requirements"), | |
| }, | |
| candidate: { | |
| role: value("candidateRole"), | |
| seniority: value("candidateSeniority"), | |
| years_experience: value("yearsExperience"), | |
| industry: value("candidateIndustry"), | |
| education: value("education"), | |
| skills: value("candidateSkills"), | |
| summary: value("summary"), | |
| experience_bullets: value("experienceBullets"), | |
| }, | |
| }; | |
| } | |
| async function scoreMatch() { | |
| scoreButton.disabled = true; | |
| scoreButton.textContent = "Scoring…"; | |
| setStatus("FastAPI is scoring the job and candidate profile…"); | |
| result.hidden = true; | |
| try { | |
| const response = await fetch("/api/score", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(buildPayload()), | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.detail || "Scoring failed."); | |
| const percent = (data.probability * 100).toFixed(1); | |
| result.innerHTML = ` | |
| <div class="result-top"> | |
| <div><h3>${data.verdict}</h3><p>Model-estimated relevance for this job and candidate profile.</p></div> | |
| <div class="score-number">${percent}%</div> | |
| </div> | |
| <div class="score-bar"><span style="width:${Math.max(2, data.probability * 100)}%"></span></div> | |
| <p>Decision threshold: ${(data.threshold * 100).toFixed(1)}%</p> | |
| <p class="result-note">${data.disclaimer}</p> | |
| `; | |
| result.hidden = false; | |
| setStatus("Match scored by FastAPI.", "success"); | |
| } catch (error) { | |
| setStatus(error.message || "Scoring failed.", "error"); | |
| } finally { | |
| scoreButton.disabled = false; | |
| scoreButton.innerHTML = 'Score match <span>→</span>'; | |
| } | |
| } | |
| cvFile.addEventListener("change", (event) => setSelectedFile(event.target.files[0])); | |
| extractButton.addEventListener("click", extractCv); | |
| scoreButton.addEventListener("click", scoreMatch); | |
| dropzone.addEventListener("dragover", (event) => { event.preventDefault(); dropzone.classList.add("dragging"); }); | |
| dropzone.addEventListener("dragleave", () => dropzone.classList.remove("dragging")); | |
| dropzone.addEventListener("drop", (event) => { | |
| event.preventDefault(); | |
| dropzone.classList.remove("dragging"); | |
| setSelectedFile(event.dataTransfer.files[0]); | |
| }); | |