| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Admin Dashboard - SASTRA Chatbot</title> |
| | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> |
| | <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> |
| | </head> |
| | <body> |
| | |
| | <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
| | <div class="container"> |
| | <a class="navbar-brand" href="/"> |
| | <i class="fas fa-university me-2"></i>SASTRA Admin |
| | </a> |
| | <div class="navbar-nav ms-auto"> |
| | <a href="/" class="nav-link"> |
| | <i class="fas fa-robot me-1"></i>Chat |
| | </a> |
| | <a href="/logout" class="nav-link"> |
| | <i class="fas fa-sign-out-alt me-1"></i>Logout |
| | </a> |
| | </div> |
| | </div> |
| | </nav> |
| |
|
| | <div class="container mt-4"> |
| | <div class="row"> |
| | <div class="col-md-12"> |
| | <h2 class="mb-4"> |
| | <i class="fas fa-tachometer-alt me-2"></i>Admin Dashboard |
| | </h2> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="row mb-4"> |
| | <div class="col-md-12"> |
| | <div class="card"> |
| | <div class="card-header bg-warning text-dark"> |
| | <h5 class="mb-0"> |
| | <i class="fas fa-retweet me-2"></i>Model Retraining |
| | </h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="alert alert-info"> |
| | <i class="fas fa-info-circle me-2"></i> |
| | Upload a new Excel file with keyword-response pairs to update the chatbot's knowledge. |
| | </div> |
| | |
| | <form id="retrainForm"> |
| | <div class="mb-3"> |
| | <label for="trainingFile" class="form-label">Upload Training Data (Excel)</label> |
| | <input class="form-control" type="file" id="trainingFile" accept=".xlsx"> |
| | <div class="form-text"> |
| | Excel file should have columns: "keyword" and "response" |
| | </div> |
| | </div> |
| | |
| | <div class="d-flex gap-2"> |
| | <button type="submit" class="btn btn-warning" id="retrainBtn"> |
| | <i class="fas fa-sync me-2"></i>Retrain Model |
| | </button> |
| | <a href="/api/download_logs" class="btn btn-outline-primary"> |
| | <i class="fas fa-download me-2"></i>Download Logs |
| | </a> |
| | </div> |
| | </form> |
| | |
| | <div id="retrainResult" class="mt-3"></div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="row"> |
| | <div class="col-md-12"> |
| | <div class="card"> |
| | <div class="card-header bg-success text-white"> |
| | <h5 class="mb-0"> |
| | <i class="fas fa-chart-bar me-2"></i>Analytics Dashboard |
| | </h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="d-flex justify-content-between mb-3"> |
| | <button class="btn btn-outline-success" onclick="refreshAnalytics()"> |
| | <i class="fas fa-redo me-2"></i>Refresh Analytics |
| | </button> |
| | <span class="text-muted">Last updated: <span id="lastUpdate">Never</span></span> |
| | </div> |
| | |
| | |
| | <div class="row" id="analyticsCards"> |
| | <div class="col-md-3 mb-3"> |
| | <div class="card text-center h-100"> |
| | <div class="card-body"> |
| | <h1 class="display-4 text-primary" id="totalQueries">0</h1> |
| | <p class="card-text">Total Queries</p> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="col-md-9 mb-3"> |
| | <div class="card h-100"> |
| | <div class="card-header"> |
| | <h6 class="mb-0">Language Distribution</h6> |
| | </div> |
| | <div class="card-body"> |
| | <div class="table-responsive"> |
| | <table class="table table-sm" id="langTable"> |
| | <tbody> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | |
| | <div class="row mt-3"> |
| | <div class="col-md-6 mb-3"> |
| | <div class="card h-100"> |
| | <div class="card-header"> |
| | <h6 class="mb-0">Response Types</h6> |
| | </div> |
| | <div class="card-body"> |
| | <div class="table-responsive"> |
| | <table class="table table-sm" id="responseTypeTable"> |
| | <tbody> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="col-md-6 mb-3"> |
| | <div class="card h-100"> |
| | <div class="card-header"> |
| | <h6 class="mb-0">Recent Questions</h6> |
| | </div> |
| | <div class="card-body"> |
| | <ul class="list-group list-group-flush" id="topQuestions"> |
| | |
| | </ul> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> |
| | |
| | <script> |
| | document.getElementById('retrainForm').addEventListener('submit', async function(e) { |
| | e.preventDefault(); |
| | |
| | const fileInput = document.getElementById('trainingFile'); |
| | const retrainBtn = document.getElementById('retrainBtn'); |
| | const resultDiv = document.getElementById('retrainResult'); |
| | |
| | if (!fileInput.files.length) { |
| | resultDiv.innerHTML = ` |
| | <div class="alert alert-warning"> |
| | <i class="fas fa-exclamation-triangle me-2"></i> |
| | Please select an Excel file to upload. |
| | </div> |
| | `; |
| | return; |
| | } |
| | |
| | const formData = new FormData(); |
| | formData.append('file', fileInput.files[0]); |
| | |
| | retrainBtn.disabled = true; |
| | retrainBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Retraining...'; |
| | |
| | try { |
| | const response = await fetch('/api/retrain', { |
| | method: 'POST', |
| | body: formData |
| | }); |
| | |
| | const data = await response.json(); |
| | |
| | if (response.ok) { |
| | resultDiv.innerHTML = ` |
| | <div class="alert alert-success"> |
| | <i class="fas fa-check-circle me-2"></i> |
| | ${data.message} |
| | </div> |
| | `; |
| | |
| | refreshAnalytics(); |
| | } else { |
| | resultDiv.innerHTML = ` |
| | <div class="alert alert-danger"> |
| | <i class="fas fa-times-circle me-2"></i> |
| | Error: ${data.error || 'Failed to retrain'} |
| | </div> |
| | `; |
| | } |
| | } catch (error) { |
| | resultDiv.innerHTML = ` |
| | <div class="alert alert-danger"> |
| | <i class="fas fa-times-circle me-2"></i> |
| | Network error: ${error.message} |
| | </div> |
| | `; |
| | } finally { |
| | retrainBtn.disabled = false; |
| | retrainBtn.innerHTML = '<i class="fas fa-sync me-2"></i>Retrain Model'; |
| | } |
| | }); |
| | |
| | async function refreshAnalytics() { |
| | try { |
| | const response = await fetch('/api/analytics'); |
| | const data = await response.json(); |
| | |
| | if (response.ok) { |
| | |
| | const now = new Date(); |
| | document.getElementById('lastUpdate').textContent = now.toLocaleTimeString(); |
| | |
| | |
| | document.getElementById('totalQueries').textContent = data.total_queries || 0; |
| | |
| | |
| | const langTable = document.getElementById('langTable'); |
| | langTable.innerHTML = ''; |
| | |
| | if (data.language_distribution) { |
| | for (const [lang, count] of Object.entries(data.language_distribution)) { |
| | const row = document.createElement('tr'); |
| | row.innerHTML = ` |
| | <td>${getLanguageName(lang)}</td> |
| | <td>${count}</td> |
| | <td> |
| | <div class="progress" style="height: 10px;"> |
| | <div class="progress-bar" style="width: ${(count / data.total_queries * 100) || 0}%"></div> |
| | </div> |
| | </td> |
| | `; |
| | langTable.appendChild(row); |
| | } |
| | } |
| | |
| | |
| | const responseTable = document.getElementById('responseTypeTable'); |
| | responseTable.innerHTML = ''; |
| | |
| | if (data.response_types) { |
| | for (const [type, count] of Object.entries(data.response_types)) { |
| | const row = document.createElement('tr'); |
| | const badgeColor = getResponseTypeColor(type); |
| | row.innerHTML = ` |
| | <td><span class="badge ${badgeColor}">${type.toUpperCase()}</span></td> |
| | <td>${count}</td> |
| | `; |
| | responseTable.appendChild(row); |
| | } |
| | } |
| | |
| | |
| | const topQuestions = document.getElementById('topQuestions'); |
| | topQuestions.innerHTML = ''; |
| | |
| | if (data.top_questions && data.top_questions.length > 0) { |
| | data.top_questions.forEach(question => { |
| | const li = document.createElement('li'); |
| | li.className = 'list-group-item'; |
| | li.textContent = question.length > 50 ? question.substring(0, 50) + '...' : question; |
| | topQuestions.appendChild(li); |
| | }); |
| | } |
| | } |
| | } catch (error) { |
| | console.error('Error refreshing analytics:', error); |
| | } |
| | } |
| | |
| | function getLanguageName(code) { |
| | const languages = { |
| | 'en': 'English', |
| | 'ta': 'Tamil', |
| | 'te': 'Telugu', |
| | 'kn': 'Kannada', |
| | 'hi': 'Hindi', |
| | 'unknown': 'Unknown' |
| | }; |
| | return languages[code] || code; |
| | } |
| | |
| | function getResponseTypeColor(type) { |
| | const colors = { |
| | 'keyword': 'bg-warning', |
| | 'rag': 'bg-info', |
| | 'llm': 'bg-primary', |
| | 'error': 'bg-danger' |
| | }; |
| | return colors[type] || 'bg-secondary'; |
| | } |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', refreshAnalytics); |
| | </script> |
| | </body> |
| | </html> |