document.getElementById('predictionForm').addEventListener('submit', async (e) => {
e.preventDefault();
// 1. Collect Course Data
const courseRows = document.querySelectorAll('.course-row');
const coursesArray = Array.from(courseRows).map(row => ({
name: row.querySelector('.course-name').value.toUpperCase().trim(),
grade: parseFloat(row.querySelector('.course-grade').value)
}));
// 2. Collect AI Features
const studentData = {
ssc: parseFloat(document.getElementById('ssc').value),
last: parseFloat(document.getElementById('last').value),
attendance: parseInt(document.getElementById('attendance').value),
preparation: parseInt(document.getElementById('preparation').value),
income: 2, hometown: 1, department: 0, gaming: 2,
courses: coursesArray
};
try {
const response = await fetch('http://127.0.0.1:8000/predict', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(studentData)
});
const data = await response.json();
const resultArea = document.getElementById('resultArea');
const resultBox = document.getElementById('resultBox');
const resultMessage = document.getElementById('resultMessage');
resultMessage.innerHTML = "";
resultArea.classList.remove('hidden');
// Logic to show "Confidence in Success" vs "Risk of Failure"
let displayScore;
let scoreLabel;
if (data.needs_resources) {
// High Risk: Show the risk percentage
displayScore = data.confidence_score;
scoreLabel = "AI Habit Risk Prediction: " + displayScore + "% chance of falling behind.";
} else {
// Low Risk: Show the success confidence (100 - risk)
displayScore = (100 - data.confidence_score).toFixed(2);
scoreLabel = "AI Success Confidence: " + displayScore + "% chance of staying on track.";
}
// Apply UI Styling
let htmlContent = `
${data.message}
`;
htmlContent += `🧠 ${scoreLabel}
`;
const styles = {
critical: "border-red-300 bg-red-50 text-red-800",
subject_alert: "border-yellow-300 bg-yellow-50 text-yellow-800",
habit_alert: "border-orange-300 bg-orange-50 text-orange-800",
safe: "border-green-300 bg-green-50 text-green-800"
};
resultBox.className = `p-4 rounded-md border ${styles[data.alert_level]}`;
//generate resource links from Neon
if (data.resource_links && data.resource_links.length > 0) {
htmlContent += `Recommended Foundational Resources:
`;
htmlContent += ``;
const seenResources = new Set();
data.resource_links.forEach(resource => {
if (!seenResources.has(resource.url)) {
// 1. Determine Icon and Color Styling based on resource_type
let icon, bgStyle, textStyle, label;
const type = resource.resource_type.toLowerCase();
if (type === 'video') {
icon = '🎥';
bgStyle = 'bg-red-50 border-red-200';
textStyle = 'text-red-700';
label = 'YouTube Video';
} else if (type === 'book') {
icon = '📚';
bgStyle = 'bg-blue-50 border-blue-200';
textStyle = 'text-blue-700';
label = 'Textbook / PDF';
} else if (type === 'article') {
icon = '📄';
bgStyle = 'bg-gray-50 border-gray-200';
textStyle = 'text-gray-700';
label = 'Web Article';
} else {
icon = '🔗';
bgStyle = 'bg-white border-gray-200';
textStyle = 'text-gray-600';
label = 'Resource';
}
htmlContent += `
${icon}
${resource.course_code} - ${label}
${resource.title}
${resource.explanation ? `
${resource.explanation}
` : ''}
`;
seenResources.add(resource.url);
}
});
htmlContent += `
`;
} else if (data.subjects_to_focus && data.subjects_to_focus.length > 0) {
htmlContent += `
Review habits for these codes:
${data.subjects_to_focus.map(s => `${s} `).join('')}
`;
}
resultMessage.innerHTML = htmlContent;
} catch (error) {
console.error("Connection Error:", error);
alert("Ensure FastAPI is running on port 8000");
}
});
function addCourseRow() {
const container = document.getElementById('courseContainer');
const newRow = document.createElement('div');
newRow.className = "flex gap-2 course-row";
newRow.innerHTML = `
× `;
container.appendChild(newRow);
}
function removeRow(btn) { btn.parentElement.remove(); }
let currentFilename= "";
async function handlePDFUpload(event) {
if(event) event.preventDefault(); // Extra safety to prevent page reloads
const fileInput = document.getElementById('pdfFile');
const urlInput = document.getElementById('pdfUrl');
const statusElement = document.getElementById('uploadStatus');
const pdfFrame = document.getElementById('pdfFrame');
const pdfContainer = document.getElementById('pdfContainer');
const processBtn = document.getElementById('processBtn');
if (fileInput.files.length === 0 && urlInput.value.trim() === "") {
alert("Please provide either a PDF file or URL");
return;
}
statusElement.innerHTML = ' AI is reading and indexing your PDF...';
processBtn.disabled = true;
processBtn.classList.add('opacity-50', 'cursor-not-allowed');
if (fileInput.files.length > 0) {
const fileURL = URL.createObjectURL(fileInput.files[0]);
pdfFrame.src = fileURL;
pdfContainer.classList.remove('hidden');
} else if (urlInput.value.trim() !== "") {
pdfFrame.src = urlInput.value;
pdfContainer.classList.remove('hidden');
}
let formData = new FormData();
let response;
try {
if (fileInput.files.length > 0) {
formData.append('file', fileInput.files[0]);
response = await fetch('http://127.0.0.1:8000/upload-pdf', { method: 'POST', body: formData });
} else {
response = await fetch('http://127.0.0.1:8000/analyze-pdf-url', {
method: 'POST',
headers : { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: urlInput.value })
});
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.message === "Success") {
currentFilename = data.filename;
statusElement.innerText = `✅ Loaded: ${data.filename} (${data.chunks_processed || data.chunks} chunks)`;
}
} catch (error) {
console.error("Error processing PDF:", error);
statusElement.textContent = 'Error processing PDF: ' + error.message;
} finally {
processBtn.disabled = false;
processBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
}
async function askQuestion() {
const input = document.getElementById('userQuestion');
const chatMessages = document.getElementById('chatMessages');
const question = input.value.trim();
if (!question) return;
// Show User Message
chatMessages.innerHTML += ``;
input.value = "";
const loadingId = "ai-loading-" + Date.now();
chatMessages.innerHTML += ``;
chatMessages.scrollTop = chatMessages.scrollHeight;
try {
const response = await fetch('http://127.0.0.1:8000/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ question: question, filename: currentFilename })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Remove thinking message and show real answer
const loadingElement = document.getElementById(loadingId);
if (loadingElement) loadingElement.remove();
const formattedAnswer = marked.parse(data.answer);
chatMessages.innerHTML += ``;
} catch (error) {
const loadingElement = document.getElementById(loadingId);
if (loadingElement) {
if (error.message.includes("429")) {
loadingElement.innerHTML = `⚠️ The AI is thinking too fast! Please wait 30 seconds and try asking again.
`;
} else {
loadingElement.innerHTML = `Error: ${error.message}
`;
}
}
}
chatMessages.scrollTop = chatMessages.scrollHeight;
}
//sidebar elements
const menuBtn = document.getElementById('menuBtn');
const sidebar = document.getElementById('sidebar');
const overlap = document.getElementById('overlap');
const pdfLibraryList = document.getElementById('pdfLibraryList');
//toggle sidebar logic
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('-translate-x-full');
overlay.classList.toggle('opacity-50');
overlay.classList.toggle('pointer-events-auto');
if (!sidebar.classList.contains('-translate-x-full')) {
refreshLibrary(); // Fetch list when opening
}
});
overlay.addEventListener('click', () => {
sidebar.classList.add('-translate-x-full');
overlay.classList.remove('opacity-50', 'pointer-events-auto');
});
// 2. Fetch and Display Library
async function refreshLibrary() {
try {
const response = await fetch('http://127.0.0.1:8000/library');
const files = await response.json();
if (files.length === 0) {
pdfLibraryList.innerHTML = 'Library is empty.
';
return;
}
pdfLibraryList.innerHTML = files.map(filename => `
📄
${filename}
`).join('');
} catch (err) {
console.error("Library Error:", err);
}
}
// 3. Select a file from History
function selectLibraryFile(filename) {
currentFilename = filename;
// Update the UI
document.getElementById('uploadStatus').innerHTML = `✅ Active Subject: ${filename}`;
document.getElementById('pdfViewerContainer').classList.remove('hidden');
// Note:Will need to integrate with Firebase/AWS S3
// Since we don't store the full file URL in the vector DB,
// we can't show the PDF in the iframe unless it was just uploaded.
// For now, we clear the iframe to show we switched subjects.
document.getElementById('pdfFrame').src = "";
// Clear chat and Close sidebar
document.getElementById('chatMessages').innerHTML = `Switched to: ${filename}
`;
sidebar.classList.add('-translate-x-full');
overlay.classList.remove('opacity-50', 'pointer-events-auto');
}
document.getElementById("userQuestion").addEventListener("keypress", function(event) {
if (event.key === "Enter") {
event.preventDefault(); // Stops the page reload
askQuestion();
}
});
async function generateBundle() {
const minutes = document.getElementById('time-budget').value;
const topic = document.getElementById('topic-select').value;
const resultsContainer = document.getElementById("bundle-results");
resultsContainer.innerHTML = "⏳ Curating your study bundle...
";
try {
const response = await fetch("http://127.0.0.1:8000/bundler/create", {
method : "POST",
headers : {
"Content-Type" : "application/json"
},
body: JSON.stringify({
minutes_available: parseInt(minutes),
topic : ""
})
});
if (!response.ok) throw new Error("Could not generate bundle.");
const data = await response.json();
//clear laading text
resultsContainer.innerHTML = "";
//loop through the retuned resources and display
data.forEach(item => {
resultsContainer.innerHTML += `
${item.type.toUpperCase()} (${item.duration_min} mins)
${item.content}
`;
});
} catch (error) {
resultsContainer.innerHTML = `Error: ${error.message}
`;
}
}