first commit
This commit is contained in:
27
.gitignore
vendored
27
.gitignore
vendored
@@ -0,0 +1,27 @@
|
|||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
uploads/
|
||||||
|
|
||||||
|
# System
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.cache/
|
||||||
|
coverage/
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
*.tgz
|
||||||
|
*.zip
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.idea/
|
||||||
|
*.iml
|
||||||
|
*.code-workspace
|
||||||
|
|||||||
0
.vscode/settings.json
vendored
0
.vscode/settings.json
vendored
@@ -0,0 +1,59 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Arial, sans-serif;
|
||||||
|
background: #f7f9fb;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 2rem 1rem 1rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.07);
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #6a82fb;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
#upload-section input[type="file"] {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #6a82fb;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #fc5c7d;
|
||||||
|
}
|
||||||
|
#summary-output, #quiz-output, #reminder-output, #progress-output {
|
||||||
|
background: #f0f4fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 60px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f0f4fa;
|
||||||
|
color: #888;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|||||||
43
index.html
43
index.html
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>EduCat: AI Study Buddy</title>
|
||||||
|
<link rel="stylesheet" href="css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>EduCat: AI Study Buddy</h1>
|
||||||
|
<p>Your AI-powered study companion for polytechnic success!</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section id="upload-section">
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
|
<section id="summary-section">
|
||||||
|
<h2>AI Summary</h2>
|
||||||
|
<div id="summary-output">Upload your notes to see a summary here.</div>
|
||||||
|
</section>
|
||||||
|
<section id="quiz-section">
|
||||||
|
<h2>Practice Quiz</h2>
|
||||||
|
<div id="quiz-output">AI-generated quizzes will appear here.</div>
|
||||||
|
</section>
|
||||||
|
<section id="reminder-section">
|
||||||
|
<h2>Study Reminders</h2>
|
||||||
|
<div id="reminder-output">Personalized reminders will show here.</div>
|
||||||
|
</section>
|
||||||
|
<section id="progress-section">
|
||||||
|
<h2>Progress Dashboard</h2>
|
||||||
|
<div id="progress-output">Track your study progress visually!</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>EduCat © 2025. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
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.)
|
||||||
|
|||||||
1472
package-lock.json
generated
Normal file
1472
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "educat-ai-study-buddy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "EduCat: AI Study Buddy - Node.js backend with Flowise integration",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"multer": "^2.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
75
server.js
Normal file
75
server.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const multer = require('multer');
|
||||||
|
const path = require('path');
|
||||||
|
const cors = require('cors');
|
||||||
|
const fs = require('fs');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.static(path.join(__dirname)));
|
||||||
|
|
||||||
|
// Multer setup for file uploads
|
||||||
|
const upload = multer({
|
||||||
|
dest: path.join(__dirname, 'uploads/')
|
||||||
|
});
|
||||||
|
|
||||||
|
// File upload endpoint
|
||||||
|
app.post('/api/upload', upload.single('file'), async (req, res) => {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: 'No file uploaded' });
|
||||||
|
}
|
||||||
|
// TODO: Integrate with Flowise API for summarization/quiz
|
||||||
|
// Example: send file to Flowise and get summary/quiz
|
||||||
|
// const flowiseResponse = await axios.post('FLOWISE_API_URL', ...);
|
||||||
|
res.json({
|
||||||
|
message: 'File uploaded successfully',
|
||||||
|
filename: req.file.filename,
|
||||||
|
originalname: req.file.originalname
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Placeholder endpoint for summarization/quiz (to be implemented)
|
||||||
|
app.post('/api/flowise', async (req, res) => {
|
||||||
|
// Simulate Flowise integration: read uploaded file and return a fake summary
|
||||||
|
const { filename } = req.body;
|
||||||
|
if (!filename) {
|
||||||
|
return res.status(400).json({ error: 'Filename required' });
|
||||||
|
}
|
||||||
|
const filePath = path.join(__dirname, 'uploads', filename);
|
||||||
|
try {
|
||||||
|
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||||
|
// TODO: Replace with actual Flowise API call
|
||||||
|
// const flowiseResponse = await axios.post('FLOWISE_API_URL', { content: fileContent });
|
||||||
|
// return res.json({ summary: flowiseResponse.data.summary });
|
||||||
|
res.json({ summary: `Sample summary for file: ${filename}\n\n${fileContent.substring(0, 200)}...` });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Could not read file for summary.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chatbot endpoint for chitchat with Flowise ChatGPT
|
||||||
|
app.post('/api/chat', async (req, res) => {
|
||||||
|
const { message } = req.body;
|
||||||
|
if (!message) {
|
||||||
|
return res.status(400).json({ error: 'Message required' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const flowiseRes = await axios.post(
|
||||||
|
'https://flowise.suika.cc/api/v1/prediction/5079e3d1-4e79-42f5-9c90-1142d9eede4c',
|
||||||
|
{ question: message },
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
res.json({ reply: flowiseRes.data.text || flowiseRes.data.answer || 'No reply.' });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Failed to get reply from Flowise.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`EduCat backend running on http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user