Download Frontend.html from ajayk007/aad: direct link, hf CLI and curl.
- Browser
- Download file 25.3 kB
-
https://huggingface.co/spaces/ajayk007/aad/resolve/main/Frontend.html
- Command line
-
hf download hf://spaces/ajayk007/aad/Frontend.html
-
curl -L -o Frontend.html https://huggingface.co/spaces/ajayk007/aad/resolve/main/Frontend.html
25.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI CV Compatibility Scorer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom CSS from Deepsite - Keep this */ | |
| .file-input-label { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 1.5rem; | |
| border: 2px dashed #d1d5db; | |
| border-radius: 0.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .file-input-label:hover { | |
| border-color: #3b82f6; | |
| background-color: #f8fafc; | |
| } | |
| .file-input-label.drag-over { | |
| border-color: #3b82f6; | |
| background-color: #eff6ff; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| background-color: #e5e7eb; | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .progress-bar-fill { | |
| height: 100%; | |
| background-color: #3b82f6; | |
| transition: width 0.3s ease; | |
| } | |
| .score-cell { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .score-bar { | |
| width: 60px; | |
| height: 6px; | |
| background-color: #e5e7eb; | |
| border-radius: 3px; | |
| margin-right: 8px; | |
| overflow: hidden; | |
| } | |
| .score-bar-fill { | |
| height: 100%; | |
| background-color: #10b981; | |
| } | |
| @media (max-width: 768px) { | |
| .input-section { | |
| flex-direction: column; | |
| } | |
| .job-description, .cv-upload { | |
| width: 100%; | |
| } | |
| } | |
| /* NEW CSS for multi-line truncation in table explanations */ | |
| .truncate-multi-line { | |
| display: -webkit-box; | |
| -webkit-line-clamp: 3; /* Limit to 3 lines */ | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen flex flex-col"> | |
| <header class="bg-white shadow-sm"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-3"> | |
| <i class="fas fa-robot text-blue-500 text-2xl"></i> | |
| <h1 class="text-2xl font-bold text-gray-800">AI CV Compatibility Scorer</h1> | |
| </div> | |
| <div class="hidden md:flex items-center space-x-4"> | |
| <span class="text-sm text-gray-500">Powered by AI</span> | |
| <span class="h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center"> | |
| <i class="fas fa-brain text-blue-500"></i> | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <main class="flex-grow container mx-auto px-4 py-8"> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8"> | |
| <div class="p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Analyze CV Compatibility</h2> | |
| <p class="text-gray-600 mb-6">Upload job description and candidate CVs to get AI-powered compatibility scores.</p> | |
| <div class="flex input-section space-x-6"> | |
| <div class="job-description flex-1"> | |
| <label for="job-description" class="block text-sm font-medium text-gray-700 mb-2">Job Description</label> | |
| <textarea | |
| id="job-description" | |
| rows="8" | |
| class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" | |
| placeholder="Paste the job description here... Include requirements, responsibilities, and any other relevant details."></textarea> | |
| </div> | |
| <div class="cv-upload flex-1"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Upload CVs</label> | |
| <div class="file-input-container"> | |
| <input type="file" id="cv-upload" class="hidden" multiple accept=".pdf,.docx,.txt"> | |
| <label for="cv-upload" id="file-input-label" class="file-input-label"> | |
| <div class="text-center"> | |
| <i class="fas fa-cloud-upload-alt text-gray-400 text-3xl mb-2"></i> | |
| <p class="text-sm text-gray-600">Drag & drop CV files here or click to browse</p> | |
| <p class="text-xs text-gray-500 mt-1">Supports PDF, DOCX, TXT (Max 10 files)</p> | |
| </div> | |
| </label> | |
| <div id="file-list" class="mt-3 space-y-2 max-h-40 overflow-y-auto hidden"> | |
| <p class="text-sm font-medium text-gray-700">Selected files:</p> | |
| <div id="file-items" class="space-y-1"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end"> | |
| <button id="score-button" class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg shadow-sm transition duration-150 ease-in-out flex items-center"> | |
| <i class="fas fa-calculator mr-2"></i> Score CVs | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="results-section" class="bg-white rounded-xl shadow-md overflow-hidden hidden"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Compatibility Results</h2> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-sm text-gray-500">Processing:</span> | |
| <div class="progress-bar w-32"> | |
| <div id="progress-bar-fill" class="progress-bar-fill" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CV Filename</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Score</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Explanation</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="results-table" class="bg-white divide-y divide-gray-200"> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <button id="export-button" class="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 flex items-center"> | |
| <i class="fas fa-file-export mr-2"></i> Export Results | |
| </button> | |
| <button id="new-analysis-button" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm font-medium text-gray-700 flex items-center"> | |
| <i class="fas fa-redo mr-2"></i> New Analysis | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="loading-state" class="hidden fixed inset-0 bg-black bg-opacity-30 flex items-center justify-center z-50"> | |
| <div class="bg-white rounded-xl p-8 max-w-md w-full text-center"> | |
| <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto mb-4"></div> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Analyzing CVs</h3> | |
| <p class="text-gray-600 mb-4">Our AI is evaluating the compatibility of your CVs with the job description.</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="loading-progress" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <p id="loading-text" class="text-sm text-gray-500 mt-2">Initializing analysis...</p> | |
| </div> | |
| </div> | |
| </main> | |
| <footer class="bg-white border-t border-gray-200 py-6"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="flex items-center space-x-2 mb-4 md:mb-0"> | |
| <i class="fas fa-robot text-blue-500"></i> | |
| <span class="text-sm font-medium text-gray-700">AI CV Compatibility Scorer</span> | |
| </div> | |
| <div class="flex space-x-6"> | |
| <a href="#" class="text-sm text-gray-500 hover:text-gray-700">Privacy Policy</a> | |
| <a href="#" class="text-sm text-gray-500 hover:text-gray-700">Terms of Service</a> | |
| <a href="#" class="text-sm text-gray-500 hover:text-gray-700">Contact Us</a> | |
| </div> | |
| </div> | |
| <div class="mt-4 text-center md:text-left"> | |
| <p class="text-xs text-gray-400">© 2023 AI Talent Solutions. All rights reserved.</p> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Get all necessary DOM elements | |
| const fileInput = document.getElementById('cv-upload'); | |
| const fileInputLabel = document.getElementById('file-input-label'); | |
| const fileList = document.getElementById('file-list'); | |
| const fileItems = document.getElementById('file-items'); | |
| const scoreButton = document.getElementById('score-button'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const resultsTable = document.getElementById('results-table'); | |
| const loadingState = document.getElementById('loading-state'); | |
| const loadingProgress = document.getElementById('loading-progress'); | |
| const loadingText = document.getElementById('loading-text'); | |
| const progressBarFill = document.getElementById('progress-bar-fill'); // Added for the small progress bar at the top | |
| const exportButton = document.getElementById('export-button'); | |
| const newAnalysisButton = document.getElementById('new-analysis-button'); | |
| let files = []; // Stores the files selected by the user | |
| // --- File Upload Logic (from Deepsite, remains largely the same) --- | |
| fileInputLabel.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| fileInputLabel.classList.add('drag-over'); | |
| }); | |
| fileInputLabel.addEventListener('dragleave', () => { | |
| fileInputLabel.classList.remove('drag-over'); | |
| }); | |
| fileInputLabel.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| fileInputLabel.classList.remove('drag-over'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFiles(); | |
| } | |
| }); | |
| fileInput.addEventListener('change', handleFiles); | |
| function handleFiles() { | |
| files = Array.from(fileInput.files); | |
| if (files.length > 10) { | |
| alert('You can upload a maximum of 10 files.'); | |
| files = files.slice(0, 10); // Truncate if too many files | |
| } | |
| updateFileList(); | |
| } | |
| function updateFileList() { | |
| fileItems.innerHTML = ''; // Clear previous file list | |
| if (files.length > 0) { | |
| fileList.classList.remove('hidden'); | |
| files.forEach((file, index) => { | |
| const fileItem = document.createElement('div'); | |
| fileItem.className = 'flex items-center justify-between p-2 bg-gray-50 rounded'; | |
| const fileInfo = document.createElement('div'); | |
| fileInfo.className = 'flex items-center truncate'; | |
| let iconClass = 'fas fa-file-alt'; | |
| if (file.name.endsWith('.pdf')) iconClass = 'fas fa-file-pdf'; | |
| if (file.name.endsWith('.docx')) iconClass = 'fas fa-file-word'; | |
| fileInfo.innerHTML = ` | |
| <i class="${iconClass} text-gray-500 mr-2"></i> | |
| <span class="text-sm text-gray-700 truncate" title="${file.name}">${file.name}</span> | |
| <span class="text-xs text-gray-500 ml-2">(${(file.size / 1024).toFixed(1)} KB)</span> | |
| `; | |
| const removeButton = document.createElement('button'); | |
| removeButton.className = 'text-gray-400 hover:text-red-500 ml-2'; | |
| removeButton.innerHTML = '<i class="fas fa-times"></i>'; | |
| removeButton.addEventListener('click', () => { | |
| files.splice(index, 1); // Remove file from array | |
| updateFileList(); // Refresh the list | |
| }); | |
| fileItem.appendChild(fileInfo); | |
| fileItem.appendChild(removeButton); | |
| fileItems.appendChild(fileItem); | |
| }); | |
| } else { | |
| fileList.classList.add('hidden'); // Hide the file list if no files | |
| } | |
| } | |
| // --- Score Button Click Handler (MODIFIED) --- | |
| scoreButton.addEventListener('click', async () => { // Made async to use await | |
| const jobDescription = document.getElementById('job-description').value.trim(); | |
| if (!jobDescription) { | |
| alert('Please enter a job description.'); | |
| return; | |
| } | |
| if (files.length === 0) { | |
| alert('Please upload at least one CV file.'); | |
| return; | |
| } | |
| // Show loading state | |
| loadingState.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; // Prevent scrolling while loading | |
| loadingProgress.style.width = '0%'; // Reset inner loading bar | |
| progressBarFill.style.width = '0%'; // Reset outer progress bar | |
| loadingText.textContent = 'Sending data to AI for analysis...'; | |
| progressBarFill.style.backgroundColor = '#3b82f6'; // Reset color for outer bar | |
| const formData = new FormData(); | |
| formData.append('job_description_text', jobDescription); | |
| files.forEach(file => { | |
| formData.append('cv_files', file); // Append each file to the form data | |
| }); | |
| try { | |
| // !!! IMPORTANT: REPLACE THIS URL with the actual URL of your deployed FastAPI Backend Space !!! | |
| // Example: 'https://<your-username>-my-cv-scorer-backend.hf.space/score_cvs' | |
| const backendApiUrl = 'YOUR_FASTAPI_BACKEND_API_URL_HERE/score_cvs'; | |
| // Simulate progress during API call (adjust intervals as needed) | |
| let apiProgress = 0; | |
| const apiProgressListener = setInterval(() => { | |
| apiProgress += Math.random() * 5; // Simulate slower progress for API call | |
| if (apiProgress > 95) apiProgress = 95; // Stop before 100 as final completion is after response | |
| loadingProgress.style.width = `${apiProgress}%`; | |
| progressBarFill.style.width = `${apiProgress}%`; | |
| }, 500); | |
| const response = await fetch(backendApiUrl, { | |
| method: 'POST', | |
| body: formData // Send as multipart/form-data | |
| }); | |
| clearInterval(apiProgressListener); // Stop simulated progress after response | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| loadingText.textContent = `Error: ${errorData.detail || 'Unknown error'}`; | |
| loadingProgress.style.width = '100%'; // Show full progress on error | |
| progressBarFill.style.width = '100%'; | |
| progressBarFill.style.backgroundColor = '#ef4444'; // Red for error | |
| throw new Error(`API Error: ${response.status} - ${errorData.detail || 'Unknown error'}`); | |
| } | |
| loadingText.textContent = 'Analysis complete! Displaying results...'; | |
| loadingProgress.style.width = '100%'; | |
| progressBarFill.style.width = '100%'; | |
| progressBarFill.style.backgroundColor = '#10b981'; // Green for success | |
| const results = await response.json(); // Parse the JSON response from your API | |
| // Give a moment for the user to see "complete" message before hiding loading state | |
| setTimeout(() => { | |
| loadingState.classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; // Re-enable scrolling | |
| resultsSection.classList.remove('hidden'); | |
| updateResultsTable(results); // Call the function to populate results table | |
| resultsSection.scrollIntoView({ behavior: 'smooth' }); // Scroll to results | |
| }, 800); // 800ms delay | |
| } catch (error) { | |
| console.error("Failed to score CVs:", error); | |
| clearInterval(apiProgressListener); // Ensure interval cleared on any error | |
| loadingState.classList.add('hidden'); // Hide loading on error | |
| document.body.style.overflow = 'auto'; | |
| alert("Error scoring CVs: " + error.message + ". Please ensure your backend API URL is correct and the server is running."); | |
| progressBarFill.style.backgroundColor = '#3b82f6'; // Reset color | |
| } | |
| }); | |
| // --- Function to Update Results Table (MODIFIED) --- | |
| function updateResultsTable(results) { | |
| resultsTable.innerHTML = ''; // Clear existing results | |
| if (!results || results.length === 0) { | |
| const noResultsRow = document.createElement('tr'); | |
| noResultsRow.innerHTML = ` | |
| <td colspan="4" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">No compatibility results found.</td> | |
| `; | |
| resultsTable.appendChild(noResultsRow); | |
| return; | |
| } | |
| results.forEach((item, index) => { | |
| const row = document.createElement('tr'); | |
| row.className = index % 2 === 0 ? 'bg-white' : 'bg-gray-50'; | |
| // Ensure score is a number for formatting and progress bar width | |
| const score = parseFloat(item['Score (%)']); | |
| const scoreDisplay = isNaN(score) ? 'N/A' : `${score.toFixed(2)}%`; | |
| const scoreBarWidth = isNaN(score) ? 0 : Math.min(100, Math.max(0, score)); // Clamp between 0 and 100% | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> | |
| <div class="flex items-center"> | |
| <i class="fas ${item['CV Filename'].endsWith('.pdf') ? 'fa-file-pdf text-red-500' : item['CV Filename'].endsWith('.docx') ? 'fa-file-word text-blue-500' : 'fa-file-alt text-gray-500'} mr-2"></i> | |
| ${item['CV Filename']} | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="score-cell"> | |
| <div class="score-bar"> | |
| <div class="score-bar-fill" style="width: ${scoreBarWidth}%"></div> | |
| </div> | |
| <span class="text-sm font-medium text-gray-800">${scoreDisplay}</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 text-sm text-gray-700 max-w-lg truncate-multi-line" title="${item['Explanation']}"> | |
| ${item['Explanation']} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | |
| <a href="#" class="text-blue-600 hover:text-blue-900">View Details</a> | |
| </td> | |
| `; | |
| resultsTable.appendChild(row); | |
| }); | |
| } | |
| // --- New Analysis Button (ADDED) --- | |
| newAnalysisButton.addEventListener('click', () => { | |
| document.getElementById('job-description').value = ''; // Clear Job Description | |
| fileInput.value = ''; // Clear file input element itself | |
| files = []; // Clear the JavaScript files array | |
| updateFileList(); // Update the displayed file list | |
| resultsTable.innerHTML = ''; // Clear results table content | |
| resultsSection.classList.add('hidden'); // Hide results section | |
| document.body.style.overflow = 'auto'; // Re-enable body scrolling | |
| progressBarFill.style.width = '0%'; // Reset progress bar | |
| progressBarFill.style.backgroundColor = '#3b82f6'; // Reset progress bar color | |
| }); | |
| // --- Export Button (ADDED) --- | |
| exportButton.addEventListener('click', () => { | |
| // Ensure there are results to export | |
| if (resultsTable.rows.length === 0 || resultsTable.rows[0].cells[0].colSpan === 4) { // Check for "No results" row | |
| alert('No results to export.'); | |
| return; | |
| } | |
| let csv = 'CV Filename,Score (%),Explanation\n'; // CSV header | |
| // Iterate through table rows to build CSV content | |
| resultsTable.querySelectorAll('tr').forEach(row => { | |
| const cells = row.querySelectorAll('td'); | |
| if (cells.length >= 3) { // Ensure row has enough cells for data | |
| const filename = cells[0].textContent.trim(); | |
| // Get score text content, which includes percentage | |
| const score = cells[1].querySelector('span').textContent.trim(); | |
| // Get explanation, remove line breaks and escape quotes for CSV | |
| const explanation = cells[2].textContent.trim().replace(/\n/g, ' ').replace(/"/g, '""'); | |
| csv += `"${filename}","${score}","${explanation}"\n`; | |
| } | |
| }); | |
| const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); | |
| const link = document.createElement('a'); | |
| // Create a temporary downloadable link | |
| if (link.download !== undefined) { // Feature detection for download attribute | |
| const url = URL.createObjectURL(blob); | |
| link.setAttribute('href', url); | |
| link.setAttribute('download', 'cv_compatibility_results.csv'); | |
| link.style.visibility = 'hidden'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } else { | |
| // Fallback for browsers that don't support download attribute | |
| alert('Your browser does not support direct file download. Please copy the results manually from the table.'); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |