NudeNet-FastAPI / index.html
xxparthparekhxx's picture
added a ui
99eb3dc
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NudeNet - Nudity Detection API</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;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 900px;
width: 100%;
padding: 40px;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.header h1 {
color: #333;
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 1.1em;
}
.documentation-link {
display: inline-block;
margin-top: 15px;
padding: 10px 20px;
background-color: #667eea;
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 0.9em;
transition: background-color 0.3s;
}
.documentation-link:hover {
background-color: #764ba2;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
border-bottom: 2px solid #e0e0e0;
}
.tab-button {
padding: 12px 24px;
background: none;
border: none;
cursor: pointer;
font-size: 1em;
color: #666;
border-bottom: 3px solid transparent;
transition: all 0.3s;
font-weight: 500;
}
.tab-button:hover {
color: #667eea;
}
.tab-button.active {
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 20px;
}
.upload-area:hover {
background-color: #f5f5f5;
border-color: #764ba2;
}
.upload-area.dragover {
background-color: #f0f0ff;
border-color: #764ba2;
}
.upload-icon {
font-size: 3em;
margin-bottom: 15px;
}
.upload-area h3 {
color: #333;
margin-bottom: 10px;
}
.upload-area p {
color: #666;
margin-bottom: 15px;
}
input[type="file"] {
display: none;
}
.model-selector {
margin-bottom: 20px;
}
.model-selector label {
display: inline-block;
margin-right: 20px;
color: #333;
font-weight: 500;
}
.radio-group {
display: flex;
gap: 20px;
}
.radio-group label {
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
}
.radio-group input[type="radio"] {
margin-right: 8px;
cursor: pointer;
}
.button-group {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1em;
font-weight: 600;
transition: all 0.3s;
}
.btn-primary {
background-color: #667eea;
color: white;
flex: 1;
}
.btn-primary:hover:not(:disabled) {
background-color: #764ba2;
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn-primary:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background-color: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background-color: #d0d0d0;
}
.preview-section {
margin-bottom: 30px;
}
.preview-section h3 {
color: #333;
margin-bottom: 15px;
}
.preview-image {
max-width: 100%;
max-height: 400px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.results-section {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}
.results-section h3 {
color: #333;
margin-bottom: 15px;
}
.result-item {
background: white;
padding: 15px;
border-radius: 6px;
margin-bottom: 10px;
border-left: 4px solid #667eea;
}
.result-label {
font-weight: 600;
color: #333;
}
.result-value {
color: #666;
margin-top: 5px;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.loading.active {
display: block;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
background-color: #fee;
border-left: 4px solid #f44;
color: #c33;
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
}
.success {
background-color: #efe;
border-left: 4px solid #4a4;
color: #3a3;
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
}
.detection-box {
background: white;
padding: 12px;
border-radius: 6px;
margin-bottom: 8px;
border-left: 4px solid #667eea;
}
.detection-box strong {
color: #667eea;
}
.base64-input {
width: 100%;
min-height: 150px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: monospace;
font-size: 0.9em;
resize: vertical;
margin-bottom: 15px;
}
.filename {
color: #667eea;
font-weight: 600;
margin-bottom: 10px;
}
.video-info {
background-color: #f9f9f9;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
border-left: 4px solid #667eea;
}
.video-info p {
color: #666;
margin-bottom: 5px;
}
.timestamp {
color: #667eea;
font-weight: 600;
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.header h1 {
font-size: 1.8em;
}
.tabs {
flex-wrap: wrap;
}
.tab-button {
padding: 10px 16px;
font-size: 0.9em;
}
.button-group {
flex-direction: column;
}
.radio-group {
flex-direction: column;
gap: 10px;
}
.upload-area {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔍 NudeNet Detection API</h1>
<p>Test nudity detection in images and videos</p>
<a href="/docs" class="documentation-link">📖 View API Documentation</a>
</div>
<div class="tabs">
<button class="tab-button active" data-tab="image">Image Detection</button>
<button class="tab-button" data-tab="base64">Base64 Image</button>
<button class="tab-button" data-tab="video">Video Detection</button>
</div>
<!-- Image Detection Tab -->
<div id="image" class="tab-content active">
<div class="model-selector">
<label>Select Model:</label>
<div class="radio-group">
<label>
<input type="radio" name="imageModel" value="320n" checked>
320n (Faster, Less Accurate)
</label>
<label>
<input type="radio" name="imageModel" value="640m">
640m (Slower, More Accurate)
</label>
</div>
</div>
<div class="upload-area" id="imageUploadArea">
<div class="upload-icon">📸</div>
<h3>Drag and drop your image here</h3>
<p>or click to select an image</p>
<p style="font-size: 0.9em; color: #999;">Supported formats: JPG, PNG, GIF, BMP</p>
<input type="file" id="imageInput" accept="image/*">
</div>
<div id="imageFilename" class="filename" style="display: none;"></div>
<div class="preview-section" id="imagePreviewSection" style="display: none;">
<h3>Preview</h3>
<img id="imagePreview" class="preview-image" alt="Preview">
</div>
<div class="button-group">
<button class="btn-primary" id="detectImageBtn" style="display: none;">Detect Nudity</button>
<button class="btn-secondary" id="clearImageBtn" onclick="clearImage()">Clear</button>
</div>
<div class="loading" id="imageLoading">
<div class="spinner"></div>
<p>Analyzing image...</p>
</div>
<div id="imageError" class="error" style="display: none;"></div>
<div id="imageResults" class="results-section" style="display: none;">
<h3>Detection Results</h3>
<div id="imageResultsContent"></div>
</div>
</div>
<!-- Base64 Image Tab -->
<div id="base64" class="tab-content">
<div class="model-selector">
<label>Select Model:</label>
<div class="radio-group">
<label>
<input type="radio" name="base64Model" value="320n" checked>
320n (Faster, Less Accurate)
</label>
<label>
<input type="radio" name="base64Model" value="640m">
640m (Slower, More Accurate)
</label>
</div>
</div>
<h3>Base64 Encoded Image</h3>
<textarea id="base64Input" class="base64-input" placeholder="Paste your base64 encoded image here..."></textarea>
<div class="button-group">
<button class="btn-primary" id="detectBase64Btn">Detect Nudity</button>
<button class="btn-secondary" onclick="clearBase64()">Clear</button>
</div>
<div class="loading" id="base64Loading">
<div class="spinner"></div>
<p>Analyzing image...</p>
</div>
<div id="base64Error" class="error" style="display: none;"></div>
<div id="base64Results" class="results-section" style="display: none;">
<h3>Detection Results</h3>
<div id="base64ResultsContent"></div>
</div>
</div>
<!-- Video Detection Tab -->
<div id="video" class="tab-content">
<div class="model-selector">
<label>Select Model:</label>
<div class="radio-group">
<label>
<input type="radio" name="videoModel" value="320n" checked>
320n (Faster, Less Accurate)
</label>
<label>
<input type="radio" name="videoModel" value="640m">
640m (Slower, More Accurate)
</label>
</div>
</div>
<div class="model-selector">
<label for="frameInterval">Frame Interval (seconds):</label>
<input type="number" id="frameInterval" value="1" min="0" max="30" step="0.1" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 100px;">
<p style="font-size: 0.9em; color: #666; margin-top: 5px;">0 = every frame, 1 = every second, etc.</p>
</div>
<div class="upload-area" id="videoUploadArea">
<div class="upload-icon">🎥</div>
<h3>Drag and drop your video here</h3>
<p>or click to select a video</p>
<p style="font-size: 0.9em; color: #999;">Supported formats: MP4, AVI, MOV, MKV</p>
<input type="file" id="videoInput" accept="video/*">
</div>
<div id="videoFilename" class="filename" style="display: none;"></div>
<div class="button-group">
<button class="btn-primary" id="detectVideoBtn" style="display: none;">Detect Nudity in Video</button>
<button class="btn-secondary" id="clearVideoBtn" onclick="clearVideo()">Clear</button>
</div>
<div class="loading" id="videoLoading">
<div class="spinner"></div>
<p>Analyzing video... This may take a while</p>
</div>
<div id="videoError" class="error" style="display: none;"></div>
<div id="videoResults" class="results-section" style="display: none;">
<h3>Detection Results</h3>
<div id="videoResultsContent"></div>
</div>
</div>
</div>
<script>
// Tab switching
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
const tabName = button.dataset.tab;
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all buttons
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
// Show selected tab and mark button as active
document.getElementById(tabName).classList.add('active');
button.classList.add('active');
});
});
// Image Upload
const imageUploadArea = document.getElementById('imageUploadArea');
const imageInput = document.getElementById('imageInput');
let selectedImageFile = null;
imageUploadArea.addEventListener('click', () => imageInput.click());
imageUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
imageUploadArea.classList.add('dragover');
});
imageUploadArea.addEventListener('dragleave', () => {
imageUploadArea.classList.remove('dragover');
});
imageUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
imageUploadArea.classList.remove('dragover');
handleImageUpload(e.dataTransfer.files[0]);
});
imageInput.addEventListener('change', (e) => {
if (e.target.files[0]) {
handleImageUpload(e.target.files[0]);
}
});
function handleImageUpload(file) {
if (!file.type.startsWith('image/')) {
showImageError('Please upload a valid image file');
return;
}
selectedImageFile = file;
document.getElementById('imageFilename').textContent = `File: ${file.name}`;
document.getElementById('imageFilename').style.display = 'block';
document.getElementById('imagePreviewSection').style.display = 'block';
document.getElementById('detectImageBtn').style.display = 'block';
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('imagePreview').src = e.target.result;
};
reader.readAsDataURL(file);
}
document.getElementById('detectImageBtn').addEventListener('click', async () => {
if (!selectedImageFile) return;
const model = document.querySelector('input[name="imageModel"]:checked').value;
const use640m = model === '640m';
const formData = new FormData();
formData.append('image', selectedImageFile);
try {
showImageLoading(true);
hideImageError();
document.getElementById('imageResults').style.display = 'none';
const response = await fetch(`/detect?use_640m=${use640m}`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
displayImageResults(data.result);
} catch (error) {
showImageError(`Error: ${error.message}`);
} finally {
showImageLoading(false);
}
});
function clearImage() {
selectedImageFile = null;
imageInput.value = '';
document.getElementById('imageFilename').style.display = 'none';
document.getElementById('imagePreviewSection').style.display = 'none';
document.getElementById('detectImageBtn').style.display = 'none';
document.getElementById('imageResults').style.display = 'none';
hideImageError();
}
function displayImageResults(results) {
const resultsContent = document.getElementById('imageResultsContent');
if (!results || results.length === 0) {
resultsContent.innerHTML = '<div class="success">✓ No nudity detected!</div>';
} else {
let html = '';
results.forEach((detection, index) => {
html += `
<div class="detection-box">
<strong>Detection ${index + 1}</strong><br>
Class: ${detection.class}<br>
Confidence: ${(detection.confidence * 100).toFixed(2)}%
</div>
`;
});
resultsContent.innerHTML = html;
}
document.getElementById('imageResults').style.display = 'block';
}
function showImageError(message) {
const errorDiv = document.getElementById('imageError');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
function hideImageError() {
document.getElementById('imageError').style.display = 'none';
}
function showImageLoading(show) {
document.getElementById('imageLoading').classList.toggle('active', show);
}
// Base64 Upload
document.getElementById('detectBase64Btn').addEventListener('click', async () => {
const base64Input = document.getElementById('base64Input').value.trim();
if (!base64Input) {
showBase64Error('Please paste a base64 encoded image');
return;
}
const model = document.querySelector('input[name="base64Model"]:checked').value;
const use640m = model === '640m';
try {
showBase64Loading(true);
hideBase64Error();
document.getElementById('base64Results').style.display = 'none';
const response = await fetch(`/detect_base64?use_640m=${use640m}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ image: base64Input })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
displayBase64Results(data.result);
} catch (error) {
showBase64Error(`Error: ${error.message}`);
} finally {
showBase64Loading(false);
}
});
function clearBase64() {
document.getElementById('base64Input').value = '';
document.getElementById('base64Results').style.display = 'none';
hideBase64Error();
}
function displayBase64Results(results) {
const resultsContent = document.getElementById('base64ResultsContent');
if (!results || results.length === 0) {
resultsContent.innerHTML = '<div class="success">✓ No nudity detected!</div>';
} else {
let html = '';
results.forEach((detection, index) => {
html += `
<div class="detection-box">
<strong>Detection ${index + 1}</strong><br>
Class: ${detection.class}<br>
Confidence: ${(detection.confidence * 100).toFixed(2)}%
</div>
`;
});
resultsContent.innerHTML = html;
}
document.getElementById('base64Results').style.display = 'block';
}
function showBase64Error(message) {
const errorDiv = document.getElementById('base64Error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
function hideBase64Error() {
document.getElementById('base64Error').style.display = 'none';
}
function showBase64Loading(show) {
document.getElementById('base64Loading').classList.toggle('active', show);
}
// Video Upload
const videoUploadArea = document.getElementById('videoUploadArea');
const videoInput = document.getElementById('videoInput');
let selectedVideoFile = null;
videoUploadArea.addEventListener('click', () => videoInput.click());
videoUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
videoUploadArea.classList.add('dragover');
});
videoUploadArea.addEventListener('dragleave', () => {
videoUploadArea.classList.remove('dragover');
});
videoUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
videoUploadArea.classList.remove('dragover');
handleVideoUpload(e.dataTransfer.files[0]);
});
videoInput.addEventListener('change', (e) => {
if (e.target.files[0]) {
handleVideoUpload(e.target.files[0]);
}
});
function handleVideoUpload(file) {
if (!file.type.startsWith('video/')) {
showVideoError('Please upload a valid video file');
return;
}
selectedVideoFile = file;
document.getElementById('videoFilename').textContent = `File: ${file.name}`;
document.getElementById('videoFilename').style.display = 'block';
document.getElementById('detectVideoBtn').style.display = 'block';
}
document.getElementById('detectVideoBtn').addEventListener('click', async () => {
if (!selectedVideoFile) return;
const model = document.querySelector('input[name="videoModel"]:checked').value;
const use640m = model === '640m';
const frameInterval = document.getElementById('frameInterval').value;
const formData = new FormData();
formData.append('video', selectedVideoFile);
try {
showVideoLoading(true);
hideVideoError();
document.getElementById('videoResults').style.display = 'none';
const response = await fetch(`/detect_video?frame_interval=${frameInterval}&use_640m=${use640m}`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
displayVideoResults(data.results);
} catch (error) {
showVideoError(`Error: ${error.message}`);
} finally {
showVideoLoading(false);
}
});
function clearVideo() {
selectedVideoFile = null;
videoInput.value = '';
document.getElementById('videoFilename').style.display = 'none';
document.getElementById('detectVideoBtn').style.display = 'none';
document.getElementById('videoResults').style.display = 'none';
hideVideoError();
}
function displayVideoResults(results) {
const resultsContent = document.getElementById('videoResultsContent');
if (!results || results.length === 0) {
resultsContent.innerHTML = '<div class="success">✓ No nudity detected in the video!</div>';
} else {
let html = '';
results.forEach((result) => {
html += `
<div class="video-info">
<p><span class="timestamp">Timestamp: ${result.timestamp}s</span></p>
`;
result.detections.forEach((detection, index) => {
html += `
<div class="detection-box">
<strong>Detection ${index + 1}</strong><br>
Class: ${detection.class}<br>
Confidence: ${(detection.confidence * 100).toFixed(2)}%
</div>
`;
});
html += '</div>';
});
resultsContent.innerHTML = html;
}
document.getElementById('videoResults').style.display = 'block';
}
function showVideoError(message) {
const errorDiv = document.getElementById('videoError');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
function hideVideoError() {
document.getElementById('videoError').style.display = 'none';
}
function showVideoLoading(show) {
document.getElementById('videoLoading').classList.toggle('active', show);
}
</script>
</body>
</html>