Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ThutoAI - Your Student Assistant</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| } | |
| .app-container { | |
| display: flex; | |
| width: 100%; | |
| min-height: 100vh; | |
| } | |
| /* Sidebar */ | |
| .sidebar { | |
| width: 280px; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| box-shadow: 2px 0 20px rgba(0,0,0,0.1); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .sidebar-header { | |
| padding: 30px 20px; | |
| text-align: center; | |
| border-bottom: 1px solid rgba(0,0,0,0.1); | |
| } | |
| .sidebar-header h1 { | |
| color: #333; | |
| font-size: 28px; | |
| margin-bottom: 5px; | |
| } | |
| .sidebar-header p { | |
| color: #666; | |
| font-size: 14px; | |
| } | |
| .sidebar-nav { | |
| flex: 1; | |
| padding: 20px 0; | |
| } | |
| .nav-item { | |
| padding: 15px 25px; | |
| margin: 5px 15px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| color: #555; | |
| font-weight: 500; | |
| } | |
| .nav-item:hover { | |
| background: rgba(102, 126, 234, 0.1); | |
| color: #667eea; | |
| } | |
| .nav-item.active { | |
| background: #667eea; | |
| color: white; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
| } | |
| .nav-item span { | |
| font-size: 16px; | |
| } | |
| .sidebar-footer { | |
| padding: 20px; | |
| border-top: 1px solid rgba(0,0,0,0.1); | |
| text-align: center; | |
| } | |
| .admin-link { | |
| color: #667eea; | |
| text-decoration: none; | |
| font-size: 14px; | |
| padding: 8px 16px; | |
| border: 1px solid #667eea; | |
| border-radius: 20px; | |
| transition: all 0.3s ease; | |
| } | |
| .admin-link:hover { | |
| background: #667eea; | |
| color: white; | |
| } | |
| /* Main Content */ | |
| .main-content { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| } | |
| .content-header { | |
| padding: 30px 40px; | |
| border-bottom: 1px solid rgba(0,0,0,0.1); | |
| background: rgba(255, 255, 255, 0.8); | |
| } | |
| .content-header h2 { | |
| color: #333; | |
| font-size: 32px; | |
| margin-bottom: 8px; | |
| } | |
| .content-header p { | |
| color: #666; | |
| font-size: 16px; | |
| } | |
| .content-body { | |
| flex: 1; | |
| padding: 40px; | |
| overflow-y: auto; | |
| } | |
| /* Views */ | |
| .view { | |
| display: block; | |
| } | |
| .view.hidden { | |
| display: none; | |
| } | |
| /* Dashboard Cards */ | |
| .dashboard-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 25px; | |
| margin-bottom: 30px; | |
| } | |
| .dashboard-card { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 25px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.1); | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| } | |
| .dashboard-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.15); | |
| } | |
| .card-header { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .card-icon { | |
| font-size: 24px; | |
| margin-right: 12px; | |
| } | |
| .card-title { | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: #333; | |
| } | |
| .card-content { | |
| color: #666; | |
| } | |
| .card-item { | |
| padding: 12px 0; | |
| border-bottom: 1px solid #f0f0f0; | |
| } | |
| .card-item:last-child { | |
| border-bottom: none; | |
| } | |
| .item-title { | |
| font-weight: 600; | |
| color: #333; | |
| margin-bottom: 4px; | |
| } | |
| .item-details { | |
| font-size: 14px; | |
| color: #666; | |
| } | |
| /* Chat Interface */ | |
| .chat-container { | |
| display: flex; | |
| flex-direction: column; | |
| height: 600px; | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| } | |
| .chat-messages { | |
| flex: 1; | |
| padding: 25px; | |
| overflow-y: auto; | |
| background: #f8f9fa; | |
| } | |
| .welcome-message { | |
| text-align: center; | |
| padding: 40px 20px; | |
| background: white; | |
| border-radius: 15px; | |
| margin-bottom: 20px; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.05); | |
| } | |
| .welcome-message h3 { | |
| color: #333; | |
| margin-bottom: 15px; | |
| font-size: 24px; | |
| } | |
| .welcome-message p { | |
| color: #666; | |
| margin-bottom: 10px; | |
| } | |
| .message { | |
| margin-bottom: 20px; | |
| display: flex; | |
| align-items: flex-start; | |
| } | |
| .message.user { | |
| justify-content: flex-end; | |
| } | |
| .message-content { | |
| max-width: 70%; | |
| padding: 15px 20px; | |
| border-radius: 20px; | |
| word-wrap: break-word; | |
| } | |
| .message.user .message-content { | |
| background: #667eea; | |
| color: white; | |
| border-bottom-right-radius: 5px; | |
| } | |
| .message.assistant .message-content { | |
| background: white; | |
| color: #333; | |
| border-bottom-left-radius: 5px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| .chat-input-container { | |
| padding: 25px; | |
| background: white; | |
| border-top: 1px solid #e0e0e0; | |
| } | |
| .chat-input-form { | |
| display: flex; | |
| gap: 15px; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| padding: 15px 20px; | |
| border: 2px solid #e0e0e0; | |
| border-radius: 25px; | |
| font-size: 16px; | |
| outline: none; | |
| transition: border-color 0.3s ease; | |
| } | |
| .chat-input:focus { | |
| border-color: #667eea; | |
| } | |
| .send-button { | |
| padding: 15px 25px; | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 600; | |
| transition: background 0.3s ease; | |
| } | |
| .send-button:hover { | |
| background: #5a6fd8; | |
| } | |
| .send-button:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| /* Loading Animation */ | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #667eea; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .app-container { | |
| flex-direction: column; | |
| } | |
| .sidebar { | |
| width: 100%; | |
| height: auto; | |
| } | |
| .sidebar-nav { | |
| display: flex; | |
| overflow-x: auto; | |
| padding: 10px 0; | |
| } | |
| .nav-item { | |
| white-space: nowrap; | |
| min-width: 120px; | |
| margin: 0 5px; | |
| } | |
| .content-body { | |
| padding: 20px; | |
| } | |
| .dashboard-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Sidebar --> | |
| <div class="sidebar"> | |
| <div class="sidebar-header"> | |
| <h1>π ThutoAI</h1> | |
| <p>Your Student Assistant</p> | |
| </div> | |
| <div class="sidebar-nav"> | |
| <div class="nav-item active" data-view="dashboard"> | |
| <span>π Dashboard</span> | |
| </div> | |
| <div class="nav-item" data-view="chat"> | |
| <span>π¬ AI Chat</span> | |
| </div> | |
| <div class="nav-item" data-view="announcements"> | |
| <span>π’ Announcements</span> | |
| </div> | |
| <div class="nav-item" data-view="exams"> | |
| <span>π Exams</span> | |
| </div> | |
| <div class="nav-item" data-view="grades"> | |
| <span>π Grades</span> | |
| </div> | |
| </div> | |
| <div class="sidebar-footer"> | |
| <a href="/admin/login" class="admin-link">π Admin Panel</a> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="main-content"> | |
| <!-- Dashboard View --> | |
| <div id="dashboard-view" class="view"> | |
| <div class="content-header"> | |
| <h2>Student Dashboard</h2> | |
| <p>Your academic overview and important updates</p> | |
| </div> | |
| <div class="content-body"> | |
| <div class="dashboard-grid" id="dashboardGrid"> | |
| <div class="loading">Loading your dashboard...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat View --> | |
| <div id="chat-view" class="view hidden"> | |
| <div class="content-header"> | |
| <h2>AI Chat Assistant</h2> | |
| <p>Ask me anything about your studies, school, or get academic help</p> | |
| </div> | |
| <div class="content-body"> | |
| <div class="chat-container"> | |
| <div class="chat-messages" id="chatMessages"> | |
| <div class="welcome-message"> | |
| <h3>π Hello! I'm your ThutoAI Assistant</h3> | |
| <p>I can help you with:</p> | |
| <ul style="text-align: left; display: inline-block; margin-top: 10px;"> | |
| <li>π Study questions and academic help</li> | |
| <li>π Information about exams and assignments</li> | |
| <li>π Your grades and academic performance</li> | |
| <li>π’ School announcements and events</li> | |
| <li>π‘ Study tips and motivation</li> | |
| </ul> | |
| <p style="margin-top: 15px;">What would you like to know?</p> | |
| </div> | |
| </div> | |
| <div class="chat-input-container"> | |
| <form class="chat-input-form" id="chatForm"> | |
| <input type="text" class="chat-input" id="chatInput" placeholder="Ask me anything about your studies..." required> | |
| <button type="submit" class="send-button" id="sendButton">Send</button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Announcements View --> | |
| <div id="announcements-view" class="view hidden"> | |
| <div class="content-header"> | |
| <h2>School Announcements</h2> | |
| <p>Latest news and updates from your school</p> | |
| </div> | |
| <div class="content-body"> | |
| <div class="dashboard-grid" id="announcementsGrid"> | |
| <div class="loading">Loading announcements...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Exams View --> | |
| <div id="exams-view" class="view hidden"> | |
| <div class="content-header"> | |
| <h2>Upcoming Exams</h2> | |
| <p>Your examination schedule and important details</p> | |
| </div> | |
| <div class="content-body"> | |
| <div class="dashboard-grid" id="examsGrid"> | |
| <div class="loading">Loading exams...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Grades View --> | |
| <div id="grades-view" class="view hidden"> | |
| <div class="content-header"> | |
| <h2>Your Grades</h2> | |
| <p>Academic performance and assessment results</p> | |
| </div> | |
| <div class="content-body"> | |
| <div class="dashboard-grid" id="gradesGrid"> | |
| <div class="loading">Loading grades...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let currentView = 'dashboard'; | |
| // DOM elements | |
| const navItems = document.querySelectorAll('.nav-item'); | |
| const views = document.querySelectorAll('.view'); | |
| const chatForm = document.getElementById('chatForm'); | |
| const chatInput = document.getElementById('chatInput'); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| const sendButton = document.getElementById('sendButton'); | |
| // Initialize app | |
| document.addEventListener('DOMContentLoaded', () => { | |
| setupNavigation(); | |
| loadDashboard(); | |
| }); | |
| // Navigation setup | |
| function setupNavigation() { | |
| navItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| const viewName = item.dataset.view; | |
| switchView(viewName); | |
| }); | |
| }); | |
| } | |
| // Switch between views | |
| function switchView(viewName) { | |
| // Update navigation | |
| navItems.forEach(item => { | |
| item.classList.toggle('active', item.dataset.view === viewName); | |
| }); | |
| // Update views | |
| views.forEach(view => { | |
| view.classList.toggle('hidden', !view.id.startsWith(viewName)); | |
| }); | |
| currentView = viewName; | |
| // Load view-specific data | |
| switch(viewName) { | |
| case 'dashboard': | |
| loadDashboard(); | |
| break; | |
| case 'announcements': | |
| loadAnnouncements(); | |
| break; | |
| case 'exams': | |
| loadExams(); | |
| break; | |
| case 'grades': | |
| loadGrades(); | |
| break; | |
| } | |
| } | |
| // Load dashboard data | |
| async function loadDashboard() { | |
| try { | |
| const response = await fetch('/dashboard'); | |
| const data = await response.json(); | |
| if (data.success) { | |
| renderDashboard(data.data); | |
| } else { | |
| console.error('Dashboard error:', data.error); | |
| } | |
| } catch (error) { | |
| console.error('Dashboard fetch error:', error); | |
| } | |
| } | |
| // Render dashboard | |
| function renderDashboard(data) { | |
| const grid = document.getElementById('dashboardGrid'); | |
| grid.innerHTML = ''; | |
| // Recent announcements card | |
| if (data.announcements && data.announcements.length > 0) { | |
| grid.appendChild(createDashboardCard('π’', 'Recent Announcements', data.announcements, 'announcements')); | |
| } | |
| // Upcoming exams card | |
| if (data.upcoming_exams && data.upcoming_exams.length > 0) { | |
| grid.appendChild(createDashboardCard('π', 'Upcoming Exams', data.upcoming_exams, 'exams')); | |
| } | |
| // Upcoming tests card | |
| if (data.upcoming_tests && data.upcoming_tests.length > 0) { | |
| grid.appendChild(createDashboardCard('π', 'Upcoming Tests', data.upcoming_tests, 'tests')); | |
| } | |
| // Recent grades card | |
| if (data.recent_grades && data.recent_grades.length > 0) { | |
| grid.appendChild(createDashboardCard('π', 'Recent Grades', data.recent_grades, 'grades')); | |
| } | |
| // Upcoming events card | |
| if (data.upcoming_events && data.upcoming_events.length > 0) { | |
| grid.appendChild(createDashboardCard('π', 'Upcoming Events', data.upcoming_events, 'events')); | |
| } | |
| if (grid.children.length === 0) { | |
| grid.innerHTML = '<div class="welcome-message"><h3>Welcome to ThutoAI!</h3><p>Your dashboard will show information as it becomes available.</p></div>'; | |
| } | |
| } | |
| function createDashboardCard(icon, title, items, type) { | |
| const card = document.createElement('div'); | |
| card.className = 'dashboard-card'; | |
| const header = document.createElement('div'); | |
| header.className = 'card-header'; | |
| header.innerHTML = ` | |
| <span class="card-icon">${icon}</span> | |
| <span class="card-title">${title}</span> | |
| `; | |
| const content = document.createElement('div'); | |
| content.className = 'card-content'; | |
| items.slice(0, 3).forEach(item => { | |
| const itemDiv = document.createElement('div'); | |
| itemDiv.className = 'card-item'; | |
| let itemContent = ''; | |
| switch(type) { | |
| case 'announcements': | |
| itemContent = ` | |
| <div class="item-title">${item.title}</div> | |
| <div class="item-details">${item.content.substring(0, 100)}...</div> | |
| `; | |
| break; | |
| case 'exams': | |
| itemContent = ` | |
| <div class="item-title">${item.name}</div> | |
| <div class="item-details">${item.subject} - ${item.date} at ${item.time}</div> | |
| `; | |
| break; | |
| case 'tests': | |
| itemContent = ` | |
| <div class="item-title">${item.title}</div> | |
| <div class="item-details">${item.subject} - Due: ${item.due_date}</div> | |
| `; | |
| break; | |
| case 'grades': | |
| itemContent = ` | |
| <div class="item-title">${item.subject}</div> | |
| <div class="item-details">${item.percentage}% (${item.grade}) - ${item.name}</div> | |
| `; | |
| break; | |
| case 'events': | |
| itemContent = ` | |
| <div class="item-title">${item.name}</div> | |
| <div class="item-details">${item.date} - ${item.location}</div> | |
| `; | |
| break; | |
| } | |
| itemDiv.innerHTML = itemContent; | |
| content.appendChild(itemDiv); | |
| }); | |
| card.appendChild(header); | |
| card.appendChild(content); | |
| return card; | |
| } | |
| // Load announcements | |
| async function loadAnnouncements() { | |
| try { | |
| const response = await fetch('/announcements'); | |
| const data = await response.json(); | |
| if (data.success) { | |
| renderAnnouncements(data.announcements); | |
| } | |
| } catch (error) { | |
| console.error('Announcements fetch error:', error); | |
| } | |
| } | |
| function renderAnnouncements(announcements) { | |
| const grid = document.getElementById('announcementsGrid'); | |
| grid.innerHTML = ''; | |
| if (announcements.length === 0) { | |
| grid.innerHTML = '<div class="welcome-message"><h3>No Announcements</h3><p>No announcements available at the moment.</p></div>'; | |
| return; | |
| } | |
| announcements.forEach(ann => { | |
| const card = document.createElement('div'); | |
| card.className = 'dashboard-card'; | |
| card.innerHTML = ` | |
| <div class="card-header"> | |
| <span class="card-icon">π’</span> | |
| <span class="card-title">${ann.title}</span> | |
| </div> | |
| <div class="card-content"> | |
| <p>${ann.content}</p> | |
| <div class="item-details" style="margin-top: 10px;"> | |
| Priority: ${ann.priority} | Date: ${new Date(ann.date).toLocaleDateString()} | |
| </div> | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| // Load exams | |
| async function loadExams() { | |
| try { | |
| const response = await fetch('/exams'); | |
| const data = await response.json(); | |
| if (data.success) { | |
| renderExams(data.exams); | |
| } | |
| } catch (error) { | |
| console.error('Exams fetch error:', error); | |
| } | |
| } | |
| function renderExams(exams) { | |
| const grid = document.getElementById('examsGrid'); | |
| grid.innerHTML = ''; | |
| if (exams.length === 0) { | |
| grid.innerHTML = '<div class="welcome-message"><h3>No Upcoming Exams</h3><p>No exams scheduled at the moment.</p></div>'; | |
| return; | |
| } | |
| exams.forEach(exam => { | |
| const card = document.createElement('div'); | |
| card.className = 'dashboard-card'; | |
| card.innerHTML = ` | |
| <div class="card-header"> | |
| <span class="card-icon">π</span> | |
| <span class="card-title">${exam.name}</span> | |
| </div> | |
| <div class="card-content"> | |
| <div class="card-item"> | |
| <div class="item-title">Subject: ${exam.subject}</div> | |
| </div> | |
| <div class="card-item"> | |
| <div class="item-title">Date & Time: ${exam.date} at ${exam.time}</div> | |
| </div> | |
| <div class="card-item"> | |
| <div class="item-title">Location: ${exam.location}</div> | |
| </div> | |
| ${exam.instructions ? `<div class="card-item"><div class="item-details">${exam.instructions}</div></div>` : ''} | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| // Load grades | |
| async function loadGrades() { | |
| try { | |
| const response = await fetch('/grades'); | |
| const data = await response.json(); | |
| if (data.success) { | |
| renderGrades(data.grades); | |
| } | |
| } catch (error) { | |
| console.error('Grades fetch error:', error); | |
| } | |
| } | |
| function renderGrades(grades) { | |
| const grid = document.getElementById('gradesGrid'); | |
| grid.innerHTML = ''; | |
| if (grades.length === 0) { | |
| grid.innerHTML = '<div class="welcome-message"><h3>No Grades Available</h3><p>No grades recorded yet.</p></div>'; | |
| return; | |
| } | |
| grades.forEach(grade => { | |
| const card = document.createElement('div'); | |
| card.className = 'dashboard-card'; | |
| card.innerHTML = ` | |
| <div class="card-header"> | |
| <span class="card-icon">π</span> | |
| <span class="card-title">${grade.subject}</span> | |
| </div> | |
| <div class="card-content"> | |
| <div class="card-item"> | |
| <div class="item-title">${grade.name}</div> | |
| <div class="item-details">${grade.type}</div> | |
| </div> | |
| <div class="card-item"> | |
| <div class="item-title">Score: ${grade.marks_obtained}/${grade.total_marks} (${grade.percentage}%)</div> | |
| <div class="item-details">Grade: ${grade.grade}</div> | |
| </div> | |
| <div class="card-item"> | |
| <div class="item-details">Date: ${grade.date}</div> | |
| </div> | |
| ${grade.comments ? `<div class="card-item"><div class="item-details">Comments: ${grade.comments}</div></div>` : ''} | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| // Chat functionality | |
| if (chatForm) { | |
| chatForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const message = chatInput.value.trim(); | |
| if (!message) return; | |
| // Add user message to chat | |
| addMessage(message, 'user'); | |
| chatInput.value = ''; | |
| sendButton.disabled = true; | |
| sendButton.innerHTML = '<span class="loading"></span>'; | |
| try { | |
| const response = await fetch('/chat', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ message: message }) | |
| }); | |
| const data = await response.json(); | |
| if (data.reply) { | |
| addMessage(data.reply, 'assistant'); | |
| } else { | |
| addMessage('Sorry, I encountered an error. Please try again.', 'assistant'); | |
| } | |
| } catch (error) { | |
| addMessage('Sorry, I could not connect to the server. Please check your connection and try again.', 'assistant'); | |
| } finally { | |
| sendButton.disabled = false; | |
| sendButton.innerHTML = 'Send'; | |
| } | |
| }); | |
| } | |
| function addMessage(content, sender) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `message ${sender}`; | |
| const contentDiv = document.createElement('div'); | |
| contentDiv.className = 'message-content'; | |
| contentDiv.innerHTML = content.replace(/\n/g, '<br>'); | |
| messageDiv.appendChild(contentDiv); | |
| chatMessages.appendChild(messageDiv); | |
| // Scroll to bottom | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| </script> | |
| </body> | |
| </html> |