Files
CatGPT/js/app.js
2025-06-05 10:12:52 +08:00

516 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 &copy; 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, youre 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.)