self-trained2 / static /admin.html
DeepImagix's picture
Rename static/admin_panel.html to static/admin.html
64e36f9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NeuraPrompt AI - Admin Panel</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #111827; /* bg-gray-900 */
color: #d1d5db; /* text-gray-300 */
}
.sidebar { background-color: #1f2937; /* bg-gray-800 */ }
.sidebar-link { @apply flex items-center p-3 my-1 rounded-lg text-gray-300 hover:bg-gray-700 transition-colors; }
.sidebar-link.active { @apply bg-blue-600 text-white; }
.card { @apply bg-gray-800 p-6 rounded-lg shadow-lg border border-gray-700; }
.input-field { @apply w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2.5 focus:ring-blue-500 focus:border-blue-500; }
.btn { @apply px-4 py-2 rounded-lg font-semibold transition-colors; }
.btn-primary { @apply bg-blue-600 text-white hover:bg-blue-700; }
.btn-secondary { @apply bg-gray-600 text-white hover:bg-gray-700; }
.btn-danger { @apply bg-red-600 text-white hover:bg-red-700; }
.btn-success { @apply bg-green-600 text-white hover:bg-green-700; }
.toast {
animation: toast-in 0.5s, toast-out 0.5s 4.5s;
@apply fixed bottom-5 right-5 p-4 rounded-lg shadow-lg text-white;
}
.toast.success { @apply bg-green-600; }
.toast.error { @apply bg-red-600; }
@keyframes toast-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes toast-out { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
</style>
</head>
<body class="flex h-screen">
<!-- Sidebar Navigation -->
<aside class="sidebar w-64 flex-shrink-0 p-4">
<div class="flex items-center gap-3 mb-8">
<ion-icon name="hardware-chip-outline" class="text-3xl text-blue-400"></ion-icon>
<h1 class="text-xl font-bold text-white">NeuraPrompt AI</h1>
</div>
<nav>
<a href="#dashboard" class="sidebar-link active" data-target="dashboard-section">
<ion-icon name="grid-outline" class="mr-3"></ion-icon> Dashboard
</a>
<a href="#image-verification" class="sidebar-link" data-target="image-verification-section">
<ion-icon name="checkbox-outline" class="mr-3"></ion-icon> Image Verification
<span id="pending-count-badge" class="ml-auto bg-yellow-500 text-black text-xs font-bold px-2 py-0.5 rounded-full hidden">0</span>
</a>
<a href="#model-training" class="sidebar-link" data-target="model-training-section">
<ion-icon name="school-outline" class="mr-3"></ion-icon> Model Training
</a>
<a href="#system-management" class="sidebar-link" data-target="system-management-section">
<ion-icon name="server-outline" class="mr-3"></ion-icon> System Management
</a>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 p-8 overflow-y-auto">
<!-- Dashboard Section -->
<section id="dashboard-section" class="content-section">
<h2 class="text-3xl font-bold text-white mb-6">Admin Dashboard</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="card">
<h3 class="text-lg font-semibold text-gray-400">Pending Images</h3>
<p id="pending-images-stat" class="text-4xl font-bold text-white mt-2">0</p>
</div>
<div class="card">
<h3 class="text-lg font-semibold text-gray-400">Text AI Model</h3>
<p class="text-4xl font-bold text-white mt-2">Neurones Self</p>
</div>
<div class="card">
<h3 class="text-lg font-semibold text-gray-400">Image AI Model</h3>
<p class="text-4xl font-bold text-white mt-2">Custom Classifier</p>
</div>
</div>
</section>
<!-- Image Verification Section -->
<section id="image-verification-section" class="content-section hidden">
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-bold text-white">Image Verification Queue</h2>
<button id="refresh-pending-btn" class="btn btn-secondary flex items-center gap-2">
<ion-icon name="refresh-outline"></ion-icon> Refresh
</button>
</div>
<div id="pending-images-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- Pending images will be loaded here -->
<p id="no-pending-message">No images pending verification.</p>
</div>
</section>
<!-- Model Training Section -->
<section id="model-training-section" class="content-section hidden">
<h2 class="text-3xl font-bold text-white mb-6">Model Training</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Manual Text Model Training -->
<div class="card">
<h3 class="text-xl font-semibold mb-4">Manual Text Model Training</h3>
<form id="train-text-form" class="space-y-4">
<div>
<label for="prompt" class="block mb-2 text-sm font-medium">User Prompt</label>
<textarea id="prompt" name="prompt" rows="3" class="input-field" required></textarea>
</div>
<div>
<label for="reply" class="block mb-2 text-sm font-medium">AI Reply</label>
<textarea id="reply" name="reply" rows="3" class="input-field" required></textarea>
</div>
<div>
<label for="model_name_text" class="block mb-2 text-sm font-medium">Model</label>
<select id="model_name_text" name="model_name" class="input-field">
<option value="neurones_self">Neurones Self</option>
</select>
</div>
<button type="submit" class="btn btn-primary w-full">Train Text Model</button>
</form>
</div>
<!-- Custom Image Model Training -->
<div class="card">
<h3 class="text-xl font-semibold mb-4">Custom Image Model Training</h3>
<p class="text-gray-400 mb-4">Trigger a training run using all approved images in the dataset. This can take some time.</p>
<button id="train-image-btn" class="btn btn-primary w-full">Start Image Model Training</button>
</div>
</div>
</section>
<!-- System Management Section -->
<section id="system-management-section" class="content-section hidden">
<h2 class="text-3xl font-bold text-white mb-6">System Management</h2>
<div class="card border-red-500/50">
<h3 class="text-xl font-semibold text-red-400 mb-2">Danger Zone</h3>
<p class="text-gray-400 mb-4">These actions are irreversible. Please be certain before proceeding.</p>
<form id="reset-ai-form" class="flex items-center gap-4">
<select id="model_name_reset" name="model_name" class="input-field">
<option value="neurones_self">Neurones Self</option>
</select>
<button type="submit" class="btn btn-danger flex-shrink-0">Reset AI Model</button>
</form>
</div>
</section>
</main>
<div id="toast-container"></div>
<script>
const API_BASE_URL = window.location.origin;
// --- Navigation Logic ---
const navLinks = document.querySelectorAll('.sidebar-link');
const contentSections = document.querySelectorAll('.content-section');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('data-target');
navLinks.forEach(l => l.classList.remove('active'));
link.classList.add('active');
contentSections.forEach(section => {
section.classList.add('hidden');
if (section.id === targetId) {
section.classList.remove('hidden');
}
});
if (targetId === 'image-verification-section') {
loadPendingImages();
}
});
});
// --- Toast Notifications ---
function showToast(message, type = 'success') {
const toastContainer = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
toastContainer.appendChild(toast);
setTimeout(() => toast.remove(), 5000);
}
// --- Image Verification Logic ---
const pendingContainer = document.getElementById('pending-images-container');
const noPendingMessage = document.getElementById('no-pending-message');
const pendingBadge = document.getElementById('pending-count-badge');
const pendingStat = document.getElementById('pending-images-stat');
async function loadPendingImages() {
try {
const response = await fetch(`${API_BASE_URL}/admin/pending-images`);
if (!response.ok) throw new Error('Failed to fetch pending images.');
const images = await response.json();
pendingContainer.innerHTML = '';
if (images.length === 0) {
pendingContainer.appendChild(noPendingMessage);
noPendingMessage.classList.remove('hidden');
} else {
noPendingMessage.classList.add('hidden');
}
pendingBadge.textContent = images.length;
pendingStat.textContent = images.length;
pendingBadge.classList.toggle('hidden', images.length === 0);
images.forEach(image => {
const card = document.createElement('div');
card.className = 'card space-y-3';
card.innerHTML = `
<img src="data:${image.content_type};base64,${image.image_data_b64}" class="rounded-md w-full h-40 object-cover">
<div>
<label class="text-xs text-gray-400">User Label</label>
<p class="font-semibold">${image.user_label}</p>
</div>
<div class="flex gap-2 pt-2">
<button class="btn btn-success w-full" onclick="handleImageAction('${image.id}', 'approve')">Approve</button>
<button class="btn btn-danger w-full" onclick="handleImageAction('${image.id}', 'reject')">Reject</button>
</div>
`;
pendingContainer.appendChild(card);
});
} catch (error) {
showToast(error.message, 'error');
}
}
async function handleImageAction(id, action) {
const endpoint = action === 'approve' ? `/admin/approve-image/${id}` : `/admin/reject-image/${id}`;
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, { method: 'POST' });
const result = await response.json();
if (!response.ok) throw new Error(result.detail);
showToast(result.message, 'success');
loadPendingImages(); // Refresh the list
} catch (error) {
showToast(error.message, 'error');
}
}
document.getElementById('refresh-pending-btn').addEventListener('click', loadPendingImages);
// --- Model Training Logic ---
document.getElementById('train-text-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch(`${API_BASE_URL}/admin/train/`, {
method: 'POST',
body: formData
});
const result = await response.json();
if (!response.ok) throw new Error(result.detail);
showToast(result.message, 'success');
e.target.reset();
} catch (error) {
showToast(error.message, 'error');
}
});
document.getElementById('train-image-btn').addEventListener('click', async () => {
if (!confirm('Are you sure you want to start image model training? This may take several minutes and consume resources.')) return;
showToast('Starting image model training...', 'success');
try {
const response = await fetch(`${API_BASE_URL}/admin/train-image-model/`, { method: 'POST' });
const result = await response.json();
if (!response.ok) throw new Error(result.detail);
showToast(`Training complete! Accuracy: ${result.final_validation_accuracy}`, 'success');
} catch (error) {
showToast(error.message, 'error');
}
});
// --- System Management Logic ---
document.getElementById('reset-ai-form').addEventListener('submit', async (e) => {
e.preventDefault();
const modelName = document.getElementById('model_name_reset').value;
if (!confirm(`Are you absolutely sure you want to reset the '${modelName}' AI model? All its training data will be permanently deleted.`)) return;
try {
const response = await fetch(`${API_BASE_URL}/admin/reset-ai/?model_name=${modelName}`, { method: 'POST' });
const result = await response.json();
if (!response.ok) throw new Error(result.detail);
showToast(result.message, 'success');
} catch (error) {
showToast(error.message, 'error');
}
});
// Initial Load
window.onload = () => {
loadPendingImages();
};
</script>
</body>
</html>