JobHune 29112025
This commit is contained in:
parent
15994a64a1
commit
c9d9ee75e6
71
analyze.php
Normal file
71
analyze.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||
|
||||
if (isset($_FILES['resume']) && $_FILES['resume']['error'] === UPLOAD_ERR_OK) {
|
||||
$fileTmpPath = $_FILES['resume']['tmp_name'];
|
||||
|
||||
// Read the file content
|
||||
$resumeContent = file_get_contents($fileTmpPath);
|
||||
|
||||
if ($resumeContent === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'Failed to read resume file.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Define the AI prompt
|
||||
$prompt = <<<PROMPT
|
||||
Act as an expert Applicant Tracking System (ATS) resume analyzer. Analyze the following resume content and return a JSON object with your analysis.
|
||||
|
||||
The JSON object must have the following structure:
|
||||
{
|
||||
"score": <an integer score from 0 to 100 representing ATS compatibility>,
|
||||
"status": "<a short status like 'Strong Candidate', 'Needs Improvement', or 'Poor Fit'>",
|
||||
"strengths": [
|
||||
"<a brief description of a positive aspect of the resume>",
|
||||
"<another positive aspect>"
|
||||
],
|
||||
"weaknesses": [
|
||||
"<a brief description of a negative aspect or area for improvement>",
|
||||
"<another area for improvement>"
|
||||
]
|
||||
}
|
||||
|
||||
Here is the resume content:
|
||||
---
|
||||
{$resumeContent}
|
||||
---
|
||||
PROMPT;
|
||||
|
||||
// Call the AI
|
||||
$response = LocalAIApi::createResponse([
|
||||
'input' => [
|
||||
['role' => 'system', 'content' => 'You are an expert ATS resume analyzer that only responds with JSON.'],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
],
|
||||
]);
|
||||
|
||||
if (empty($response['success'])) {
|
||||
error_log('AI API Error: ' . ($response['error'] ?? 'Unknown error'));
|
||||
echo json_encode(['success' => false, 'message' => 'AI analysis failed. Please try again later.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$analysisJson = LocalAIApi::decodeJsonFromResponse($response);
|
||||
|
||||
if ($analysisJson === null) {
|
||||
error_log('AI API JSON Decode Error: ' . LocalAIApi::extractText($response));
|
||||
echo json_encode(['success' => false, 'message' => 'Failed to parse AI response. The response may not be valid JSON.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $analysisJson]);
|
||||
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'File upload failed or no file was uploaded.'
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@ -1,15 +1,101 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const uploadForm = document.getElementById('upload-form');
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
const uploadSection = document.getElementById('upload-section');
|
||||
const analysisSection = document.getElementById('analysis-section');
|
||||
const resumeFileInput = document.getElementById('resume-file');
|
||||
|
||||
if (analyzeBtn) {
|
||||
analyzeBtn.addEventListener('click', function(event) {
|
||||
if (uploadForm) {
|
||||
uploadForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Hide upload section and show analysis section
|
||||
uploadSection.style.display = 'none';
|
||||
analysisSection.style.display = 'block';
|
||||
|
||||
if (!resumeFileInput.files || resumeFileInput.files.length === 0) {
|
||||
alert('Please select a resume file to analyze.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('resume', resumeFileInput.files[0]);
|
||||
|
||||
// Show loading state
|
||||
analyzeBtn.disabled = true;
|
||||
analyzeBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Analyzing...';
|
||||
|
||||
fetch('analyze.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Hide loading state
|
||||
analyzeBtn.disabled = false;
|
||||
analyzeBtn.innerHTML = '<i class="bi bi-magic me-2"></i>Analyze My Resume';
|
||||
|
||||
if (data.success && data.data) {
|
||||
const analysis = data.data;
|
||||
|
||||
// Update Score and Status
|
||||
document.getElementById('ats-score').textContent = analysis.score;
|
||||
const scoreCircle = document.querySelector('.score-circle');
|
||||
const statusElement = scoreCircle.parentElement.parentElement.querySelector('.fw-semibold');
|
||||
statusElement.textContent = analysis.status;
|
||||
|
||||
if (analysis.score >= 80) {
|
||||
statusElement.className = 'fw-semibold text-success';
|
||||
} else if (analysis.score >= 60) {
|
||||
statusElement.className = 'fw-semibold text-warning';
|
||||
} else {
|
||||
statusElement.className = 'fw-semibold text-danger';
|
||||
}
|
||||
|
||||
// Update Strengths
|
||||
const strengthsList = document.querySelector('#analysis-section .bg-success').parentElement.querySelector('.list-group');
|
||||
strengthsList.innerHTML = ''; // Clear existing items
|
||||
if (analysis.strengths && analysis.strengths.length > 0) {
|
||||
analysis.strengths.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
li.textContent = item;
|
||||
strengthsList.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
li.textContent = 'No specific strengths identified.';
|
||||
strengthsList.appendChild(li);
|
||||
}
|
||||
|
||||
// Update Weaknesses
|
||||
const weaknessesList = document.querySelector('#analysis-section .bg-warning').parentElement.querySelector('.list-group');
|
||||
weaknessesList.innerHTML = ''; // Clear existing items
|
||||
if (analysis.weaknesses && analysis.weaknesses.length > 0) {
|
||||
analysis.weaknesses.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
li.textContent = item;
|
||||
weaknessesList.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
li.textContent = 'No specific areas for improvement identified.';
|
||||
weaknessesList.appendChild(li);
|
||||
}
|
||||
|
||||
// Show the analysis
|
||||
uploadSection.style.display = 'none';
|
||||
analysisSection.style.display = 'block';
|
||||
} else {
|
||||
alert('Error: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Hide loading state
|
||||
analyzeBtn.disabled = false;
|
||||
analyzeBtn.innerHTML = '<i class="bi bi-magic me-2"></i>Analyze My Resume';
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -29,10 +29,10 @@
|
||||
<h1 class="display-5 fw-bold">Get Your Resume ATS-Ready</h1>
|
||||
<p class="lead mb-4">Upload your resume to see your ATS compatibility score and get actionable insights in seconds.</p>
|
||||
<div class="col-lg-6 mx-auto">
|
||||
<form>
|
||||
<form id="upload-form">
|
||||
<div class="mb-3">
|
||||
<label for="resume-file" class="visually-hidden">Upload Resume</label>
|
||||
<input class="form-control form-control-lg" type="file" id="resume-file" disabled>
|
||||
<input class="form-control form-control-lg" type="file" id="resume-file" name="resume" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button id="analyze-btn" type="submit" class="btn btn-primary-accent btn-lg">
|
||||
@ -40,7 +40,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-white-50 mt-3 small">For demo purposes, just click "Analyze" to see a sample report.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user