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