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