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