Spaces:
Runtime error
Runtime error
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Technical Interview Analyzer</title> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> | |
<style> | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
background-color: #f5f5f5; | |
} | |
.container { | |
background-color: white; | |
padding: 30px; | |
border-radius: 12px; | |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
margin-bottom: 30px; | |
} | |
h1, h2, h3 { | |
color: #2c3e50; | |
} | |
h1 { | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
.section { | |
margin-bottom: 40px; | |
} | |
.upload-section { | |
text-align: center; | |
margin: 30px 0; | |
padding: 20px; | |
background-color: #f8f9fa; | |
border-radius: 8px; | |
} | |
#uploadForm { | |
margin: 20px 0; | |
} | |
.results-container { | |
margin-top: 30px; | |
} | |
.progress-bar { | |
background-color: #3498db; | |
} | |
button { | |
background-color: #3498db; | |
color: white; | |
padding: 12px 24px; | |
border: none; | |
border-radius: 6px; | |
cursor: pointer; | |
font-size: 16px; | |
transition: background-color 0.3s; | |
} | |
button:hover { | |
background-color: #2980b9; | |
} | |
.loading { | |
text-align: center; | |
margin: 20px 0; | |
display: none; | |
} | |
.error { | |
color: #e74c3c; | |
padding: 10px; | |
background-color: #fde8e8; | |
border-radius: 4px; | |
margin: 10px 0; | |
} | |
/* Print styles */ | |
@media print { | |
body { | |
padding: 0; | |
margin: 0; | |
} | |
.container { | |
box-shadow: none; | |
padding: 20px; | |
} | |
.upload-section, | |
#uploadForm, | |
#loading, | |
#downloadExcelBtn, | |
.btn { | |
display: none ; | |
} | |
.assessment-card { | |
break-inside: avoid; | |
} | |
} | |
.loading-overlay { | |
display: none; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0, 0, 0, 0.5); | |
z-index: 1000; | |
} | |
.loading-content { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: white; | |
padding: 30px; | |
border-radius: 10px; | |
text-align: center; | |
} | |
.spinner { | |
width: 50px; | |
height: 50px; | |
border: 5px solid #f3f3f3; | |
border-top: 5px solid #3498db; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
margin: 0 auto 20px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.loading-text { | |
color: #2c3e50; | |
font-size: 18px; | |
margin-top: 15px; | |
} | |
.progress-steps { | |
margin-top: 15px; | |
text-align: left; | |
} | |
.step { | |
margin: 8px 0; | |
color: #666; | |
} | |
.step.active { | |
color: #3498db; | |
font-weight: bold; | |
} | |
.step.completed { | |
color: #2ecc71; | |
} | |
.score-display { | |
display: flex; | |
align-items: center; | |
margin-bottom: 15px; | |
} | |
.score-number { | |
font-size: 2rem; | |
font-weight: bold; | |
margin-right: 15px; | |
color: #2c3e50; | |
min-width: 50px; | |
text-align: center; | |
} | |
.progress { | |
flex-grow: 1; | |
height: 15px; | |
} | |
.list-group-item { | |
border-left: none; | |
border-right: none; | |
} | |
.assessment-card { | |
margin-bottom: 25px; | |
border-radius: 10px; | |
overflow: hidden; | |
} | |
.assessment-card .card-header { | |
font-weight: bold; | |
padding: 15px; | |
} | |
.card-header-technical { | |
background-color: #4a90e2; | |
color: white; | |
} | |
.card-header-problem { | |
background-color: #50c878; | |
color: white; | |
} | |
.card-header-communication { | |
background-color: #6a5acd; | |
color: white; | |
} | |
.card-header-questions { | |
background-color: #ff7f50; | |
color: white; | |
} | |
.card-header-recommendation { | |
background-color: #2c3e50; | |
color: white; | |
} | |
.card-header-emotions { | |
background-color: #9b59b6; | |
color: white; | |
} | |
.badge-excellent { | |
background-color: #28a745; | |
} | |
.badge-good { | |
background-color: #4a90e2; | |
} | |
.badge-average { | |
background-color: #ffc107; | |
color: #212529; | |
} | |
.badge-poor { | |
background-color: #dc3545; | |
} | |
.recommendation-display { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
margin: 20px 0; | |
} | |
.recommendation-badge { | |
padding: 10px 20px; | |
font-size: 1.2rem; | |
margin-bottom: 15px; | |
border-radius: 30px; | |
} | |
.recommendation-hire { | |
background-color: #28a745; | |
color: white; | |
} | |
.recommendation-strong { | |
background-color: #4a90e2; | |
color: white; | |
} | |
.recommendation-consider { | |
background-color: #ffc107; | |
color: #212529; | |
} | |
.recommendation-reject { | |
background-color: #dc3545; | |
color: white; | |
} | |
.transcript-container { | |
max-height: 300px; | |
overflow-y: auto; | |
background-color: #f8f9fa; | |
padding: 15px; | |
border-radius: 5px; | |
margin-top: 15px; | |
border: 1px solid #dee2e6; | |
} | |
#accordionTranscript .accordion-button:not(.collapsed) { | |
background-color: #e7f5ff; | |
color: #0c63e4; | |
} | |
.question-card { | |
margin-bottom: 15px; | |
border-left: 4px solid #4a90e2; | |
} | |
.question-text { | |
font-weight: bold; | |
} | |
.answer-quality { | |
display: inline-block; | |
padding: 3px 10px; | |
border-radius: 15px; | |
font-size: 0.85rem; | |
margin: 5px 0; | |
} | |
.emotion-bar { | |
height: 30px; | |
margin-bottom: 10px; | |
border-radius: 5px; | |
position: relative; | |
} | |
.emotion-label { | |
position: absolute; | |
left: 10px; | |
top: 5px; | |
color: white; | |
font-weight: bold; | |
text-shadow: 1px 1px 1px rgba(0,0,0,0.5); | |
} | |
.emotion-percentage { | |
position: absolute; | |
right: 10px; | |
top: 5px; | |
color: white; | |
font-weight: bold; | |
text-shadow: 1px 1px 1px rgba(0,0,0,0.5); | |
} | |
.emotion-angry { | |
background-color: #e74c3c; | |
} | |
.emotion-disgust { | |
background-color: #8e44ad; | |
} | |
.emotion-fear { | |
background-color: #34495e; | |
} | |
.emotion-happy { | |
background-color: #27ae60; | |
} | |
.emotion-neutral { | |
background-color: #7f8c8d; | |
} | |
.emotion-sad { | |
background-color: #3498db; | |
} | |
.emotion-surprise { | |
background-color: #f39c12; | |
} | |
/* Add new styles for video preview */ | |
.video-preview-container { | |
margin-top: 15px; | |
max-width: 100%; | |
display: none; | |
} | |
.video-preview { | |
width: 100%; | |
max-width: 640px; | |
margin: 0 auto; | |
display: block; | |
border-radius: 8px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
/* Add processing status styles */ | |
.processing-status { | |
margin-top: 10px; | |
padding: 10px; | |
border-radius: 5px; | |
background-color: #f8f9fa; | |
display: none; | |
} | |
.processing-status.success { | |
background-color: #d4edda; | |
color: #155724; | |
} | |
.processing-status.error { | |
background-color: #f8d7da; | |
color: #721c24; | |
} | |
/* Improve loading overlay */ | |
.loading-overlay .progress { | |
width: 100%; | |
margin-top: 15px; | |
height: 10px; | |
} | |
.step-status { | |
display: inline-block; | |
margin-left: 10px; | |
font-size: 14px; | |
} | |
.step-time { | |
float: right; | |
color: #666; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="loading-overlay" id="loadingOverlay"> | |
<div class="loading-content"> | |
<div class="spinner"></div> | |
<div class="loading-text">Processing your interview recording...</div> | |
<div class="progress-steps"> | |
<div class="step" id="step1">1. Uploading video file...</div> | |
<div class="step" id="step2">2. Extracting audio and analyzing emotions...</div> | |
<div class="step" id="step3">3. Transcribing audio...</div> | |
<div class="step" id="step4">4. Analyzing technical interview...</div> | |
</div> | |
</div> | |
</div> | |
<div class="container section"> | |
<h1>An AI Based Analyzer for Personal Attributes</h1> | |
<p class="text-center text-muted">Upload a recording of a technical interview to get AI-powered analysis and feedback</p> | |
<form id="uploadForm" class="mb-4"> | |
<div class="row mb-3"> | |
<div class="col-md-6"> | |
<label for="candidateName" class="form-label">Candidate Name</label> | |
<input type="text" id="candidateName" name="candidate_name" class="form-control" placeholder="Enter candidate name" required> | |
</div> | |
<div class="col-md-6"> | |
<label for="roleApplied" class="form-label">Role Applied For</label> | |
<input type="text" id="roleApplied" name="role_applied" class="form-control" placeholder="e.g. Senior Full Stack Developer" required> | |
</div> | |
</div> | |
<div class="mb-3"> | |
<label for="techSkills" class="form-label">Technical Skills to Evaluate (comma-separated)</label> | |
<input type="text" id="techSkills" name="tech_skills" class="form-control" placeholder="e.g. JavaScript, React, Node.js, System Design"> | |
</div> | |
<div class="mb-3"> | |
<label for="videoFile" class="form-label">Upload Interview Recording</label> | |
<input type="file" id="videoFile" name="video" accept="video/*" class="form-control" required> | |
<div class="form-text">Upload a video recording of the technical interview (.mp4, .mov, .avi)</div> | |
<div class="video-preview-container"> | |
<video id="videoPreview" class="video-preview" controls> | |
Your browser does not support the video tag. | |
</video> | |
</div> | |
<div id="processingStatus" class="processing-status"></div> | |
</div> | |
<div class="d-grid"> | |
<button type="submit" class="btn btn-primary"> | |
<i class="fas fa-analytics"></i> Analyze Interview | |
</button> | |
</div> | |
</form> | |
</div> | |
<!-- Assessment Results Section --> | |
<div class="container section" id="assessmentResultContainer" style="display:none;"> | |
<h2 class="text-center mb-4">Interview Assessment Results</h2> | |
<div id="candidateInfo" class="text-center mb-4"></div> | |
<div class="row"> | |
<!-- Technical Knowledge Section --> | |
<div class="col-md-4"> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-technical"> | |
<i class="fas fa-code"></i> Technical Knowledge | |
</div> | |
<div class="card-body"> | |
<div id="technicalScore" class="score-display"> | |
<div class="score-number">-</div> | |
<div class="progress"> | |
<div class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="10"></div> | |
</div> | |
</div> | |
<h5>Strengths</h5> | |
<ul id="technicalStrengths" class="list-group list-group-flush mb-3"></ul> | |
<h5>Areas for Improvement</h5> | |
<ul id="technicalImprovements" class="list-group list-group-flush"></ul> | |
</div> | |
</div> | |
</div> | |
<!-- Problem Solving Section --> | |
<div class="col-md-4"> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-problem"> | |
<i class="fas fa-puzzle-piece"></i> Problem Solving | |
</div> | |
<div class="card-body"> | |
<div id="problemSolvingScore" class="score-display"> | |
<div class="score-number">-</div> | |
<div class="progress"> | |
<div class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="10"></div> | |
</div> | |
</div> | |
<h5>Strengths</h5> | |
<ul id="problemSolvingStrengths" class="list-group list-group-flush mb-3"></ul> | |
<h5>Areas for Improvement</h5> | |
<ul id="problemSolvingImprovements" class="list-group list-group-flush"></ul> | |
</div> | |
</div> | |
</div> | |
<!-- Communication Section --> | |
<div class="col-md-4"> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-communication"> | |
<i class="fas fa-comments"></i> Communication | |
</div> | |
<div class="card-body"> | |
<div id="communicationScore" class="score-display"> | |
<div class="score-number">-</div> | |
<div class="progress"> | |
<div class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="10"></div> | |
</div> | |
</div> | |
<h5>Strengths</h5> | |
<ul id="communicationStrengths" class="list-group list-group-flush mb-3"></ul> | |
<h5>Areas for Improvement</h5> | |
<ul id="communicationImprovements" class="list-group list-group-flush"></ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Emotion Analysis Section --> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-emotions"> | |
<i class="fas fa-smile"></i> Emotion Analysis | |
</div> | |
<div class="card-body"> | |
<p>Facial emotions detected throughout the interview:</p> | |
<div id="emotionAnalysis" class="mb-4"></div> | |
<div class="row"> | |
<div class="col-md-6"> | |
<p><strong>Total Faces Detected:</strong> <span id="totalFaces">-</span></p> | |
<p><strong>Frames Processed:</strong> <span id="framesProcessed">-</span></p> | |
</div> | |
<div class="col-md-6"> | |
<p><strong>Frames with Faces:</strong> <span id="framesWithFaces">-</span></p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Overall Recommendation --> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-recommendation"> | |
<i class="fas fa-thumbs-up"></i> Overall Recommendation | |
</div> | |
<div class="card-body text-center"> | |
<div id="overallRecommendation" class="recommendation-display"> | |
<div class="recommendation-badge">-</div> | |
</div> | |
<div id="overallFeedback" class="mt-3"></div> | |
</div> | |
</div> | |
<!-- Questions Analysis Section --> | |
<div class="card assessment-card"> | |
<div class="card-header card-header-questions"> | |
<i class="fas fa-question-circle"></i> Question Analysis | |
</div> | |
<div class="card-body"> | |
<div id="questionAnalysis"></div> | |
</div> | |
</div> | |
<!-- Interview Transcript --> | |
<div class="card"> | |
<div class="card-header"> | |
Interview Transcript | |
</div> | |
<div class="card-body"> | |
<div class="accordion" id="accordionTranscript"> | |
<div class="accordion-item"> | |
<h2 class="accordion-header" id="headingTranscript"> | |
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTranscript" aria-expanded="false" aria-controls="collapseTranscript"> | |
Show Full Transcript | |
</button> | |
</h2> | |
<div id="collapseTranscript" class="accordion-collapse collapse" aria-labelledby="headingTranscript" data-bs-parent="#accordionTranscript"> | |
<div class="accordion-body"> | |
<div id="transcriptText" class="transcript-container"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Action Buttons --> | |
<div class="d-flex justify-content-center gap-3 mt-4"> | |
<button id="downloadExcelBtn" class="btn btn-success"> | |
<i class="fas fa-file-excel"></i> Download Assessment Report | |
</button> | |
<button id="printAssessmentBtn" class="btn btn-secondary"> | |
<i class="fas fa-print"></i> Print Assessment | |
</button> | |
<button id="newAnalysisBtn" class="btn btn-primary"> | |
<i class="fas fa-plus"></i> New Analysis | |
</button> | |
</div> | |
</div> | |
<!-- Error Modal --> | |
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true"> | |
<div class="modal-dialog"> | |
<div class="modal-content"> | |
<div class="modal-header bg-danger text-white"> | |
<h5 class="modal-title" id="errorModalLabel">Error</h5> | |
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | |
</div> | |
<div class="modal-body" id="errorModalBody"> | |
An error occurred while processing your request. | |
</div> | |
<div class="modal-footer"> | |
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Bootstrap JS --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> | |
<!-- SheetJS (for Excel export) --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
<!-- FileSaver.js (for Excel download) --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Get DOM elements | |
const uploadForm = document.getElementById('uploadForm'); | |
const loadingOverlay = document.getElementById('loadingOverlay'); | |
const step1 = document.getElementById('step1'); | |
const step2 = document.getElementById('step2'); | |
const step3 = document.getElementById('step3'); | |
const step4 = document.getElementById('step4'); | |
const assessmentResultContainer = document.getElementById('assessmentResultContainer'); | |
const downloadExcelBtn = document.getElementById('downloadExcelBtn'); | |
const printAssessmentBtn = document.getElementById('printAssessmentBtn'); | |
const newAnalysisBtn = document.getElementById('newAnalysisBtn'); | |
const errorModal = new bootstrap.Modal(document.getElementById('errorModal')); | |
const errorModalBody = document.getElementById('errorModalBody'); | |
// Store the current analysis results | |
let currentAnalysisResults = null; | |
// Test server connection on page load | |
async function testServerConnection() { | |
try { | |
const response = await fetch('/test'); | |
if (!response.ok) { | |
throw new Error(`Server responded with status: ${response.status}`); | |
} | |
const data = await response.json(); | |
console.log('Server connection test:', data); | |
return true; | |
} catch (error) { | |
console.error('Server connection test failed:', error); | |
showError('Server connection failed. Please check if the server is running.'); | |
return false; | |
} | |
} | |
// Test connection on page load | |
testServerConnection(); | |
// Form submission handler | |
uploadForm.addEventListener('submit', async function(e) { | |
e.preventDefault(); | |
console.log('Form submission started'); | |
// Test server connection before proceeding | |
const isServerConnected = await testServerConnection(); | |
if (!isServerConnected) { | |
return; | |
} | |
// Get form data | |
const formData = new FormData(uploadForm); | |
const candidateName = formData.get('candidate_name'); | |
const roleApplied = formData.get('role_applied'); | |
const techSkills = formData.get('tech_skills'); | |
const videoFile = formData.get('video'); | |
// Validate video file | |
if (videoFile.size === 0) { | |
showError('Please select a valid video file.'); | |
return; | |
} | |
// Check file size (max 100MB) | |
const maxSize = 100 * 1024 * 1024; // 100MB in bytes | |
if (videoFile.size > maxSize) { | |
showError('Video file is too large. Please select a file smaller than 100MB.'); | |
return; | |
} | |
// Check file type | |
const allowedTypes = ['video/mp4', 'video/quicktime', 'video/x-msvideo']; | |
if (!allowedTypes.includes(videoFile.type)) { | |
showError('Please select a valid video file (MP4, MOV, or AVI).'); | |
return; | |
} | |
console.log('Form data:', { | |
candidateName, | |
roleApplied, | |
techSkills, | |
videoFileName: videoFile.name, | |
videoFileSize: videoFile.size, | |
videoFileType: videoFile.type | |
}); | |
// Validate form inputs | |
if (!candidateName || !roleApplied || !videoFile) { | |
showError('Please fill in all required fields.'); | |
return; | |
} | |
try { | |
loadingOverlay.style.display = 'block'; | |
const startTime = Date.now(); | |
// Update processing status | |
processingStatus.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<i class="fas fa-spinner fa-spin me-2"></i> | |
<div>Processing video... Please wait</div> | |
</div> | |
<div class="progress mt-2"> | |
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0%"></div> | |
</div> | |
`; | |
processingStatus.style.display = 'block'; | |
processingStatus.className = 'processing-status'; | |
// Send video to server for analysis | |
const response = await fetch('/analyze_interview', { | |
method: 'POST', | |
body: formData | |
}); | |
console.log('Server response status:', response.status); | |
if (!response.ok) { | |
const errorText = await response.text(); | |
console.error('Server error response:', errorText); | |
throw new Error(`Server responded with ${response.status}: ${errorText}`); | |
} | |
const results = await response.json(); | |
console.log('Received results:', results); | |
if (!results || typeof results !== 'object') { | |
throw new Error('Invalid response format from server'); | |
} | |
if (results.error) { | |
throw new Error(results.error); | |
} | |
// Store current results | |
currentAnalysisResults = results; | |
console.log('Results stored in currentAnalysisResults'); | |
// Hide loading overlay | |
loadingOverlay.style.display = 'none'; | |
// Make sure the results container is visible | |
assessmentResultContainer.style.display = 'block'; | |
console.log('Results container displayed'); | |
// Display results | |
await displayAnalysisResults(results); | |
console.log('Results displayed'); | |
// Update processing status | |
const endTime = Date.now(); | |
const duration = ((endTime - startTime) / 1000).toFixed(2); | |
processingStatus.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<i class="fas fa-check-circle text-success me-2"></i> | |
<div>Analysis completed in ${duration} seconds</div> | |
</div> | |
`; | |
processingStatus.style.display = 'block'; | |
processingStatus.className = 'processing-status success'; | |
// Scroll to results | |
assessmentResultContainer.scrollIntoView({ behavior: 'smooth' }); | |
console.log('Scrolled to results'); | |
} catch (error) { | |
console.error('Error in form submission:', error); | |
loadingOverlay.style.display = 'none'; | |
showError('Failed to analyze the interview: ' + error.message); | |
} | |
}); | |
// Download Excel report handler | |
downloadExcelBtn.addEventListener('click', async function() { | |
if (!currentAnalysisResults) { | |
showError('No analysis results available'); | |
return; | |
} | |
try { | |
const response = await fetch('/download_assessment', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(currentAnalysisResults) | |
}); | |
if (!response.ok) { | |
throw new Error('Failed to generate Excel report'); | |
} | |
// Create a blob from the response | |
const blob = await response.blob(); | |
// Create a download link | |
const url = window.URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.style.display = 'none'; | |
a.href = url; | |
a.download = `${currentAnalysisResults.candidate_name.replace(/\s+/g, '_')}_interview_assessment.xlsx`; | |
// Append to the document and trigger click | |
document.body.appendChild(a); | |
a.click(); | |
// Clean up | |
window.URL.revokeObjectURL(url); | |
document.body.removeChild(a); | |
} catch (error) { | |
console.error('Error downloading Excel:', error); | |
showError('Failed to download Excel report: ' + error.message); | |
} | |
}); | |
// Print assessment handler | |
printAssessmentBtn.addEventListener('click', function() { | |
window.print(); | |
}); | |
// New analysis handler | |
newAnalysisBtn.addEventListener('click', function() { | |
// Hide results container | |
assessmentResultContainer.style.display = 'none'; | |
// Reset form | |
uploadForm.reset(); | |
// Scroll to top | |
window.scrollTo({ top: 0, behavior: 'smooth' }); | |
}); | |
// Function to show error message | |
function showError(message) { | |
console.error('Error:', message); | |
errorModalBody.textContent = message; | |
errorModal.show(); | |
} | |
// Function to update progress steps | |
function updateProgressStep(step) { | |
console.log('Updating to step:', step); | |
const steps = [step1, step2, step3, step4]; | |
const startTime = new Date(); | |
// Reset all steps | |
steps.forEach((el, index) => { | |
el.classList.remove('active', 'completed'); | |
// Add timing element if not exists | |
if (!el.querySelector('.step-time')) { | |
const timeSpan = document.createElement('span'); | |
timeSpan.className = 'step-time'; | |
el.appendChild(timeSpan); | |
} | |
// Add status indicator if not exists | |
if (!el.querySelector('.step-status')) { | |
const statusSpan = document.createElement('span'); | |
statusSpan.className = 'step-status'; | |
el.appendChild(statusSpan); | |
} | |
}); | |
// Update steps based on progress | |
steps.forEach((el, index) => { | |
const stepNum = index + 1; | |
const statusSpan = el.querySelector('.step-status'); | |
const timeSpan = el.querySelector('.step-time'); | |
if (stepNum < step) { | |
el.classList.add('completed'); | |
statusSpan.innerHTML = '<i class="fas fa-check text-success"></i>'; | |
timeSpan.textContent = '✓'; | |
} else if (stepNum === step) { | |
el.classList.add('active'); | |
statusSpan.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; | |
timeSpan.textContent = 'Processing...'; | |
} else { | |
statusSpan.innerHTML = ''; | |
timeSpan.textContent = 'Waiting...'; | |
} | |
}); | |
} | |
// Function to display analysis results | |
async function displayAnalysisResults(results) { | |
console.log('Starting to display results'); | |
try { | |
if (!results.candidate_assessment) { | |
throw new Error('Missing candidate assessment data'); | |
} | |
// Update candidate info | |
const candidateInfo = document.getElementById('candidateInfo'); | |
if (!candidateInfo) { | |
throw new Error('Cannot find candidateInfo element'); | |
} | |
candidateInfo.innerHTML = ` | |
<h3>${results.candidate_name || 'Candidate'}</h3> | |
<p class="text-muted">${results.role_applied || 'Role not specified'}</p> | |
<p>Interview Date: ${results.interview_date || new Date().toLocaleDateString()}</p> | |
`; | |
console.log('Updated candidate info'); | |
// Update scores and assessments | |
await updateAssessmentSection('technical', results.candidate_assessment.technical_knowledge); | |
await updateAssessmentSection('problemSolving', results.candidate_assessment.problem_solving); | |
await updateAssessmentSection('communication', results.candidate_assessment.communication); | |
console.log('Updated assessment sections'); | |
// Update emotion analysis | |
await updateEmotionAnalysis(results.emotion_analysis); | |
console.log('Updated emotion analysis'); | |
// Update recommendation | |
await updateRecommendation(results); | |
console.log('Updated recommendation'); | |
// Update question analysis | |
await updateQuestionAnalysis(results.question_analysis); | |
console.log('Updated question analysis'); | |
// Update transcript | |
const transcriptText = document.getElementById('transcriptText'); | |
if (transcriptText) { | |
transcriptText.textContent = results.transcription || 'No transcript available'; | |
console.log('Updated transcript'); | |
} | |
} catch (error) { | |
console.error('Error in displayAnalysisResults:', error); | |
showError('Error displaying results: ' + error.message); | |
} | |
} | |
// Helper function to update assessment sections | |
async function updateAssessmentSection(type, data) { | |
if (!data) { | |
console.warn(`No data provided for ${type} assessment`); | |
return; | |
} | |
try { | |
// Update score | |
const scoreElement = document.querySelector(`#${type}Score .score-number`); | |
const progressBar = document.querySelector(`#${type}Score .progress-bar`); | |
if (scoreElement && progressBar) { | |
scoreElement.textContent = data.score || 0; | |
const width = ((data.score || 0) * 10) + '%'; | |
progressBar.style.width = width; | |
progressBar.setAttribute('aria-valuenow', data.score || 0); | |
} | |
// Update strengths | |
const strengthsList = document.getElementById(`${type}Strengths`); | |
if (strengthsList) { | |
strengthsList.innerHTML = ''; | |
(data.strengths || []).forEach(strength => { | |
const li = document.createElement('li'); | |
li.className = 'list-group-item'; | |
li.innerHTML = `<i class="fas fa-check-circle text-success me-2"></i>${strength}`; | |
strengthsList.appendChild(li); | |
}); | |
} | |
// Update improvements | |
const improvementsList = document.getElementById(`${type}Improvements`); | |
if (improvementsList) { | |
improvementsList.innerHTML = ''; | |
(data.areas_for_improvement || []).forEach(improvement => { | |
const li = document.createElement('li'); | |
li.className = 'list-group-item'; | |
li.innerHTML = `<i class="fas fa-arrow-circle-up text-primary me-2"></i>${improvement}`; | |
improvementsList.appendChild(li); | |
}); | |
} | |
} catch (error) { | |
console.error(`Error updating ${type} assessment:`, error); | |
} | |
} | |
// Helper function to update emotion analysis | |
async function updateEmotionAnalysis(emotionData) { | |
if (!emotionData) { | |
console.warn('No emotion data provided'); | |
return; | |
} | |
try { | |
const emotionAnalysis = document.getElementById('emotionAnalysis'); | |
if (!emotionAnalysis) return; | |
emotionAnalysis.innerHTML = ''; | |
if (emotionData.emotion_percentages) { | |
const emotions = emotionData.emotion_percentages; | |
const sortedEmotions = Object.entries(emotions) | |
.sort((a, b) => b[1] - a[1]) | |
.filter(([_, value]) => value > 0); | |
sortedEmotions.forEach(([emotion, percentage]) => { | |
const emotionBar = document.createElement('div'); | |
emotionBar.className = `emotion-bar emotion-${emotion.toLowerCase()}`; | |
emotionBar.style.width = `${percentage}%`; | |
emotionBar.style.minWidth = '150px'; | |
const emotionLabel = document.createElement('span'); | |
emotionLabel.className = 'emotion-label'; | |
emotionLabel.textContent = emotion.charAt(0).toUpperCase() + emotion.slice(1); | |
const emotionPercentage = document.createElement('span'); | |
emotionPercentage.className = 'emotion-percentage'; | |
emotionPercentage.textContent = `${percentage.toFixed(1)}%`; | |
emotionBar.appendChild(emotionLabel); | |
emotionBar.appendChild(emotionPercentage); | |
emotionAnalysis.appendChild(emotionBar); | |
}); | |
} | |
// Update metrics | |
document.getElementById('totalFaces').textContent = emotionData.total_faces || 0; | |
document.getElementById('framesProcessed').textContent = emotionData.frames_processed || 0; | |
document.getElementById('framesWithFaces').textContent = emotionData.frames_with_faces || 0; | |
} catch (error) { | |
console.error('Error updating emotion analysis:', error); | |
} | |
} | |
// Helper function to update recommendation | |
async function updateRecommendation(results) { | |
try { | |
const recommendationBadge = document.querySelector('#overallRecommendation .recommendation-badge'); | |
if (!recommendationBadge) return; | |
const recommendation = results.overall_recommendation || 'Consider'; | |
recommendationBadge.textContent = recommendation; | |
recommendationBadge.className = 'recommendation-badge'; | |
const recommendationLower = recommendation.toLowerCase(); | |
if (recommendationLower.includes('hire')) { | |
recommendationBadge.classList.add('recommendation-hire'); | |
} else if (recommendationLower.includes('strong')) { | |
recommendationBadge.classList.add('recommendation-strong'); | |
} else if (recommendationLower.includes('consider')) { | |
recommendationBadge.classList.add('recommendation-consider'); | |
} else { | |
recommendationBadge.classList.add('recommendation-reject'); | |
} | |
const feedbackElement = document.getElementById('overallFeedback'); | |
if (feedbackElement) { | |
feedbackElement.innerHTML = results.overall_feedback || 'No feedback available'; | |
} | |
} catch (error) { | |
console.error('Error updating recommendation:', error); | |
} | |
} | |
// Helper function to update question analysis | |
async function updateQuestionAnalysis(questionData) { | |
try { | |
const questionAnalysis = document.getElementById('questionAnalysis'); | |
if (!questionAnalysis) return; | |
questionAnalysis.innerHTML = ''; | |
if (Array.isArray(questionData)) { | |
questionData.forEach(qa => { | |
const questionCard = document.createElement('div'); | |
questionCard.className = 'card question-card mb-3'; | |
questionCard.innerHTML = ` | |
<div class="card-body"> | |
<p class="question-text"><i class="fas fa-question-circle me-2"></i>${qa.question || 'Question not available'}</p> | |
<span class="answer-quality ${getQualityClass(qa.answer_quality)}">${qa.answer_quality || 'Not rated'}</span> | |
<p class="mt-2">${qa.feedback || 'No feedback available'}</p> | |
</div> | |
`; | |
questionAnalysis.appendChild(questionCard); | |
}); | |
} | |
} catch (error) { | |
console.error('Error updating question analysis:', error); | |
} | |
} | |
// Helper function to get the appropriate class for answer quality | |
function getQualityClass(quality) { | |
quality = quality.toLowerCase(); | |
if (quality.includes('excellent')) return 'bg-success text-white'; | |
if (quality.includes('good')) return 'bg-primary text-white'; | |
if (quality.includes('average') || quality.includes('satisfactory')) return 'bg-warning'; | |
return 'bg-danger text-white'; | |
} | |
// Simulated progression for the demo | |
function simulateProgressSteps() { | |
setTimeout(() => updateProgressStep(1), 1000); | |
setTimeout(() => updateProgressStep(2), 3000); | |
setTimeout(() => updateProgressStep(3), 6000); | |
setTimeout(() => updateProgressStep(4), 9000); | |
} | |
// For the demo version, show all sections | |
function setupDemoMode() { | |
// Add event listener to show a loading demo | |
document.getElementById('uploadForm').addEventListener('submit', function(e) { | |
e.preventDefault(); | |
loadingOverlay.style.display = 'block'; | |
simulateProgressSteps(); | |
// After "processing" display demo results | |
setTimeout(() => { | |
loadingOverlay.style.display = 'none'; | |
displayDemoResults(); | |
}, 12000); | |
}); | |
} | |
// If in demo mode, set up the demo interface | |
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { | |
setupDemoMode(); | |
} | |
// Add this to your existing JavaScript after DOMContentLoaded | |
const videoFile = document.getElementById('videoFile'); | |
const videoPreview = document.getElementById('videoPreview'); | |
const videoPreviewContainer = document.querySelector('.video-preview-container'); | |
const processingStatus = document.getElementById('processingStatus'); | |
// Video file preview handler | |
videoFile.addEventListener('change', function(e) { | |
const file = e.target.files[0]; | |
if (file) { | |
const videoUrl = URL.createObjectURL(file); | |
videoPreview.src = videoUrl; | |
videoPreviewContainer.style.display = 'block'; | |
// Show file details | |
processingStatus.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<i class="fas fa-check-circle text-success me-2"></i> | |
<div> | |
<strong>File selected:</strong> ${file.name}<br> | |
<small class="text-muted">Size: ${(file.size / (1024 * 1024)).toFixed(2)} MB</small> | |
</div> | |
</div> | |
`; | |
processingStatus.style.display = 'block'; | |
processingStatus.className = 'processing-status success'; | |
} | |
}); | |
}); | |
</script> | |
</body> | |
</html> |