first commit

This commit is contained in:
inubimambo
2025-06-05 10:12:52 +08:00
parent f5a7111f8e
commit 8060dbb140
8 changed files with 2212 additions and 0 deletions

27
.gitignore vendored
View File

@@ -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

View File

View File

@@ -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;
}

View File

@@ -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 &copy; 2025. All rights reserved.</p>
</footer>
<script src="js/app.js"></script>
</body>
</html>

515
js/app.js
View File

@@ -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 &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.)

1472
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View 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
View 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}`);
});