first commit
This commit is contained in:
515
js/app.js
515
js/app.js
@@ -0,0 +1,515 @@
|
||||
// EduCat: AI Study Buddy - Initial JS
|
||||
|
||||
// --- PAGE ROUTING LOGIC ---
|
||||
const pages = {
|
||||
main: `
|
||||
<header><h1>🐾 Welcome to EduCat: AI Study Buddy</h1><p>Your purr-fect companion for smarter studying!</p></header>
|
||||
<main>
|
||||
<section id="dashboard-section"></section>
|
||||
<section id="reminder-section"></section>
|
||||
<section id="chat-section"></section>
|
||||
</main>
|
||||
`,
|
||||
upload: `
|
||||
<header><h1>📤 Upload Materials & AI Summary 🐱</h1></header>
|
||||
<main>
|
||||
<section id="upload-section"></section>
|
||||
<section id="summary-section"></section>
|
||||
</main>
|
||||
`,
|
||||
quiz: `
|
||||
<header><h1>📝 Practice Quiz 🐾</h1></header>
|
||||
<main>
|
||||
<section id="quiz-section"></section>
|
||||
</main>
|
||||
`,
|
||||
chatbot: `
|
||||
<header><h1>💬 EduCat Chatbot 🐱</h1></header>
|
||||
<main>
|
||||
<section id="chat-section"></section>
|
||||
</main>
|
||||
`
|
||||
};
|
||||
|
||||
function renderPage(page) {
|
||||
document.body.innerHTML = `
|
||||
<nav style="display:flex;gap:1.2rem;padding:1rem 2rem;background:#f0f4fa;align-items:center;">
|
||||
<button class="nav-btn" data-page="main">🏠 Home</button>
|
||||
<button class="nav-btn" data-page="upload">📤 Upload & Summary</button>
|
||||
<button class="nav-btn" data-page="quiz">📝 Practice Quiz</button>
|
||||
<button class="nav-btn" data-page="chatbot">💬 Chatbot</button>
|
||||
</nav>
|
||||
${pages[page]}
|
||||
<footer><p>EduCat © 2025. All rights reserved.</p></footer>
|
||||
`;
|
||||
document.querySelectorAll('.nav-btn').forEach(btn => {
|
||||
btn.onclick = () => renderPage(btn.dataset.page);
|
||||
});
|
||||
// Re-initialize page content
|
||||
if (page === 'main') {
|
||||
renderDashboard();
|
||||
renderReminders();
|
||||
renderChatbot();
|
||||
} else if (page === 'upload') {
|
||||
renderUpload();
|
||||
renderSummary();
|
||||
} else if (page === 'quiz') {
|
||||
renderQuiz();
|
||||
} else if (page === 'chatbot') {
|
||||
renderChatbot();
|
||||
}
|
||||
}
|
||||
|
||||
// --- COMPONENT RENDERERS ---
|
||||
function renderDashboard() {
|
||||
const progressSection = document.getElementById('dashboard-section');
|
||||
if (!progressSection) return;
|
||||
progressSection.innerHTML = `
|
||||
<h2>📊 Progress Dashboard 🐾</h2>
|
||||
<div id="dashboard-cards" style="display:flex;flex-wrap:wrap;gap:1.5rem;justify-content:space-between;">
|
||||
<div class="dashboard-card">
|
||||
<h3>📚 Study Sessions</h3>
|
||||
<p><b>5</b> sessions this week</p>
|
||||
<div class="dashboard-bar"><div style="width:70%"></div></div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>✅ Quizzes Completed</h3>
|
||||
<p><b>3</b> / 5 this week</p>
|
||||
<div class="dashboard-bar"><div style="width:60%"></div></div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>🏆 Streak</h3>
|
||||
<p><b>4</b> days in a row</p>
|
||||
<div class="dashboard-bar"><div style="width:80%"></div></div>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h3>💡 Motivation</h3>
|
||||
<p>“Keep going, you’re doing great!”</p>
|
||||
<div class="dashboard-bar"><div style="width:90%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderReminders() {
|
||||
const reminderSection = document.getElementById('reminder-section');
|
||||
if (!reminderSection) return;
|
||||
reminderSection.innerHTML = `
|
||||
<h2>⏰ Study Reminders 🐱</h2>
|
||||
<div class="reminder-card">
|
||||
<ul id="reminder-list">
|
||||
<li>📅 Next quiz: <b>Friday, 3pm</b></li>
|
||||
<li>⏰ Review notes for <b>Math</b> tonight</li>
|
||||
<li>📝 Complete 2 practice quizzes by <b>Sunday</b></li>
|
||||
<li>💧 Take a short break every 45 minutes!</li>
|
||||
</ul>
|
||||
<form id="reminder-form" style="margin-top:1em;display:flex;gap:0.5em;flex-wrap:wrap;">
|
||||
<input type="text" id="reminder-input" placeholder="Add a schedule, quiz date, etc..." style="flex:1;padding:0.4em;border-radius:6px;border:1px solid #ccc;">
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
const reminderList = document.getElementById('reminder-list');
|
||||
const reminderForm = document.getElementById('reminder-form');
|
||||
const reminderInput = document.getElementById('reminder-input');
|
||||
reminderForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const val = reminderInput.value.trim();
|
||||
if (val) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = val;
|
||||
reminderList.appendChild(li);
|
||||
reminderInput.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderChatbot() {
|
||||
const chatSection = document.getElementById('chat-section');
|
||||
if (!chatSection) return;
|
||||
chatSection.innerHTML = `
|
||||
<h2>EduCat Chatbot</h2>
|
||||
<div id="chat-window" style="background:#f0f4fa;min-height:120px;padding:1rem;border-radius:8px;overflow-y:auto;max-height:260px;margin-bottom:1rem;"></div>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
<input type="text" id="chat-input" placeholder="Type your message..." style="flex:1;padding:0.5rem;border-radius:6px;border:1px solid #ccc;">
|
||||
<button id="chat-send">Send</button>
|
||||
</div>
|
||||
`;
|
||||
const chatWindow = document.getElementById('chat-window');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const sendBtn = document.getElementById('chat-send');
|
||||
|
||||
function addBubble(text, sender) {
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = sender === 'user' ? 'bubble user-bubble' : 'bubble ai-bubble';
|
||||
bubble.innerHTML = text;
|
||||
chatWindow.appendChild(bubble);
|
||||
chatWindow.scrollTop = chatWindow.scrollHeight;
|
||||
}
|
||||
|
||||
function showTyping() {
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'bubble ai-bubble typing';
|
||||
typing.id = 'ai-typing';
|
||||
typing.innerHTML = '<span class="dot"></span><span class="dot"></span><span class="dot"></span> EduCat is typing...';
|
||||
chatWindow.appendChild(typing);
|
||||
chatWindow.scrollTop = chatWindow.scrollHeight;
|
||||
}
|
||||
|
||||
function hideTyping() {
|
||||
const typing = document.getElementById('ai-typing');
|
||||
if (typing) typing.remove();
|
||||
}
|
||||
|
||||
sendBtn.addEventListener('click', sendMessage);
|
||||
chatInput.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
|
||||
|
||||
function sendMessage() {
|
||||
const msg = chatInput.value.trim();
|
||||
if (!msg) return;
|
||||
addBubble(msg, 'user');
|
||||
chatInput.value = '';
|
||||
showTyping();
|
||||
fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: msg })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
hideTyping();
|
||||
setTimeout(() => {
|
||||
addBubble(data.reply || data.error, 'ai');
|
||||
}, 500); // Simulate slight delay for realism
|
||||
})
|
||||
.catch(() => {
|
||||
hideTyping();
|
||||
addBubble('Error contacting chatbot.', 'ai');
|
||||
});
|
||||
}
|
||||
|
||||
// Add styles for bubbles and typing
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
#chat-window { font-size: 1rem; }
|
||||
.bubble {
|
||||
display: inline-block;
|
||||
padding: 0.6em 1em;
|
||||
margin: 0.3em 0;
|
||||
border-radius: 18px;
|
||||
max-width: 80%;
|
||||
word-break: break-word;
|
||||
clear: both;
|
||||
position: relative;
|
||||
animation: fadeIn 0.2s;
|
||||
}
|
||||
.user-bubble {
|
||||
background: #6a82fb;
|
||||
color: #fff;
|
||||
align-self: flex-end;
|
||||
float: right;
|
||||
margin-left: 20%;
|
||||
}
|
||||
.ai-bubble {
|
||||
background: #fff;
|
||||
color: #222;
|
||||
border: 1px solid #e0e0e0;
|
||||
align-self: flex-start;
|
||||
float: left;
|
||||
margin-right: 20%;
|
||||
}
|
||||
.typing {
|
||||
opacity: 0.7;
|
||||
font-style: italic;
|
||||
}
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 2px;
|
||||
background: #6a82fb;
|
||||
border-radius: 50%;
|
||||
animation: blink 1.4s infinite both;
|
||||
}
|
||||
.dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes blink {
|
||||
0%, 80%, 100% { opacity: 0.2; }
|
||||
40% { opacity: 1; }
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function renderUpload() {
|
||||
const uploadSection = document.getElementById('upload-section');
|
||||
if (!uploadSection) return;
|
||||
uploadSection.innerHTML = `
|
||||
<h2>📤 Upload Study Materials 🐾</h2>
|
||||
<input type="file" id="file-upload" multiple accept=".pdf,.doc,.docx,.ppt,.pptx,.txt">
|
||||
<button id="upload-btn">Upload</button>
|
||||
<div id="upload-status"></div>
|
||||
`;
|
||||
document.getElementById('upload-btn').addEventListener('click', async () => {
|
||||
const fileInput = document.getElementById('file-upload');
|
||||
const statusDiv = document.getElementById('upload-status');
|
||||
if (!fileInput.files.length) {
|
||||
statusDiv.textContent = 'Please select at least one file.';
|
||||
return;
|
||||
}
|
||||
const file = fileInput.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
statusDiv.textContent = 'Uploading...';
|
||||
try {
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
statusDiv.textContent = 'File uploaded! Getting summary...';
|
||||
// Call Flowise summarization endpoint
|
||||
const summaryRes = await fetch('/api/flowise', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ filename: result.filename })
|
||||
});
|
||||
const summaryData = await summaryRes.json();
|
||||
document.getElementById('summary-output').textContent = summaryData.summary || 'No summary available.';
|
||||
} else {
|
||||
statusDiv.textContent = result.error || 'Upload failed.';
|
||||
}
|
||||
} catch (err) {
|
||||
statusDiv.textContent = 'Error uploading file.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderSummary() {
|
||||
const summarySection = document.getElementById('summary-section');
|
||||
if (!summarySection) return;
|
||||
summarySection.innerHTML = `
|
||||
<h2>📝 AI Summary 🐱</h2>
|
||||
<div id="summary-output">Upload your notes to see a summary here.</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderQuiz() {
|
||||
const quizSection = document.getElementById('quiz-section');
|
||||
if (!quizSection) return;
|
||||
// Subjects and questions
|
||||
const subjects = {
|
||||
Math: [
|
||||
{
|
||||
q: 'What is the value of π (pi) rounded to 2 decimal places?',
|
||||
opts: ['A. 3.12', 'B. 3.14', 'C. 3.16', 'D. 3.18'],
|
||||
ans: 'B. 3.14'
|
||||
},
|
||||
{
|
||||
q: 'Solve: 5 × 6 = ?',
|
||||
opts: ['A. 11', 'B. 30', 'C. 56', 'D. 26'],
|
||||
ans: 'B. 30'
|
||||
}
|
||||
],
|
||||
Science: [
|
||||
{
|
||||
q: 'What planet is known as the Red Planet?',
|
||||
opts: ['A. Venus', 'B. Mars', 'C. Jupiter', 'D. Saturn'],
|
||||
ans: 'B. Mars'
|
||||
},
|
||||
{
|
||||
q: 'What is H2O commonly known as?',
|
||||
opts: ['A. Oxygen', 'B. Hydrogen', 'C. Water', 'D. Helium'],
|
||||
ans: 'C. Water'
|
||||
}
|
||||
],
|
||||
History: [
|
||||
{
|
||||
q: 'Who was the first President of the United States?',
|
||||
opts: ['A. Abraham Lincoln', 'B. George Washington', 'C. John Adams', 'D. Thomas Jefferson'],
|
||||
ans: 'B. George Washington'
|
||||
},
|
||||
{
|
||||
q: 'In which year did World War II end?',
|
||||
opts: ['A. 1942', 'B. 1945', 'C. 1948', 'D. 1950'],
|
||||
ans: 'B. 1945'
|
||||
}
|
||||
]
|
||||
};
|
||||
let selectedSubject = Object.keys(subjects)[0];
|
||||
let currentQ = 0;
|
||||
quizSection.innerHTML = `
|
||||
<h2>Practice Quiz 🐾</h2>
|
||||
<div style="margin-bottom:1em;">
|
||||
<label for="subject-select"><b>Subject:</b></label>
|
||||
<select id="subject-select">
|
||||
${Object.keys(subjects).map(s => `<option value="${s}">${s}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="quiz-card enhanced-quiz-card">
|
||||
<div class="quiz-q"></div>
|
||||
<div class="quiz-options"></div>
|
||||
<div class="quiz-feedback"></div>
|
||||
<div class="quiz-progress"></div>
|
||||
<div style="margin-top:1em;display:flex;justify-content:space-between;gap:0.5em;">
|
||||
<button id="prev-q">Previous</button>
|
||||
<button id="next-q">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const subjectSelect = document.getElementById('subject-select');
|
||||
const quizQ = quizSection.querySelector('.quiz-q');
|
||||
const quizOpts = quizSection.querySelector('.quiz-options');
|
||||
const quizFeedback = quizSection.querySelector('.quiz-feedback');
|
||||
const quizProgress = quizSection.querySelector('.quiz-progress');
|
||||
const prevBtn = document.getElementById('prev-q');
|
||||
const nextBtn = document.getElementById('next-q');
|
||||
|
||||
function renderQuestion() {
|
||||
const qArr = subjects[selectedSubject];
|
||||
const qObj = qArr[currentQ];
|
||||
quizQ.innerHTML = `<span class='quiz-q-num'>Q${currentQ + 1} of ${qArr.length}</span> ${qObj.q}`;
|
||||
quizOpts.innerHTML = qObj.opts.map(opt => `<button class="quiz-opt">${opt}</button>`).join('');
|
||||
quizFeedback.textContent = '';
|
||||
quizProgress.innerHTML = `<div class='quiz-bar-bg'><div class='quiz-bar-fg' style='width:${((currentQ+1)/qArr.length)*100}%'></div></div>`;
|
||||
prevBtn.style.visibility = currentQ === 0 ? 'hidden' : 'visible';
|
||||
nextBtn.style.display = currentQ === qArr.length - 1 ? 'none' : 'inline-block';
|
||||
}
|
||||
renderQuestion();
|
||||
|
||||
quizOpts.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('quiz-opt')) {
|
||||
const qObj = subjects[selectedSubject][currentQ];
|
||||
if (e.target.textContent === qObj.ans) {
|
||||
quizFeedback.textContent = '✅ Correct!';
|
||||
quizFeedback.style.color = '#2ecc40';
|
||||
} else {
|
||||
quizFeedback.textContent = '❌ Try again!';
|
||||
quizFeedback.style.color = '#fc5c7d';
|
||||
}
|
||||
}
|
||||
});
|
||||
subjectSelect.addEventListener('change', function() {
|
||||
selectedSubject = this.value;
|
||||
currentQ = 0;
|
||||
renderQuestion();
|
||||
});
|
||||
prevBtn.addEventListener('click', function() {
|
||||
if (currentQ > 0) {
|
||||
currentQ--;
|
||||
renderQuestion();
|
||||
}
|
||||
});
|
||||
nextBtn.addEventListener('click', function() {
|
||||
if (currentQ < subjects[selectedSubject].length - 1) {
|
||||
currentQ++;
|
||||
renderQuestion();
|
||||
}
|
||||
});
|
||||
|
||||
// Add/Update styles for enhanced quiz UI
|
||||
const quizStyle = document.createElement('style');
|
||||
quizStyle.innerHTML = `
|
||||
.enhanced-quiz-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 16px rgba(106,130,251,0.10);
|
||||
padding: 1.5rem 1.2rem 1.2rem 1.2rem;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.quiz-q {
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.1rem;
|
||||
font-size: 1.15rem;
|
||||
color: #6a82fb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7em;
|
||||
}
|
||||
.quiz-q-num {
|
||||
background: #f0f4fa;
|
||||
color: #6a82fb;
|
||||
border-radius: 8px;
|
||||
padding: 0.2em 0.7em;
|
||||
font-size: 0.95em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.quiz-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.7em;
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
.quiz-opt {
|
||||
background: #f7f9fb;
|
||||
border: 2px solid #6a82fb;
|
||||
color: #6a82fb;
|
||||
border-radius: 8px;
|
||||
padding: 0.7em 1.2em;
|
||||
font-size: 1.05em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s, border 0.2s;
|
||||
text-align: left;
|
||||
}
|
||||
.quiz-opt:hover {
|
||||
background: #6a82fb;
|
||||
color: #fff;
|
||||
border: 2px solid #fc5c7d;
|
||||
}
|
||||
.quiz-feedback {
|
||||
font-size: 1.1em;
|
||||
margin-top: 0.5em;
|
||||
min-height: 1.5em;
|
||||
}
|
||||
.quiz-bar-bg {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #e0e7fa;
|
||||
border-radius: 5px;
|
||||
margin: 0.7em 0 0.2em 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.quiz-bar-fg {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
|
||||
border-radius: 5px;
|
||||
transition: width 0.4s;
|
||||
}
|
||||
#prev-q, #next-q {
|
||||
background: #6a82fb;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.5em 1.2em;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
#prev-q[style*="hidden"] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
#next-q {
|
||||
background: #fc5c7d;
|
||||
}
|
||||
#next-q[style*="none"] {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(quizStyle);
|
||||
}
|
||||
|
||||
// --- INITIALIZE APP ---
|
||||
renderPage('main');
|
||||
|
||||
// Placeholder for AI summary, quiz, reminders, and progress dashboard
|
||||
// TODO: Connect to backend APIs (Perplexity, Gemini Dev, Telegram/Discord bots, etc.)
|
||||
|
||||
Reference in New Issue
Block a user