// Helper function to convert basic markdown to HTML
function convertMarkdownToHTML(text) {
if (typeof text !== 'string') {
return text;
}
let htmlText = text;
htmlText = htmlText.replace(/\*\*(.*?)\*\*/g, '$1');
htmlText = htmlText.replace(/\*(.*?)\*/g, '$1');
htmlText = htmlText.replace(/\n/g, '
');
return htmlText;
}
document.addEventListener('DOMContentLoaded', () => {
const studentSelector = document.getElementById('student-selector');
const generateReportBtn = document.getElementById('generate-report-btn');
const jobApplicationInput = document.getElementById('job-application-input');
const analyzeJobBtn = document.getElementById('analyze-job-btn');
const loadingSpinner = document.getElementById('loading-spinner');
const reportContainer = document.getElementById('reports');
const jobAnalysisContainer = document.getElementById('job-analysis');
const chatbotContainer = document.getElementById('chat');
const chatForm = document.getElementById('chat-form');
const chatInput = document.getElementById('chat-input');
const chatHistory = document.getElementById('chat-history');
const newChatBtn = document.getElementById('new-chat-btn');
const chatSessionsList = document.getElementById('chat-sessions-list');
// Chat State
let currentSessionId = null;
// Chart Instances
let skillsChart = null;
let dsaChart = null;
let jobMatchChart = null;
let cgpaTrendChart = null;
let dsaPerformanceChart = null;
// Store pan-zoom instances to prevent memory leaks
const panZoomInstances = new Map();
// Initialize Mermaid
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
themeVariables: {
primaryColor: '#00E5FF',
primaryTextColor: '#F0F0F0',
primaryBorderColor: '#00E5FF',
lineColor: '#00E5FF',
secondaryColor: '#0099CC',
tertiaryColor: '#2C2C2C'
},
flowchart: {
useMaxWidth: false,
htmlLabels: true,
curve: 'basis'
}
});
// Navigation Elements
const navLinks = document.querySelectorAll('.nav-link');
const contentSections = document.querySelectorAll('.content-section');
// --- Sidebar Navigation ---
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href').substring(1);
navLinks.forEach(l => l.classList.remove('active'));
link.classList.add('active');
contentSections.forEach(section => {
section.classList.add('hidden');
if (section.id === targetId) {
section.classList.remove('hidden');
section.classList.add('active');
if (targetId === 'dashboard') {
loadDashboardData();
}
} else {
section.classList.remove('active');
}
});
});
});
// --- Tab Navigation for Report Section ---
initializeTabs('.ai-analysis-report', 'data-tab');
initializeTabs('.ai-suggestion-channel', 'data-tab');
function initializeTabs(containerSelector, attribute) {
const container = document.querySelector(containerSelector);
if (!container) return;
const tabButtons = container.querySelectorAll('.tab-btn');
const tabContents = container.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const targetTab = button.getAttribute(attribute);
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === `${targetTab}-tab`) {
content.classList.add('active');
if (targetTab === 'performance' && studentSelector.value) {
loadPerformanceCharts(studentSelector.value);
}
}
});
});
});
}
// --- Initial Navigation Setup ---
const dashboardLink = document.querySelector('.nav-link[href="#dashboard"]');
if (dashboardLink) {
dashboardLink.click();
}
// --- Populate student dropdown ---
fetch('/api/students')
.then(response => response.json())
.then(students => {
students.forEach(student => {
const option = document.createElement('option');
option.value = student.enrollment_no;
option.textContent = `${student.name} (${student.enrollment_no})`;
studentSelector.appendChild(option);
});
})
.catch(error => console.error('Error fetching students:', error));
// --- Enable buttons when inputs are filled ---
studentSelector.addEventListener('change', () => {
const hasSelection = !!studentSelector.value;
generateReportBtn.disabled = !hasSelection;
});
jobApplicationInput.addEventListener('input', () => {
analyzeJobBtn.disabled = !jobApplicationInput.value.trim();
});
// --- Report History Logic ---
const reportHistoryBtn = document.getElementById('report-history-btn');
const reportHistoryModal = document.getElementById('report-history-modal');
const reportHistoryList = document.getElementById('report-history-list');
const closeModalBtn = document.querySelector('.close-modal');
if (reportHistoryBtn) {
reportHistoryBtn.addEventListener('click', () => {
const enrollmentNo = studentSelector.value;
if (!enrollmentNo) {
alert('Please select a student first.');
return;
}
loadReportHistory(enrollmentNo);
reportHistoryModal.classList.remove('hidden');
});
}
if (closeModalBtn) {
closeModalBtn.addEventListener('click', () => {
reportHistoryModal.classList.add('hidden');
});
}
window.addEventListener('click', (e) => {
if (e.target === reportHistoryModal) {
reportHistoryModal.classList.add('hidden');
}
});
function loadReportHistory(enrollmentNo) {
reportHistoryList.innerHTML = '
No saved reports found.
'; return; } history.forEach(report => { const item = document.createElement('div'); item.className = 'report-item'; const date = new Date(report.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); item.innerHTML = `Failed to load history.
'; }); } function loadSavedReport(enrollmentNo, reportId) { loadingSpinner.classList.remove('hidden'); fetch(`/api/report/load/${enrollmentNo}/${reportId}`) .then(response => response.json()) .then(report => { loadingSpinner.classList.add('hidden'); if (report.error) { alert(`Error loading report: ${report.error}`); } else { displayNewReport(report); } }) .catch(error => { loadingSpinner.classList.add('hidden'); console.error('Error loading saved report:', error); alert('Failed to load report.'); }); } // --- Handle "Generate Report" button click --- generateReportBtn.addEventListener('click', () => { const enrollmentNo = studentSelector.value; if (!enrollmentNo) return; loadingSpinner.classList.remove('hidden'); document.querySelector('.nav-link[href="#reports"]').click(); fetch(`/api/report/${enrollmentNo}`) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(report => { loadingSpinner.classList.add('hidden'); if (report.error) { alert(`Error generating report: ${report.error}`); } else { displayNewReport(report); } }) .catch(error => { loadingSpinner.classList.add('hidden'); console.error('Report generation error:', error); alert(`An unexpected error occurred: ${error.message}`); }); if (enrollmentNo) { // Also load chat history for this student loadChatSessions(enrollmentNo); } else { alert('Please select a student first.'); } }); // New Chat Button if (newChatBtn) { newChatBtn.addEventListener('click', () => { currentSessionId = null; chatHistory.innerHTML = ''; // Remove active class from all sessions document.querySelectorAll('.session-item').forEach(item => item.classList.remove('active')); appendMessage('Start a new conversation!', 'ai', false, true); }); } // --- Handle "Analyze Job Application" button click --- analyzeJobBtn.addEventListener('click', () => { const jobDescription = jobApplicationInput.value.trim(); const enrollmentNo = studentSelector.value; if (!jobDescription) { alert('Please paste a job description.'); return; } if (!enrollmentNo) { alert('Please select a student first.'); return; } loadingSpinner.classList.remove('hidden'); jobAnalysisContainer.classList.add('hidden'); fetch('/api/job-analysis', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enrollment_no: enrollmentNo, job_description: jobDescription }) }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { loadingSpinner.classList.add('hidden'); if (data.error) { alert(`Error analyzing job application: ${data.error}`); } else { displayJobAnalysis(data.data); } }) .catch(error => { loadingSpinner.classList.add('hidden'); console.error('Job analysis error:', error); alert(`An unexpected error occurred: ${error.message}`); }); }); // --- Chat form submission --- chatForm.addEventListener('submit', (e) => { e.preventDefault(); const enrollmentNo = studentSelector.value; const question = chatInput.value.trim(); if (!question || !enrollmentNo) { if (!enrollmentNo) alert('Please select a student first.'); return; } // If not already in chat tab, switch to it const chatTabLink = document.querySelector('.nav-link[href="#chat"]'); if (chatTabLink && !chatTabLink.classList.contains('active')) { chatTabLink.click(); } appendMessage(question, 'user'); chatInput.value = ''; appendMessage('Thinking...', 'ai', true); fetch('/api/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enrollment_no: enrollmentNo, question: question, session_id: currentSessionId }) }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { const loadingElement = chatHistory.querySelector('.loading'); if (loadingElement) { loadingElement.parentElement.remove(); } if (data.success) { appendMessage(data.answer, 'ai', false, true); // Update session ID if it was a new session if (!currentSessionId && data.session_id) { currentSessionId = data.session_id; // Reload sessions list to show the new one loadChatSessions(enrollmentNo); } } else { appendMessage(`Error: ${data.error}`, 'ai'); } chatHistory.scrollTop = chatHistory.scrollHeight; }) .catch(error => { console.error('Chat error:', error); const loadingElement = chatHistory.querySelector('.loading'); if (loadingElement) { loadingElement.parentElement.remove(); } appendMessage('Sorry, an error occurred while fetching the answer.', 'ai', false, true); }); }); // --- Chat History Functions --- function loadChatSessions(enrollmentNo) { if (!chatSessionsList) return; fetch(`/api/chat/history/${enrollmentNo}`) .then(res => res.json()) .then(sessions => { chatSessionsList.innerHTML = ''; if (sessions.error) { console.error(sessions.error); return; } if (sessions.length === 0) { chatSessionsList.innerHTML = 'Error rendering flowchart. Please check the diagram syntax.
'; }); } // --- Display Report Function --- function displayNewReport(report) { // Update Profile Card document.getElementById('student-name').textContent = report.name || 'Student Name'; document.getElementById('student-id').textContent = `ID: ${report.enrollment_no || 'N/A'}`; document.getElementById('student-phone').textContent = report.phone || '+91 9876543210'; document.getElementById('student-email').textContent = report.email || 'student@example.com'; if (report.photo) { document.getElementById('student-photo').src = report.photo; } // Update Summary Tab document.getElementById('hr-summary').innerHTML = convertMarkdownToHTML(report.overall_summary || 'No summary available.'); const strengthsList = document.getElementById('summary-strengths'); strengthsList.innerHTML = ''; if (report.analysis?.strengths) { report.analysis.strengths.forEach(strength => { const li = document.createElement('li'); li.innerHTML = convertMarkdownToHTML(strength); strengthsList.appendChild(li); }); } const weaknessesList = document.getElementById('summary-weaknesses'); weaknessesList.innerHTML = ''; if (report.analysis?.weaknesses) { report.analysis.weaknesses.forEach(weakness => { const li = document.createElement('li'); li.innerHTML = convertMarkdownToHTML(weakness); weaknessesList.appendChild(li); }); } // Update Skills Tab const skillsList = document.getElementById('skills-list'); skillsList.innerHTML = ''; if (report.skills) { report.skills.forEach(skill => { const skillRow = document.createElement('div'); skillRow.className = 'skill-row'; skillRow.innerHTML = `${convertMarkdownToHTML(recommendation.description || '')}
${recommendation.mermaid_flowchart ? `${convertMarkdownToHTML(path.description || '')}
`; if (path.resources) { const resourcesDiv = document.createElement('div'); resourcesDiv.className = 'recommended-resources'; resourcesDiv.innerHTML = '${resource.description || ''}
${video.reason || ''}
No videos found for this topic.
'; } topicSection.appendChild(videoGrid); youtubeContainer.appendChild(topicSection); }); } } // --- Dashboard Logic --- const refreshDashboardBtn = document.getElementById('refresh-dashboard-btn'); if (refreshDashboardBtn) { refreshDashboardBtn.addEventListener('click', loadDashboardData); } function loadDashboardData() { console.log("Loading dashboard data..."); fetch('/api/dashboard/students') .then(response => response.json()) .then(data => { if (data.error) { console.error("Error loading dashboard:", data.error); return; } // Update Summary Cards const totalStudentsEl = document.getElementById('total-students-count'); if (totalStudentsEl) totalStudentsEl.textContent = data.total_students; const classAvgEl = document.getElementById('class-avg-cgpa'); if (classAvgEl) classAvgEl.textContent = data.class_average_cgpa; // Count reports ready const reportsReady = data.students.filter(s => s.report_status === 'Generated').length; const reportsReadyEl = document.getElementById('reports-ready-count'); if (reportsReadyEl) reportsReadyEl.textContent = reportsReady; // Populate Student Table const tableBody = document.getElementById('student-table-body'); if (tableBody) { tableBody.innerHTML = ''; data.students.forEach(student => { const row = document.createElement('tr'); // Status Badges const reportBadgeClass = student.report_status === 'Generated' ? 'badge-success' : 'badge-pending'; const cgpaClass = parseFloat(student.cgpa) >= 8.0 ? 'text-success' : (parseFloat(student.cgpa) >= 6.0 ? 'text-warning' : 'text-danger'); row.innerHTML = `