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