|
|
<!DOCTYPE html>
|
|
|
<html lang="en" class="dark">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>Single Classification</title>
|
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
|
<style>
|
|
|
html.dark {
|
|
|
--bg-color: #1a202c;
|
|
|
--text-color: #e2e8f0;
|
|
|
--card-bg-color: #2d3748;
|
|
|
--card-border-color: #4a5568;
|
|
|
--btn-bg-color: #4a5568;
|
|
|
--btn-hover-bg-color: #2c5282;
|
|
|
--btn-text-color: #e2e8f0;
|
|
|
}
|
|
|
|
|
|
html.light {
|
|
|
--bg-color: #f7fafc;
|
|
|
--text-color: #2d3748;
|
|
|
--card-bg-color: #ffffff;
|
|
|
--card-border-color: #e2e8f0;
|
|
|
--btn-bg-color: #3182ce;
|
|
|
--btn-hover-bg-color: #2c5282;
|
|
|
--btn-text-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
background-color: var(--bg-color);
|
|
|
color: var(--text-color);
|
|
|
}
|
|
|
|
|
|
.card {
|
|
|
background-color: var(--card-bg-color);
|
|
|
border: 1px solid var(--card-border-color);
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
background-color: var(--btn-bg-color);
|
|
|
color: var(--btn-text-color);
|
|
|
}
|
|
|
|
|
|
.btn:hover {
|
|
|
background-color: var(--btn-hover-bg-color);
|
|
|
}
|
|
|
|
|
|
.loading {
|
|
|
display: none;
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
z-index: 1000;
|
|
|
}
|
|
|
|
|
|
.result-table {
|
|
|
font-family: monospace;
|
|
|
white-space: pre;
|
|
|
}
|
|
|
|
|
|
.classification-text {
|
|
|
font-size: 1.5rem;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.btn-classify {
|
|
|
background-color: #10b981;
|
|
|
color: white;
|
|
|
font-size: 1.1rem;
|
|
|
padding: 0.75rem 1.5rem;
|
|
|
border-radius: 9999px;
|
|
|
}
|
|
|
|
|
|
.btn-classify:hover {
|
|
|
background-color: #059669;
|
|
|
}
|
|
|
|
|
|
.btn-remove {
|
|
|
background-color: #ef4444;
|
|
|
color: white;
|
|
|
font-size: 1.1rem;
|
|
|
padding: 0.75rem 1.5rem;
|
|
|
border-radius: 9999px;
|
|
|
}
|
|
|
|
|
|
.btn-remove:hover {
|
|
|
background-color: #dc2626;
|
|
|
}
|
|
|
|
|
|
.results-container {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.classification-result {
|
|
|
margin: 1.5rem 0;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.table-container {
|
|
|
width: 100%;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
|
|
|
.loading-overlay {
|
|
|
background: rgba(0, 0, 0, 0.7);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.loading-text {
|
|
|
color: white;
|
|
|
font-size: 1.2rem;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body class="min-h-screen">
|
|
|
<div class="container mx-auto px-4 py-8">
|
|
|
<div class="flex justify-end mb-4">
|
|
|
<button id="themeToggle" class="bg-gray-800 text-white px-4 py-2 rounded hover:bg-gray-600">
|
|
|
☀️
|
|
|
</button>
|
|
|
</div>
|
|
|
<h1 class="text-3xl font-bold text-center mb-8">Single Image Classification</h1>
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
|
|
|
|
<div class="card p-6 rounded-lg shadow-md">
|
|
|
<div id="uploadSection" class="mb-4">
|
|
|
<label class="block text-sm font-bold mb-2">Upload Image</label>
|
|
|
<input type="file" id="imageInput" accept=".jpg,.jpeg,.png" class="hidden">
|
|
|
<button onclick="document.getElementById('imageInput').click()" class="btn w-full py-2 px-4 rounded">
|
|
|
Select Image
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div id="imagePreview" class="mt-4 hidden">
|
|
|
<img id="preview" class="max-w-full h-auto rounded-lg">
|
|
|
<div class="mt-4 flex space-x-4">
|
|
|
<button onclick="removeImage()" class="btn btn-remove flex-1">
|
|
|
Remove
|
|
|
</button>
|
|
|
<button onclick="classifyImage()" class="btn btn-classify flex-1">
|
|
|
Classify
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="card p-6 rounded-lg shadow-md">
|
|
|
<h2 class="text-xl font-bold mb-4 text-center">Classification Results</h2>
|
|
|
<div id="results" class="results-container"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="loading" class="loading flex items-center justify-center">
|
|
|
<div class="loading-overlay p-8 rounded-lg text-center">
|
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto"></div>
|
|
|
<p id="loadingText" class="mt-4 loading-text">Processing...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
|
const htmlElement = document.documentElement;
|
|
|
|
|
|
|
|
|
if (!localStorage.getItem('theme')) {
|
|
|
localStorage.setItem('theme', 'dark');
|
|
|
}
|
|
|
|
|
|
|
|
|
const currentTheme = localStorage.getItem('theme') || 'dark';
|
|
|
htmlElement.classList.remove('light', 'dark');
|
|
|
htmlElement.classList.add(currentTheme);
|
|
|
themeToggle.textContent = currentTheme === 'dark' ? '☀️' : '🌙';
|
|
|
|
|
|
|
|
|
themeToggle.addEventListener('click', () => {
|
|
|
const isDark = htmlElement.classList.contains('dark');
|
|
|
htmlElement.classList.remove('dark', 'light');
|
|
|
const newTheme = isDark ? 'light' : 'dark';
|
|
|
htmlElement.classList.add(newTheme);
|
|
|
localStorage.setItem('theme', newTheme);
|
|
|
themeToggle.textContent = newTheme === 'dark' ? '☀️' : '🌙';
|
|
|
});
|
|
|
|
|
|
|
|
|
let currentFile = null;
|
|
|
|
|
|
document.getElementById('imageInput').addEventListener('change', function(e) {
|
|
|
const file = e.target.files[0];
|
|
|
if (file) {
|
|
|
uploadFile(file);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
function uploadFile(file) {
|
|
|
const formData = new FormData();
|
|
|
formData.append('file', file);
|
|
|
|
|
|
showLoading('Uploading...');
|
|
|
|
|
|
fetch('/upload_single', {
|
|
|
method: 'POST',
|
|
|
body: formData
|
|
|
})
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
if (data.filename) {
|
|
|
currentFile = data.filename;
|
|
|
document.getElementById('preview').src = `/static/uploads/single/${data.filename}`;
|
|
|
document.getElementById('imagePreview').classList.remove('hidden');
|
|
|
document.getElementById('uploadSection').classList.add('hidden');
|
|
|
} else {
|
|
|
alert(data.error || 'Upload failed');
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('Error:', error);
|
|
|
alert('Upload failed');
|
|
|
})
|
|
|
.finally(() => {
|
|
|
hideLoading();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function removeImage() {
|
|
|
document.getElementById('imageInput').value = '';
|
|
|
document.getElementById('imagePreview').classList.add('hidden');
|
|
|
document.getElementById('uploadSection').classList.remove('hidden');
|
|
|
document.getElementById('results').innerHTML = '';
|
|
|
currentFile = null;
|
|
|
}
|
|
|
|
|
|
function classifyImage() {
|
|
|
if (!currentFile) {
|
|
|
alert('Please upload an image first');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
showLoading('Classifying...');
|
|
|
|
|
|
fetch('/classify_single', {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
},
|
|
|
body: JSON.stringify({ filename: currentFile })
|
|
|
})
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
if (data.error) {
|
|
|
throw new Error(data.error);
|
|
|
}
|
|
|
const resultsDiv = document.getElementById('results');
|
|
|
resultsDiv.innerHTML = `
|
|
|
<div class="classification-result">
|
|
|
<span class="font-bold">Classification: </span>
|
|
|
<span class="classification-text ${data.classification === 'Pass' ? 'text-green-600' : 'text-red-600'}">
|
|
|
${data.classification}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="table-container">
|
|
|
<pre class="whitespace-pre-wrap font-mono text-sm">${data.result_table}</pre>
|
|
|
</div>
|
|
|
`;
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('Error:', error);
|
|
|
alert('Classification failed: ' + error.message);
|
|
|
})
|
|
|
.finally(() => {
|
|
|
hideLoading();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function showLoading(text) {
|
|
|
document.getElementById('loading').style.display = 'flex';
|
|
|
document.getElementById('loadingText').textContent = text;
|
|
|
}
|
|
|
|
|
|
function hideLoading() {
|
|
|
document.getElementById('loading').style.display = 'none';
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|
|
|
</html> |