v2: AI recruiter interface

This commit is contained in:
Dauren777 2026-06-19 06:05:41 +00:00
parent 2e85ce1d59
commit f2151ec2b6

View File

@ -3,146 +3,697 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>ИИ-Анализатор Резюме</title> <title>ИИ-Рекрутер — Анализ вакансий и резюме</title>
<style> <style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7} :root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--success:#10B981;--warning:#F59E0B;--danger:#EF4444}
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)} body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100)}
.container{max-width:1140px;margin:0 auto;padding:80px 24px} .container{max-width:1200px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white)} .hero{background:var(--ink);color:var(--white);padding:60px 0}
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px} .hero h1{font-size:48px;font-weight:800;line-height:1.1;margin-bottom:16px}
.hero p{font-size:20px;color:#9aa3b2;max-width:600px;margin-bottom:32px} .hero p{font-size:20px;color:#9aa3b2;max-width:700px;margin-bottom:32px}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none} .btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer}
.btn:hover{background:#1be5ff} .btn:hover{background:#1be5ff}
.section h2{font-size:36px;font-weight:700;margin-bottom:24px} .btn-secondary{background:transparent;color:var(--ink);border:2px solid var(--ink);padding:12px 26px;border-radius:8px}
.card{background:var(--gray-100);border-radius:16px;padding:32px;margin-bottom:16px} .tabs{display:flex;gap:8px;margin-bottom:32px;flex-wrap:wrap}
.upload-form{max-width:600px;margin:0 auto} .tab{padding:12px 24px;background:var(--white);border-radius:8px;cursor:pointer;font-weight:600;border:2px solid transparent}
textarea{width:100%;height:200px;padding:16px;border:2px solid var(--gray-100);border-radius:8px;font-family:inherit;font-size:16px;resize:vertical} .tab.active{border-color:var(--cyan);background:var(--cyan-50)}
textarea:focus{outline:none;border-color:var(--cyan)} .tab-content{display:none}
.file-input{margin:16px 0} .tab-content.active{display:block}
.results{display:none;margin-top:40px} .card{background:var(--white);border-radius:16px;padding:32px;margin-bottom:24px;box-shadow:0 4px 12px rgba(0,0,0,0.05)}
.results h3{font-size:22px;font-weight:700;margin-bottom:16px} .form-group{margin-bottom:20px}
.results .score{font-size:24px;font-weight:800;color:var(--cyan);margin-bottom:8px} .form-group label{display:block;font-weight:600;margin-bottom:8px}
.results .section{margin-bottom:24px} .form-group input,.form-group textarea,.form-group select{width:100%;padding:12px;border:2px solid var(--gray-100);border-radius:8px;font-family:inherit;font-size:16px}
.results .section h4{font-size:18px;font-weight:700;margin-bottom:8px} .form-group textarea{height:150px;resize:vertical}
.results .section p{margin-bottom:8px} .form-group input:focus,.form-group textarea:focus,.form-group select:focus{outline:none;border-color:var(--cyan)}
@media (max-width:640px){.hero h1{font-size:36px}.section h2{font-size:28px}.container{padding:48px 20px}} .radio-group{display:flex;gap:16px;flex-wrap:wrap}
.radio-group label{display:flex;align-items:center;gap:8px;font-weight:normal;cursor:pointer}
.upload-area{border:3px dashed var(--gray-100);border-radius:16px;padding:48px;text-align:center;cursor:pointer;transition:all 0.3s}
.upload-area:hover{border-color:var(--cyan);background:var(--cyan-50)}
.upload-area.dragover{border-color:var(--cyan);background:var(--cyan-50)}
.file-list{margin-top:24px}
.file-item{display:flex;justify-content:space-between;align-items:center;padding:12px;background:var(--gray-100);border-radius:8px;margin-bottom:8px}
.file-item .name{font-weight:600}
.file-item .size{color:var(--gray-500);font-size:14px}
.file-item .remove{color:var(--danger);cursor:pointer;font-weight:600}
.results-table{width:100%;border-collapse:collapse;margin-top:24px}
.results-table th,.results-table td{padding:16px;text-align:left;border-bottom:1px solid var(--gray-100)}
.results-table th{background:var(--gray-100);font-weight:600}
.results-table tr:hover{background:var(--cyan-50)}
.score-badge{display:inline-block;padding:4px 12px;border-radius:20px;font-weight:700;font-size:14px}
.score-badge.high{background:#D1FAE5;color:#065F46}
.score-badge.medium{background:#FEF3C7;color:#92400E}
.score-badge.low{background:#FEE2E2;color:#991B1B}
.candidate-modal{position:fixed;inset:0;background:rgba(0,0,0,0.5);display:none;align-items:center;justify-content:center;z-index:1000;padding:20px}
.candidate-modal.active{display:flex}
.modal-content{background:var(--white);border-radius:16px;padding:32px;max-width:900px;width:100%;max-height:90vh;overflow-y:auto}
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}
.modal-close{background:none;border:none;font-size:24px;cursor:pointer}
.section-title{font-size:22px;font-weight:700;margin:24px 0 16px;border-bottom:2px solid var(--gray-100);padding-bottom:8px}
.progress-bar{height:8px;background:var(--gray-100);border-radius:4px;overflow:hidden;margin-top:8px}
.progress-fill{height:100%;background:var(--cyan);border-radius:4px}
@media (max-width:768px){.hero h1{font-size:36px}.container{padding:48px 20px}.tabs{gap:4px}.tab{padding:10px 16px;font-size:14px}}
</style> </style>
</head> </head>
<body> <body>
<section class="hero"> <section class="hero">
<div class="container"> <div class="container">
<h1>ИИ-Анализатор Резюме</h1> <h1>ИИ-Рекрутер</h1>
<p>Загрузите ваше резюме, и наш ИИ-агент проанализирует его, выделит ключевые компетенции и предложит подходящие должности.</p> <p>Автоматический анализ вакансий и резюме. Загрузите описание вакансии и резюме кандидатов — ИИ-агент найдет лучших.</p>
<a class="btn" href="#upload">Загрузить резюме</a> <button class="btn" onclick="showTab('job')">Начать анализ</button>
</div> </div>
</section> </section>
<section id="upload" class="section">
<div class="container"> <div class="container">
<h2>Загрузите ваше резюме</h2> <div class="tabs">
<div class="card upload-form"> <div class="tab active" onclick="showTab('job')">1. Вакансия</div>
<form id="resumeForm"> <div class="tab" onclick="showTab('requirements')">2. Требования</div>
<textarea id="resumeText" placeholder="Вставьте текст вашего резюме сюда..."></textarea> <div class="tab" onclick="showTab('upload')">3. Загрузка резюме</div>
<div class="file-input"> <div class="tab" onclick="showTab('results')">4. Результаты</div>
<label for="resumeFile">Или загрузите файл (TXT, PDF, DOC):</label><br> </div>
<input type="file" id="resumeFile" accept=".txt,.pdf,.doc,.docx">
<!-- Блок 1: Информация о вакансии -->
<div id="job" class="tab-content active">
<div class="card">
<h2 style="margin-bottom:24px">Информация о вакансии</h2>
<div class="form-group">
<label>Название вакансии</label>
<input type="text" id="jobTitle" placeholder="например, Senior Python Developer">
</div>
<div class="form-group">
<label>Компания</label>
<input type="text" id="company" placeholder="название компании">
</div>
<div class="form-group">
<label>Отдел</label>
<input type="text" id="department" placeholder="например, IT-отдел">
</div>
<div class="form-group">
<label>Город</label>
<input type="text" id="city" placeholder="например, Алматы">
</div>
<div class="form-group">
<label>Формат работы</label>
<div class="radio-group">
<label><input type="radio" name="format" value="office"> Офис</label>
<label><input type="radio" name="format" value="hybrid" checked> Гибрид</label>
<label><input type="radio" name="format" value="remote"> Удаленно</label>
</div> </div>
<button type="submit" class="btn">Анализировать</button> </div>
</form> <div class="form-group">
<label>Уровень позиции</label>
<select id="level">
<option value="Junior">Junior</option>
<option value="Middle" selected>Middle</option>
<option value="Senior">Senior</option>
<option value="Lead">Lead</option>
<option value="Head">Head</option>
</select>
</div>
<div class="form-group">
<label>Описание вакансии (Job Description)</label>
<textarea id="jobDescription" placeholder="Вставьте полное описание вакансии..."></textarea>
</div>
<button class="btn" onclick="showTab('requirements')">Далее: Требования</button>
</div> </div>
</div> </div>
</section>
<section id="results" class="section results"> <!-- Блок 2: Требования -->
<div class="container"> <div id="requirements" class="tab-content">
<h2>Результаты анализа</h2> <div class="card">
<div class="card" id="resultsContent"> <h2 style="margin-bottom:24px">Требования к кандидату</h2>
<!-- Результаты будут вставлены здесь --> <div class="form-group">
<label>Обязательные требования (каждое с новой строки)</label>
<textarea id="requiredSkills" placeholder="Python&#10;SQL&#10;Опыт от 3 лет&#10;Знание английского языка"></textarea>
</div>
<div class="form-group">
<label>Желательные требования (каждое с новой строки)</label>
<textarea id="preferredSkills" placeholder="Power BI&#10;Machine Learning&#10;Опыт в FinTech"></textarea>
</div>
<div class="form-group">
<label>Стоп-факторы (каждое с новой строки)</label>
<textarea id="stopFactors" placeholder="Нет опыта в нужной области&#10;Нет знания английского языка&#10;Отсутствует высшее образование"></textarea>
</div>
<button class="btn" onclick="showTab('upload')">Далее: Загрузка резюме</button>
</div> </div>
</div> </div>
</section>
<!-- Блок 3: Загрузка резюме -->
<div id="upload" class="tab-content">
<div class="card">
<h2 style="margin-bottom:24px">Загрузка резюме</h2>
<p style="margin-bottom:16px;color:var(--gray-500)">Загрузите до 100 файлов (PDF, DOC, DOCX, TXT). Извлечение текста и анализ выполняются в браузере.</p>
<div class="upload-area" id="uploadArea">
<div style="font-size:48px;margin-bottom:16px">📄</div>
<div style="font-size:20px;font-weight:600;margin-bottom:8px">Перетащите файлы сюда</div>
<div style="color:var(--gray-500)">или нажмите для выбора</div>
<input type="file" id="fileInput" multiple accept=".pdf,.doc,.docx,.txt" style="display:none">
</div>
<div class="file-list" id="fileList"></div>
<div style="margin-top:24px;display:flex;gap:16px">
<button class="btn" onclick="analyzeAllResumes()" id="analyzeBtn" disabled>Анализировать все резюме</button>
<button class="btn-secondary" onclick="clearFiles()">Очистить список</button>
</div>
</div>
</div>
<!-- Блок 4: Результаты -->
<div id="results" class="tab-content">
<div class="card">
<h2 style="margin-bottom:24px">Результаты анализа</h2>
<div id="resultsSummary" style="display:none">
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:16px;margin-bottom:32px">
<div class="card" style="text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--cyan)" id="totalCandidates">0</div>
<div style="color:var(--gray-500)">Всего кандидатов</div>
</div>
<div class="card" style="text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--success)" id="topCandidates">0</div>
<div style="color:var(--gray-500)">Top Candidate</div>
</div>
<div class="card" style="text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--warning)" id="strongMatches">0</div>
<div style="color:var(--gray-500)">Strong Match</div>
</div>
<div class="card" style="text-align:center">
<div style="font-size:32px;font-weight:800;color:var(--danger)" id="weakMatches">0</div>
<div style="color:var(--gray-500)">Weak Match</div>
</div>
</div>
<h3>Таблица кандидатов</h3>
<div style="overflow-x:auto">
<table class="results-table">
<thead>
<tr>
<th>Место</th>
<th>Кандидат</th>
<th>Score</th>
<th>Уровень</th>
<th>Опыт</th>
<th>Статус</th>
<th>Действие</th>
</tr>
</thead>
<tbody id="candidatesTableBody">
</tbody>
</table>
</div>
<div style="margin-top:32px">
<button class="btn" onclick="generateHRReport()">Сформировать отчет для HR</button>
</div>
</div>
<div id="noResults" style="text-align:center;padding:48px;color:var(--gray-500)">
<div style="font-size:48px;margin-bottom:16px">📊</div>
<div style="font-size:20px;font-weight:600">Загрузите резюме и запустите анализ</div>
</div>
</div>
</div>
</div>
<!-- Модальное окно кандидата -->
<div class="candidate-modal" id="candidateModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalCandidateName">Имя кандидата</h2>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div id="modalContent">
<!-- Содержимое карточки кандидата -->
</div>
</div>
</div>
<!-- Скрипты для анализа PDF и DOCX -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<script> <script>
document.getElementById('resumeForm').addEventListener('submit', function(e) { // Глобальные переменные
let uploadedFiles = [];
let analyzedCandidates = [];
// Переключение вкладок
function showTab(tabId) {
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.querySelector(`[onclick="showTab('${tabId}')"]`).classList.add('active');
document.getElementById(tabId).classList.add('active');
}
// Загрузка файлов
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover'));
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
const resumeText = document.getElementById('resumeText').value; fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
const fileInput = document.getElementById('resumeFile');
if (!resumeText && !fileInput.files.length) { function handleFiles(files) {
alert('Пожалуйста, вставьте текст резюме или загрузите файл'); for (let file of files) {
if (uploadedFiles.length >= 100) {
alert('Максимум 100 файлов');
break;
}
if (!uploadedFiles.some(f => f.name === file.name)) {
uploadedFiles.push(file);
}
}
updateFileList();
}
function updateFileList() {
const fileList = document.getElementById('fileList');
fileList.innerHTML = uploadedFiles.map((file, index) => `
<div class="file-item">
<div>
<div class="name">${file.name}</div>
<div class="size">${(file.size / 1024).toFixed(1)} КБ</div>
</div>
<div class="remove" onclick="removeFile(${index})">Удалить</div>
</div>
`).join('');
document.getElementById('analyzeBtn').disabled = uploadedFiles.length === 0;
}
function removeFile(index) {
uploadedFiles.splice(index, 1);
updateFileList();
}
function clearFiles() {
uploadedFiles = [];
updateFileList();
}
// Извлечение текста из файлов
async function extractText(file) {
const extension = file.name.split('.').pop().toLowerCase();
if (extension === 'txt') {
return await file.text();
} else if (extension === 'pdf') {
return await extractTextFromPDF(file);
} else if (extension === 'doc' || extension === 'docx') {
return await extractTextFromDOCX(file);
}
return '';
}
async function extractTextFromPDF(file) {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({data: arrayBuffer}).promise;
let text = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
text += content.items.map(item => item.str).join(' ') + '\n';
}
return text;
}
async function extractTextFromDOCX(file) {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({arrayBuffer});
return result.value;
}
// Анализ резюме
function analyzeResume(text, fileName) {
const textLower = text.toLowerCase();
// Извлечение данных
const name = extractName(text, fileName);
const city = extractCity(text);
const lastPosition = extractLastPosition(text);
const experience = extractExperience(text);
const skills = extractSkills(text);
const education = extractEducation(text);
// Определение уровня
let level = 'Middle';
if (textLower.includes('senior') || textLower.includes('старший') || textLower.includes('lead') || textLower.includes('руководитель')) {
level = 'Senior';
} else if (textLower.includes('junior') || textLower.includes('младший') || textLower.includes('стажер')) {
level = 'Junior';
} else if (textLower.includes('head') || textLower.includes('директор') || textLower.includes('начальник')) {
level = 'Head';
}
// Анализ навыков
const hardSkills = extractHardSkills(text);
const softSkills = extractSoftSkills(text);
// Расчет Score
const score = calculateMatchScore(hardSkills, experience, level);
// Определение статуса
let status = 'Weak Match';
if (score >= 90) status = 'Top Candidate';
else if (score >= 80) status = 'Strong Match';
else if (score >= 70) status = 'Good Match';
else if (score >= 60) status = 'Potential Match';
return {
name,
city,
lastPosition,
experience,
level,
hardSkills,
softSkills,
education,
score,
status,
text,
fileName
};
}
// Вспомогательные функции извлечения
function extractName(text, fileName) {
// Простое извлечение имени из первых строк
const lines = text.split('\n').filter(line => line.trim());
if (lines.length > 0) {
const firstLine = lines[0].trim();
if (firstLine.length < 50 && !firstLine.includes('@')) {
return firstLine;
}
}
return fileName.replace(/\.[^/.]+$/, '');
}
function extractCity(text) {
const cities = ['алматы', 'астана', 'нур-султан', 'караганда', 'шимкент', 'актау', 'атырау', 'павлодар', 'семей', 'уральск'];
const textLower = text.toLowerCase();
for (const city of cities) {
if (textLower.includes(city)) {
return city.charAt(0).toUpperCase() + city.slice(1);
}
}
return 'Не указан';
}
function extractLastPosition(text) {
const lines = text.split('\n');
for (const line of lines) {
const lineLower = line.toLowerCase();
if (lineLower.includes('должность') || lineLower.includes('позиция') || lineLower.includes('менеджер') || lineLower.includes('разработчик') || lineLower.includes('аналитик')) {
return line.trim();
}
}
return 'Не указана';
}
function extractExperience(text) {
const textLower = text.toLowerCase();
if (textLower.includes('стаж') || textLower.includes('опыт')) {
const match = text.match(/(\d+)\s*(год|лет|мес)/i);
if (match) {
return `${match[1]} ${match[2]}`;
}
}
return 'Не указан';
}
function extractSkills(text) {
const skills = [];
const skillKeywords = ['python', 'java', 'javascript', 'sql', 'html', 'css', 'react', 'angular', 'vue', 'node.js', 'django', 'flask', 'spring', 'docker', 'kubernetes', 'aws', 'azure', 'gcp', 'git', 'linux', 'windows', 'sql', 'nosql', 'mongodb', 'postgresql', 'mysql', 'redis', 'kafka', 'rabbitmq', 'jenkins', 'ci/cd', 'agile', 'scrum'];
const textLower = text.toLowerCase();
for (const skill of skillKeywords) {
if (textLower.includes(skill)) {
skills.push(skill);
}
}
return skills;
}
function extractHardSkills(text) {
const hardSkills = [];
const keywords = ['python', 'java', 'javascript', 'sql', 'html', 'css', 'react', 'angular', 'vue', 'node.js', 'django', 'flask', 'spring', 'docker', 'kubernetes', 'aws', 'azure', 'gcp', 'git', 'linux', 'windows', 'mongodb', 'postgresql', 'mysql', 'redis', 'kafka', 'rabbitmq', 'jenkins', 'ci/cd', 'agile', 'scrum', 'machine learning', 'data analysis', 'tableau', 'power bi', 'excel', 'word', 'powerpoint'];
const textLower = text.toLowerCase();
for (const skill of keywords) {
if (textLower.includes(skill)) {
hardSkills.push(skill);
}
}
return hardSkills;
}
function extractSoftSkills(text) {
const softSkills = [];
const keywords = ['коммуникация', 'лидерство', 'командная работа', 'аналитическое мышление', 'креативность', 'стрессоустойчивость', 'организаторские', 'ответственность', 'инициативность', 'обучаемость', 'communication', 'leadership', 'teamwork', 'problem solving', 'creativity', 'stress resistance', 'organizational', 'responsibility', 'initiative', 'learning ability'];
const textLower = text.toLowerCase();
for (const skill of keywords) {
if (textLower.includes(skill)) {
softSkills.push(skill);
}
}
return softSkills;
}
function extractEducation(text) {
const textLower = text.toLowerCase();
if (textLower.includes('университет') || textLower.includes('институт') || textLower.includes('высшее образование')) {
return 'Высшее образование';
} else if (textLower.includes('колледж') || textLower.includes('техникум')) {
return 'Среднее профессиональное';
}
return 'Не указано';
}
function calculateMatchScore(hardSkills, experience, level) {
// Базовый балл
let score = 50;
// Балл за навыки (35%)
const skillScore = Math.min(35, hardSkills.length * 5);
score += skillScore - 25; // Корректировка
// Балл за уровень (25%)
const levelScores = { 'Junior': 10, 'Middle': 20, 'Senior': 25, 'Lead': 25, 'Head': 25 };
score += (levelScores[level] || 15) - 15;
// Балл за опыт (25%)
if (experience.includes('5') || experience.includes('6') || experience.includes('7') || experience.includes('8') || experience.includes('9') || experience.includes('10')) {
score += 10;
} else if (experience.includes('3') || experience.includes('4')) {
score += 5;
}
// Случайная вариация для демо
score += Math.floor(Math.random() * 10) - 5;
// Ограничение 0-100
return Math.max(0, Math.min(100, score));
}
// Анализ всех резюме
async function analyzeAllResumes() {
if (uploadedFiles.length === 0) return;
const analyzeBtn = document.getElementById('analyzeBtn');
analyzeBtn.textContent = 'Анализируем...';
analyzeBtn.disabled = true;
analyzedCandidates = [];
for (const file of uploadedFiles) {
try {
const text = await extractText(file);
if (text) {
const candidate = analyzeResume(text, file.name);
analyzedCandidates.push(candidate);
}
} catch (error) {
console.error(`Ошибка анализа ${file.name}:`, error);
}
}
// Сортировка по Score
analyzedCandidates.sort((a, b) => b.score - a.score);
// Обновление UI
updateResultsUI();
showTab('results');
analyzeBtn.textContent = 'Анализировать все резюме';
analyzeBtn.disabled = false;
}
function updateResultsUI() {
const tbody = document.getElementById('candidatesTableBody');
const resultsSummary = document.getElementById('resultsSummary');
const noResults = document.getElementById('noResults');
if (analyzedCandidates.length === 0) {
resultsSummary.style.display = 'none';
noResults.style.display = 'block';
return; return;
} }
// Показываем индикатор загрузки resultsSummary.style.display = 'block';
const resultsSection = document.getElementById('results'); noResults.style.display = 'none';
const resultsContent = document.getElementById('resultsContent');
resultsSection.style.display = 'block';
resultsContent.innerHTML = '<div class="score">Анализируем ваше резюме...</div>';
// Имитация задержки анализа // Статистика
setTimeout(function() { document.getElementById('totalCandidates').textContent = analyzedCandidates.length;
analyzeResume(resumeText || 'Текст из файла'); document.getElementById('topCandidates').textContent = analyzedCandidates.filter(c => c.status === 'Top Candidate').length;
}, 2000); document.getElementById('strongMatches').textContent = analyzedCandidates.filter(c => c.status === 'Strong Match').length;
}); document.getElementById('weakMatches').textContent = analyzedCandidates.filter(c => c.status === 'Weak Match').length;
function analyzeResume(text) { // Таблица
// Демо-анализ: определяем уровень на основе ключевых слов tbody.innerHTML = analyzedCandidates.map((candidate, index) => `
let level = 'Middle'; <tr>
let specialization = 'Специалист'; <td>${index + 1}</td>
let score = 75; <td><strong>${candidate.name}</strong><br><small>${candidate.city} • ${candidate.lastPosition}</small></td>
<td><span class="score-badge ${candidate.score >= 80 ? 'high' : candidate.score >= 60 ? 'medium' : 'low'}">${candidate.score}%</span></td>
<td>${candidate.level}</td>
<td>${candidate.experience}</td>
<td>${candidate.status}</td>
<td><button class="btn-secondary" onclick="showCandidateModal(${index})">Подробнее</button></td>
</tr>
`).join('');
}
const textLower = text.toLowerCase(); // Модальное окно кандидата
function showCandidateModal(index) {
const candidate = analyzedCandidates[index];
document.getElementById('modalCandidateName').textContent = candidate.name;
if (textLower.includes('senior') || textLower.includes('старший') || textLower.includes('lead') || textLower.includes('руководитель')) { const modalContent = document.getElementById('modalContent');
level = 'Senior'; modalContent.innerHTML = `
score = 85; <div style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px">
} else if (textLower.includes('junior') || textLower.includes('младший') || textLower.includes('стажер')) { <div>
level = 'Junior'; <div class="section-title">Общая информация</div>
score = 60; <p><strong>Город:</strong> ${candidate.city}</p>
} <p><strong>Последняя должность:</strong> ${candidate.lastPosition}</p>
<p><strong>Общий стаж:</strong> ${candidate.experience}</p>
if (textLower.includes('разработчик') || textLower.includes('developer') || textLower.includes('программист')) { <p><strong>Уровень:</strong> ${candidate.level}</p>
specialization = 'Разработчик'; <p><strong>Образование:</strong> ${candidate.education}</p>
} else if (textLower.includes('менеджер') || textLower.includes('manager') || textLower.includes('руководитель')) { </div>
specialization = 'Менеджер'; <div>
} else if (textLower.includes('аналитик') || textLower.includes('analyst')) { <div class="section-title">Match Score</div>
specialization = 'Аналитик'; <div style="text-align:center;padding:20px">
} else if (textLower.includes('дизайнер') || textLower.includes('designer')) { <div style="font-size:48px;font-weight:800;color:var(--cyan)">${candidate.score}%</div>
specialization = 'Дизайнер'; <div class="progress-bar" style="margin-top:16px">
} else if (textLower.includes('маркетолог') || textLower.includes('marketing')) { <div class="progress-fill" style="width:${candidate.score}%"></div>
specialization = 'Маркетолог'; </div>
} </div>
</div>
// Формируем результаты
const resultsHTML = `
<div class="score">Общий балл: ${score}/100</div>
<div class="section">
<h4>Общая оценка</h4>
<p><strong>Уровень:</strong> ${level}</p>
<p><strong>Специализация:</strong> ${specialization}</p>
<p><strong>Предполагаемый стаж:</strong> ${level === 'Senior' ? '5+ лет' : level === 'Middle' ? '3-5 лет' : '1-3 года'}</p>
</div> </div>
<div class="section">
<h4>Рекомендуемые должности</h4> <div class="section-title">Сильные стороны</div>
<p>• ${specialization} (${level})</p> <ul style="margin-bottom:24px">
<p>• ${specialization}-${level === 'Senior' ? 'лидер' : 'специалист'}</p> ${candidate.hardSkills.slice(0, 5).map(skill => `<li>${skill}</li>`).join('')}
<p>• Тимлид ${specialization.toLowerCase()}</p> ${candidate.softSkills.slice(0, 2).map(skill => `<li>${skill}</li>`).join('')}
</ul>
<div class="section-title">Слабые стороны</div>
<ul style="margin-bottom:24px">
<li>Ограниченный набор технологий</li>
<li>Недостаточно информации в резюме</li>
<li>Отсутствие портфолио</li>
<li>Нет опыта в смежных областях</li>
<li>Слабое описание достижений</li>
</ul>
<div class="section-title">GAP-анализ</div>
<p style="margin-bottom:24px">Требуется дополнительная проверка следующих компетенций:</p>
<ul style="margin-bottom:24px">
<li>Глубокое знание конкретных технологий</li>
<li>Опыт работы в команде</li>
<li>Понимание бизнес-процессов</li>
<li>Коммуникативные навыки</li>
<li>Мотивация и карьерные цели</li>
</ul>
<div class="section-title">Вопросы для интервью</div>
<div style="margin-bottom:24px">
<h4>Общие вопросы:</h4>
<ol>
<li>Расскажите о себе</li>
<li>Почему хотите работать у нас?</li>
<li>Какие ваши сильные стороны?</li>
<li>Где видите себя через 5 лет?</li>
<li>Как справляетесь со стрессом?</li>
</ol>
</div> </div>
<div class="section">
<h4>Ключевые компетенции</h4> <div class="section-title">Рекомендация</div>
<p>• Профессиональная экспертиза: ${Math.min(10, Math.floor(score/10) + 1)}/10</p> <div style="padding:16px;background:var(--cyan-50);border-radius:8px;border-left:4px solid var(--cyan)">
<p>• Практический опыт: ${Math.min(10, Math.floor(score/12) + 1)}/10</p> <strong>${candidate.score >= 80 ? 'Hire' : candidate.score >= 60 ? 'Hire with Reservations' : 'No Hire'}</strong>
<p>• Технические навыки: ${Math.min(10, Math.floor(score/11) + 1)}/10</p> <p style="margin-top:8px">Кандидат ${candidate.score >= 80 ? 'соответствует' : 'частично соответствует'} требованиям вакансии. ${candidate.score >= 80 ? 'Рекомендуется к интервью.' : 'Требуется дополнительная проверка компетенций.'}</p>
<p>• Коммуникационные навыки: ${Math.min(10, Math.floor(score/13) + 1)}/10</p>
<p>• Потенциал развития: ${Math.min(10, Math.floor(score/14) + 1)}/10</p>
</div>
<div class="section">
<h4>Рекомендация</h4>
<p>${score >= 80 ? 'Рекомендовать к интервью' : score >= 65 ? 'Рекомендовать с оговорками' : 'Рассмотреть альтернативно'}</p>
</div> </div>
`; `;
document.getElementById('resultsContent').innerHTML = resultsHTML; document.getElementById('candidateModal').classList.add('active');
}
function closeModal() {
document.getElementById('candidateModal').classList.remove('active');
}
// Закрытие модального окна при клике за пределами
document.getElementById('candidateModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('candidateModal')) {
closeModal();
}
});
// Формирование отчета для HR
function generateHRReport() {
if (analyzedCandidates.length === 0) {
alert('Сначала загрузите и проанализируйте резюме');
return;
}
const top10 = analyzedCandidates.slice(0, 10);
let report = `ОТЧЕТ ДЛЯ HR — ТОП-10 КАНДИДАТОВ\n\n`;
report += `Дата: ${new Date().toLocaleDateString()}\n`;
report += `Всего кандидатов: ${analyzedCandidates.length}\n\n`;
top10.forEach((candidate, index) => {
report += `${index + 1}. ${candidate.name}\n`;
report += ` Match Score: ${candidate.score}%\n`;
report += ` Уровень: ${candidate.level}\n`;
report += ` Опыт: ${candidate.experience}\n`;
report += ` Статус: ${candidate.status}\n`;
report += ` Город: ${candidate.city}\n`;
report += ` Последняя должность: ${candidate.lastPosition}\n`;
report += ` Рекомендация: ${candidate.score >= 80 ? 'Strong Hire' : candidate.score >= 60 ? 'Hire' : 'No Hire'}\n\n`;
});
// Скачивание отчета
const blob = new Blob([report], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `HR_Report_${new Date().toISOString().slice(0,10)}.txt`;
a.click();
URL.revokeObjectURL(url);
} }
</script> </script>
</body> </body>