first commit
This commit is contained in:
643
app.js
Normal file
643
app.js
Normal file
@@ -0,0 +1,643 @@
|
||||
// EduCat: AI Study Buddy - Main Application Logic
|
||||
|
||||
class EduCatApp {
|
||||
constructor() {
|
||||
this.uploadedFiles = [];
|
||||
this.studySessions = 0;
|
||||
this.quizzesCompleted = 0;
|
||||
this.generatedContent = [];
|
||||
this.currentTab = 'dashboard';
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
this.updateStats();
|
||||
this.updateFileSelects();
|
||||
this.showTab('dashboard');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Tab navigation
|
||||
document.querySelectorAll('.nav__item').forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const tab = e.target.dataset.tab || 'dashboard';
|
||||
this.showTab(tab);
|
||||
});
|
||||
});
|
||||
|
||||
// File upload - Fixed implementation
|
||||
const uploadArea = document.getElementById('upload-area');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
|
||||
if (uploadArea && fileInput) {
|
||||
uploadArea.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.add('drag-over');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove('drag-over');
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
this.processFiles(files);
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
this.processFiles(files);
|
||||
});
|
||||
}
|
||||
|
||||
// AI feature buttons
|
||||
document.querySelectorAll('.feature-card').forEach(card => {
|
||||
const button = card.querySelector('.btn');
|
||||
if (button) {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const feature = card.dataset.feature;
|
||||
this.generateContent(feature);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Quiz generation
|
||||
const generateQuizBtn = document.getElementById('generate-quiz');
|
||||
if (generateQuizBtn) {
|
||||
generateQuizBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.generateQuiz();
|
||||
});
|
||||
}
|
||||
|
||||
// Study planner interactions - Fixed implementation
|
||||
this.setupPlannerEvents();
|
||||
}
|
||||
|
||||
showTab(tabName) {
|
||||
// Update navigation
|
||||
document.querySelectorAll('.nav__item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.tab === tabName || (tabName === 'dashboard' && !item.dataset.tab)) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Update content
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
const targetTab = tabName === 'dashboard' ? 'dashboard' : tabName;
|
||||
const targetContent = document.getElementById(targetTab);
|
||||
if (targetContent) {
|
||||
targetContent.classList.add('active');
|
||||
}
|
||||
|
||||
this.currentTab = tabName;
|
||||
|
||||
// Special handling for library tab
|
||||
if (tabName === 'library') {
|
||||
this.updateLibraryContent();
|
||||
}
|
||||
}
|
||||
|
||||
processFiles(files) {
|
||||
if (!files || files.length === 0) {
|
||||
alert('No files selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
const validFiles = files.filter(file => this.isValidFile(file));
|
||||
|
||||
if (validFiles.length === 0) {
|
||||
alert('Please select valid file types: PDF, DOCX, PPTX, TXT, or XLSX');
|
||||
return;
|
||||
}
|
||||
|
||||
if (validFiles.length !== files.length) {
|
||||
alert('Some files were skipped due to invalid file types.');
|
||||
}
|
||||
|
||||
this.uploadFiles(validFiles);
|
||||
}
|
||||
|
||||
isValidFile(file) {
|
||||
const validExtensions = ['pdf', 'docx', 'pptx', 'txt', 'xlsx'];
|
||||
const fileName = file.name.toLowerCase();
|
||||
return validExtensions.some(ext => fileName.endsWith('.' + ext));
|
||||
}
|
||||
|
||||
uploadFiles(files) {
|
||||
const progressBar = document.getElementById('upload-progress');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
const progressText = document.getElementById('progress-text');
|
||||
|
||||
if (progressBar) {
|
||||
progressBar.classList.remove('hidden');
|
||||
}
|
||||
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
progress += Math.random() * 20 + 5;
|
||||
|
||||
if (progressFill) {
|
||||
progressFill.style.width = `${Math.min(progress, 100)}%`;
|
||||
}
|
||||
if (progressText) {
|
||||
progressText.textContent = `Uploading... ${Math.round(Math.min(progress, 100))}%`;
|
||||
}
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(progressInterval);
|
||||
this.completeUpload(files);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
completeUpload(files) {
|
||||
setTimeout(() => {
|
||||
files.forEach(file => {
|
||||
const fileData = {
|
||||
id: Date.now() + Math.random(),
|
||||
name: file.name,
|
||||
size: this.formatFileSize(file.size || 50000), // Default size for demo
|
||||
type: this.getFileType(file.name),
|
||||
uploadDate: new Date().toLocaleDateString(),
|
||||
content: this.generateMockContent(file.name)
|
||||
};
|
||||
this.uploadedFiles.push(fileData);
|
||||
});
|
||||
|
||||
const progressBar = document.getElementById('upload-progress');
|
||||
if (progressBar) {
|
||||
progressBar.classList.add('hidden');
|
||||
}
|
||||
|
||||
this.updateFileDisplay();
|
||||
this.updateStats();
|
||||
this.updateFileSelects();
|
||||
this.addActivity(`Uploaded ${files.length} file(s)`);
|
||||
|
||||
// Show success message
|
||||
alert(`Successfully uploaded ${files.length} file(s)!`);
|
||||
|
||||
// Clear file input
|
||||
const fileInput = document.getElementById('file-input');
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
getFileType(filename) {
|
||||
const extension = filename.split('.').pop().toUpperCase();
|
||||
return extension;
|
||||
}
|
||||
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
generateMockContent(filename) {
|
||||
const topics = ['Mathematics', 'Science', 'History', 'Literature', 'Computer Science'];
|
||||
const randomTopic = topics[Math.floor(Math.random() * topics.length)];
|
||||
return {
|
||||
topic: randomTopic,
|
||||
keyPoints: [
|
||||
`Key concept from ${filename}`,
|
||||
`Important theorem or principle`,
|
||||
`Practical application example`,
|
||||
`Historical context or background`
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
updateFileDisplay() {
|
||||
const container = document.getElementById('files-container');
|
||||
|
||||
if (!container) return;
|
||||
|
||||
if (this.uploadedFiles.length === 0) {
|
||||
container.innerHTML = '<div class="empty-state"><p>No files uploaded yet. Upload your first file to get started!</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = this.uploadedFiles.map(file => `
|
||||
<div class="file-item" data-file-id="${file.id}">
|
||||
<div class="file-icon">${file.type}</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">${file.name}</div>
|
||||
<div class="file-meta">${file.size} • ${file.uploadDate}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
const filesCount = document.getElementById('files-count');
|
||||
const sessionsCount = document.getElementById('sessions-count');
|
||||
const quizzesCount = document.getElementById('quizzes-count');
|
||||
|
||||
if (filesCount) filesCount.textContent = this.uploadedFiles.length;
|
||||
if (sessionsCount) sessionsCount.textContent = this.studySessions;
|
||||
if (quizzesCount) quizzesCount.textContent = this.quizzesCompleted;
|
||||
}
|
||||
|
||||
updateFileSelects() {
|
||||
const select = document.getElementById('quiz-file-select');
|
||||
if (!select) return;
|
||||
|
||||
select.innerHTML = '<option value="">Select a file to create quiz from</option>';
|
||||
|
||||
this.uploadedFiles.forEach(file => {
|
||||
const option = document.createElement('option');
|
||||
option.value = file.id;
|
||||
option.textContent = file.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
addActivity(activity) {
|
||||
const activityList = document.getElementById('activity-list');
|
||||
if (!activityList) return;
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
|
||||
// Remove empty state if it exists
|
||||
const emptyState = activityList.querySelector('.empty-state');
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
const activityItem = document.createElement('div');
|
||||
activityItem.className = 'activity-item';
|
||||
activityItem.innerHTML = `
|
||||
<span>📝</span>
|
||||
<span>${activity}</span>
|
||||
<small>${timestamp}</small>
|
||||
`;
|
||||
|
||||
activityList.insertBefore(activityItem, activityList.firstChild);
|
||||
|
||||
// Keep only last 5 activities
|
||||
const items = activityList.querySelectorAll('.activity-item');
|
||||
if (items.length > 5) {
|
||||
items[items.length - 1].remove();
|
||||
}
|
||||
}
|
||||
|
||||
generateContent(feature) {
|
||||
if (this.uploadedFiles.length === 0) {
|
||||
alert('Please upload some files first before generating content.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoadingModal(`Generating ${feature.replace('-', ' ')}...`);
|
||||
|
||||
setTimeout(() => {
|
||||
const content = this.createMockContent(feature);
|
||||
this.generatedContent.push(content);
|
||||
this.hideLoadingModal();
|
||||
this.updateLibraryContent();
|
||||
this.addActivity(`Generated ${feature.replace('-', ' ')}`);
|
||||
|
||||
// Switch to library tab to show the generated content
|
||||
this.showTab('library');
|
||||
}, 2000 + Math.random() * 2000);
|
||||
}
|
||||
|
||||
createMockContent(feature) {
|
||||
const randomFile = this.uploadedFiles[Math.floor(Math.random() * this.uploadedFiles.length)];
|
||||
const timestamp = new Date().toLocaleString();
|
||||
|
||||
const contentMap = {
|
||||
'study-guide': {
|
||||
title: `Study Guide: ${randomFile.content.topic}`,
|
||||
type: 'Study Guide',
|
||||
content: `
|
||||
<h4>Chapter Overview</h4>
|
||||
<p>This study guide covers the key concepts from ${randomFile.name}.</p>
|
||||
|
||||
<h4>Key Topics</h4>
|
||||
<ul>
|
||||
${randomFile.content.keyPoints.map(point => `<li>${point}</li>`).join('')}
|
||||
</ul>
|
||||
|
||||
<h4>Study Tips</h4>
|
||||
<ul>
|
||||
<li>Review each section multiple times</li>
|
||||
<li>Create flashcards for key terms</li>
|
||||
<li>Practice with sample problems</li>
|
||||
<li>Form study groups for discussion</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
'quiz': {
|
||||
title: `Practice Quiz: ${randomFile.content.topic}`,
|
||||
type: 'Quiz',
|
||||
content: `
|
||||
<div class="quiz-questions">
|
||||
<div class="quiz-question">
|
||||
<div class="question-text">1. What is the main concept discussed in the uploaded material?</div>
|
||||
<div class="quiz-options-list">
|
||||
<label class="quiz-option">
|
||||
<input type="radio" name="q1" value="a">
|
||||
<span>${randomFile.content.keyPoints[0]}</span>
|
||||
</label>
|
||||
<label class="quiz-option">
|
||||
<input type="radio" name="q1" value="b">
|
||||
<span>Alternative concept</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
'summary': {
|
||||
title: `Summary: ${randomFile.content.topic}`,
|
||||
type: 'Summary',
|
||||
content: `
|
||||
<p><strong>Document:</strong> ${randomFile.name}</p>
|
||||
<p><strong>Topic:</strong> ${randomFile.content.topic}</p>
|
||||
|
||||
<h4>Key Points Summary</h4>
|
||||
<p>The main concepts covered include:</p>
|
||||
<ul>
|
||||
${randomFile.content.keyPoints.map(point => `<li>${point}</li>`).join('')}
|
||||
</ul>
|
||||
|
||||
<p>This material provides a comprehensive overview of ${randomFile.content.topic} with practical applications and theoretical foundations.</p>
|
||||
`
|
||||
},
|
||||
'flashcards': {
|
||||
title: `Flashcards: ${randomFile.content.topic}`,
|
||||
type: 'Flashcards',
|
||||
content: `
|
||||
<div class="flashcard-set">
|
||||
${randomFile.content.keyPoints.map((point, index) => `
|
||||
<div class="flashcard" style="margin-bottom: 16px; padding: 16px; border: 1px solid var(--color-border); border-radius: 8px;">
|
||||
<div class="flashcard-front"><strong>Card ${index + 1}:</strong> What is ${point.toLowerCase()}?</div>
|
||||
<div class="flashcard-back" style="margin-top: 8px; color: var(--color-text-secondary);">Answer: ${point}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
id: Date.now() + Math.random(),
|
||||
...contentMap[feature],
|
||||
sourceFile: randomFile.name,
|
||||
createdAt: timestamp
|
||||
};
|
||||
}
|
||||
|
||||
updateLibraryContent() {
|
||||
const container = document.getElementById('generated-content');
|
||||
if (!container) return;
|
||||
|
||||
if (this.generatedContent.length === 0) {
|
||||
container.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
container.style.display = 'block';
|
||||
container.innerHTML = `
|
||||
<h3>Generated Study Materials</h3>
|
||||
${this.generatedContent.map(content => `
|
||||
<div class="content-item">
|
||||
<div class="content-header">
|
||||
<span class="content-title">${content.title}</span>
|
||||
<span class="status status--success">${content.type}</span>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<p><small>Generated from: ${content.sourceFile} • ${content.createdAt}</small></p>
|
||||
${content.content}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`;
|
||||
}
|
||||
|
||||
generateQuiz() {
|
||||
const fileSelect = document.getElementById('quiz-file-select');
|
||||
const questionsSelect = document.getElementById('quiz-questions');
|
||||
|
||||
if (!fileSelect || !fileSelect.value) {
|
||||
alert('Please select a file to generate quiz from.');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFile = this.uploadedFiles.find(file => file.id == fileSelect.value);
|
||||
const numQuestions = parseInt(questionsSelect.value) || 10;
|
||||
|
||||
this.showLoadingModal('Generating quiz questions...');
|
||||
|
||||
setTimeout(() => {
|
||||
const quiz = this.createQuizQuestions(selectedFile, numQuestions);
|
||||
this.displayQuiz(quiz);
|
||||
this.hideLoadingModal();
|
||||
this.addActivity(`Generated ${numQuestions}-question quiz`);
|
||||
}, 1500 + Math.random() * 1500);
|
||||
}
|
||||
|
||||
createQuizQuestions(file, numQuestions) {
|
||||
const questions = [];
|
||||
const questionTypes = [
|
||||
'What is the main concept of',
|
||||
'Which of the following best describes',
|
||||
'What is the key principle behind',
|
||||
'How does this concept apply to',
|
||||
'What is the relationship between'
|
||||
];
|
||||
|
||||
for (let i = 0; i < numQuestions; i++) {
|
||||
const questionType = questionTypes[Math.floor(Math.random() * questionTypes.length)];
|
||||
const keyPoint = file.content.keyPoints[i % file.content.keyPoints.length];
|
||||
|
||||
questions.push({
|
||||
id: i + 1,
|
||||
question: `${questionType} ${keyPoint.toLowerCase()}?`,
|
||||
options: [
|
||||
keyPoint,
|
||||
'Alternative option A',
|
||||
'Alternative option B',
|
||||
'Alternative option C'
|
||||
].sort(() => Math.random() - 0.5),
|
||||
correct: keyPoint
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
title: `Quiz: ${file.content.topic}`,
|
||||
source: file.name,
|
||||
questions: questions
|
||||
};
|
||||
}
|
||||
|
||||
displayQuiz(quiz) {
|
||||
const container = document.getElementById('quiz-container');
|
||||
if (!container) return;
|
||||
|
||||
container.classList.remove('hidden');
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="quiz-header">
|
||||
<h3>${quiz.title}</h3>
|
||||
<p>Source: ${quiz.source}</p>
|
||||
</div>
|
||||
<div class="quiz-questions">
|
||||
${quiz.questions.map(q => `
|
||||
<div class="quiz-question">
|
||||
<div class="question-text">${q.id}. ${q.question}</div>
|
||||
<div class="quiz-options-list">
|
||||
${q.options.map((option, index) => `
|
||||
<label class="quiz-option">
|
||||
<input type="radio" name="q${q.id}" value="${option}">
|
||||
<span>${option}</span>
|
||||
</label>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button class="btn btn--primary" onclick="app.submitQuiz()">Submit Quiz</button>
|
||||
`;
|
||||
}
|
||||
|
||||
submitQuiz() {
|
||||
const questions = document.querySelectorAll('.quiz-question');
|
||||
let score = 0;
|
||||
let total = questions.length;
|
||||
|
||||
questions.forEach((question, index) => {
|
||||
const selected = question.querySelector('input[type="radio"]:checked');
|
||||
if (selected) {
|
||||
// For demo purposes, we'll give a random score
|
||||
if (Math.random() > 0.3) score++;
|
||||
}
|
||||
});
|
||||
|
||||
this.quizzesCompleted++;
|
||||
this.updateStats();
|
||||
this.addActivity(`Completed quiz - Score: ${score}/${total}`);
|
||||
|
||||
alert(`Quiz completed! Your score: ${score}/${total} (${Math.round((score/total) * 100)}%)`);
|
||||
}
|
||||
|
||||
setupPlannerEvents() {
|
||||
// Wait for DOM to be ready and then attach events
|
||||
setTimeout(() => {
|
||||
const plannerButtons = document.querySelectorAll('.planner-card .btn--secondary');
|
||||
plannerButtons.forEach((btn, index) => {
|
||||
// Remove existing listeners to prevent duplication
|
||||
btn.replaceWith(btn.cloneNode(true));
|
||||
const newBtn = document.querySelectorAll('.planner-card .btn--secondary')[index];
|
||||
|
||||
newBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (index === 0) {
|
||||
this.addStudySession();
|
||||
} else if (index === 1) {
|
||||
this.addStudyGoal();
|
||||
} else if (index === 2) {
|
||||
this.setReminder();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Goal checkboxes
|
||||
const goalCheckboxes = document.querySelectorAll('.goal-item input[type="checkbox"]');
|
||||
goalCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
if (checkbox.checked) {
|
||||
this.studySessions++;
|
||||
this.updateStats();
|
||||
this.addActivity('Completed study goal');
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
addStudySession() {
|
||||
const time = prompt('Enter study time (e.g., 3:00 PM):');
|
||||
const subject = prompt('Enter subject:');
|
||||
|
||||
if (time && subject) {
|
||||
this.studySessions++;
|
||||
this.updateStats();
|
||||
this.addActivity(`Scheduled: ${subject} at ${time}`);
|
||||
alert('Study session added to your schedule!');
|
||||
}
|
||||
}
|
||||
|
||||
addStudyGoal() {
|
||||
const goal = prompt('Enter your study goal:');
|
||||
if (goal) {
|
||||
this.addActivity(`New goal: ${goal}`);
|
||||
alert('Study goal added!');
|
||||
}
|
||||
}
|
||||
|
||||
setReminder() {
|
||||
const reminder = prompt('Enter reminder text:');
|
||||
if (reminder) {
|
||||
this.addActivity(`Set reminder: ${reminder}`);
|
||||
alert('Reminder set!');
|
||||
}
|
||||
}
|
||||
|
||||
showLoadingModal(title, text = 'Please wait while we analyze your content and generate study materials.') {
|
||||
const modal = document.getElementById('loading-modal');
|
||||
const titleEl = document.getElementById('loading-title');
|
||||
const textEl = document.getElementById('loading-text');
|
||||
|
||||
if (modal) modal.classList.remove('hidden');
|
||||
if (titleEl) titleEl.textContent = title;
|
||||
if (textEl) textEl.textContent = text;
|
||||
}
|
||||
|
||||
hideLoadingModal() {
|
||||
const modal = document.getElementById('loading-modal');
|
||||
if (modal) modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application
|
||||
let app;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
app = new EduCatApp();
|
||||
|
||||
// Add some sample activity for demo
|
||||
setTimeout(() => {
|
||||
app.addActivity('Welcome to EduCat!');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Handle modal click to close
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'loading-modal') {
|
||||
if (app) app.hideLoadingModal();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user