| <!DOCTYPE html>
|
| <html lang="en">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Multiple Classification</title>
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/dropzone.min.css">
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| <style>
|
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
|
| :root {
|
| --primary-color: #4f46e5;
|
| --primary-dark: #4338ca;
|
| --success-color: #22c55e;
|
| --error-color: #ef4444;
|
| --background-color: #1a1a2e;
|
| --card-background: rgba(255, 255, 255, 0.1);
|
| --text-primary: #ffffff;
|
| --text-secondary: #a5b4fc;
|
| --border-color: rgba(255, 255, 255, 0.18);
|
| }
|
|
|
| body {
|
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| min-height: 100vh;
|
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
| display: flex;
|
| justify-content: center;
|
| align-items: center;
|
| margin: 0;
|
| color: var(--text-primary);
|
| overflow-x: hidden;
|
| }
|
|
|
| .container {
|
| max-width: 1200px;
|
| margin: 0 auto;
|
| padding: 2rem;
|
| position: relative;
|
| z-index: 1;
|
| width: 100%;
|
| }
|
|
|
| .glass-card {
|
| background: var(--card-background);
|
| backdrop-filter: blur(10px);
|
| border-radius: 20px;
|
| padding: 2rem;
|
| box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
| border: 1px solid var(--border-color);
|
| transform: translateY(0);
|
| transition: transform 0.3s ease;
|
| text-align: center;
|
| }
|
|
|
| .glass-card:hover {
|
| transform: translateY(-5px);
|
| }
|
|
|
| .header {
|
| text-align: center;
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .header h1 {
|
| font-size: 2rem;
|
| color: var(--text-primary);
|
| margin-bottom: 0.25rem;
|
| font-weight: 700;
|
| }
|
|
|
| .header p {
|
| color: var(--text-secondary);
|
| font-size: 1rem;
|
| }
|
|
|
| .upload-section {
|
| background: var(--card-background);
|
| border-radius: 0.75rem;
|
| padding: 1.5rem;
|
| box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .dropzone {
|
| border: 2px dashed var(--primary-color);
|
| border-radius: 1rem;
|
| background: rgba(255, 255, 255, 0.05);
|
| padding: 2rem;
|
| min-height: 200px;
|
| display: flex;
|
| flex-direction: column;
|
| align-items: center;
|
| justify-content: center;
|
| transition: all 0.3s ease;
|
| }
|
|
|
| .dropzone:hover {
|
| background: rgba(255, 255, 255, 0.1);
|
| border-color: var(--primary-dark);
|
| }
|
|
|
| .dropzone .dz-message {
|
| margin: 1rem 0;
|
| text-align: center;
|
| }
|
|
|
| .dropzone .dz-preview {
|
| background: var(--card-background);
|
| border-radius: 1rem;
|
| border: 1px solid var(--border-color);
|
| padding: 0.5rem;
|
| margin: 1rem;
|
| transition: transform 0.2s ease;
|
| }
|
|
|
| .dropzone .dz-preview:hover {
|
| transform: translateY(-2px);
|
| }
|
|
|
| .dropzone .dz-image {
|
| width: 120px !important;
|
| height: 120px !important;
|
| border-radius: 0.75rem !important;
|
| overflow: hidden;
|
| position: relative;
|
| }
|
|
|
| .dropzone .dz-image img {
|
| width: 100%;
|
| height: 100%;
|
| object-fit: cover;
|
| }
|
|
|
| .dropzone .dz-details {
|
| padding-top: 0.5rem;
|
| text-align: center;
|
| }
|
|
|
| .dropzone .dz-filename {
|
| color: var(--text-primary);
|
| font-size: 0.875rem;
|
| max-width: 120px;
|
| overflow: hidden;
|
| text-overflow: ellipsis;
|
| white-space: nowrap;
|
| }
|
|
|
| .dropzone .dz-remove {
|
| color: var(--error-color);
|
| text-decoration: none;
|
| font-size: 0.875rem;
|
| margin-top: 0.5rem;
|
| display: inline-block;
|
| transition: opacity 0.2s ease;
|
| }
|
|
|
| .dropzone .dz-remove:hover {
|
| opacity: 0.8;
|
| }
|
|
|
| .dropzone .dz-preview .dz-progress {
|
| height: 4px;
|
| background: rgba(255, 255, 255, 0.1);
|
| margin-top: 0.5rem;
|
| border-radius: 2px;
|
| }
|
|
|
| .dropzone .dz-preview .dz-progress .dz-upload {
|
| background: var(--primary-color);
|
| border-radius: 2px;
|
| }
|
|
|
| .preview-container {
|
| display: flex;
|
| flex-wrap: wrap;
|
| gap: 1rem;
|
| margin-top: 1rem;
|
| }
|
|
|
| .control-panel {
|
| display: flex;
|
| gap: 1rem;
|
| margin-top: 1rem;
|
| justify-content: center;
|
| }
|
|
|
| .btn {
|
| padding: 0.75rem 1.5rem;
|
| border-radius: 0.5rem;
|
| border: none;
|
| color: white;
|
| font-weight: 500;
|
| cursor: pointer;
|
| transition: all 0.2s ease;
|
| display: flex;
|
| align-items: center;
|
| gap: 0.5rem;
|
| font-size: 1rem;
|
| }
|
|
|
| .btn:hover {
|
| transform: translateY(-1px);
|
| }
|
|
|
| .btn-clear {
|
| background: var(--text-secondary);
|
| }
|
|
|
| .btn-classify {
|
| background: var(--primary-color);
|
| }
|
|
|
| .btn-classify:hover {
|
| background: var(--primary-dark);
|
| }
|
|
|
| .btn-download {
|
| background: var(--success-color);
|
| }
|
|
|
| .btn-download:hover {
|
| background: #1e9c4f;
|
| }
|
|
|
| .results-section {
|
| margin-top: 1.5rem;
|
| }
|
|
|
| .section-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| margin-bottom: 1rem;
|
| padding: 1rem;
|
| border-radius: 0.5rem;
|
| box-shadow: 0 2px 4px -1px rgb(0 0 0 / 0.1);
|
| background: var(--card-background);
|
| }
|
|
|
| .section-header h2 {
|
| margin: 0;
|
| display: flex;
|
| align-items: center;
|
| gap: 0.5rem;
|
| font-size: 1.25rem;
|
| color: var(--text-primary);
|
| }
|
|
|
| .pass-header {
|
| background: rgba(34, 197, 94, 0.1);
|
| color: var(--success-color);
|
| }
|
|
|
| .fail-header {
|
| background: rgba(239, 68, 68, 0.1);
|
| color: var(--error-color);
|
| }
|
|
|
| .download-btn {
|
| padding: 0.75rem 1.5rem;
|
| border-radius: 0.5rem;
|
| color: white;
|
| text-decoration: none;
|
| font-weight: 500;
|
| transition: all 0.2s ease;
|
| display: flex;
|
| align-items: center;
|
| gap: 0.5rem;
|
| font-size: 1rem;
|
| }
|
|
|
| .download-btn:hover {
|
| transform: translateY(-1px);
|
| }
|
|
|
| .download-btn.pass {
|
| background: var(--success-color);
|
| }
|
|
|
| .download-btn.fail {
|
| background: var(--error-color);
|
| }
|
|
|
| .image-list {
|
| display: flex;
|
| flex-direction: column;
|
| gap: 0.75rem;
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .image-ribbon {
|
| display: flex;
|
| align-items: center;
|
| padding: 0.75rem;
|
| background: var(--card-background);
|
| border-radius: 0.5rem;
|
| box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
|
| transition: all 0.2s ease;
|
| cursor: pointer;
|
| }
|
|
|
| .image-ribbon:hover {
|
| transform: translateX(4px);
|
| background: rgba(255, 255, 255, 0.1);
|
| }
|
|
|
| .thumbnail {
|
| width: 50px;
|
| height: 50px;
|
| border-radius: 0.375rem;
|
| object-fit: cover;
|
| margin-right: 1rem;
|
| }
|
|
|
| .image-info {
|
| flex: 1;
|
| }
|
|
|
| .filename {
|
| font-weight: 500;
|
| color: var(--text-primary);
|
| margin-top: 0.5rem;
|
| font-size: 1rem;
|
| white-space: normal;
|
| word-break: break-word;
|
| text-align: center;
|
| }
|
|
|
| .labels {
|
| display: flex;
|
| flex-wrap: wrap;
|
| gap: 0.5rem;
|
| justify-content: center;
|
| align-items: center;
|
| margin: 0 auto;
|
| position: relative;
|
| transition: all 0.3s ease;
|
| }
|
|
|
| .label {
|
| background: rgba(255, 255, 255, 0.1);
|
| padding: 0.5rem 1rem;
|
| border-radius: 9999px;
|
| font-size: 0.875rem;
|
| color: var(--text-secondary);
|
| white-space: nowrap;
|
| text-align: center;
|
| }
|
|
|
| #summary {
|
| background: var(--card-background);
|
| padding: 1rem;
|
| border-radius: 0.5rem;
|
| text-align: center;
|
| font-size: 1rem;
|
| font-weight: 500;
|
| margin-bottom: 1.5rem;
|
| box-shadow: 0 2px 4px -1px rgb(0 0 0 / 0.1);
|
| color: var(--text-primary);
|
| }
|
|
|
| .progress-container {
|
| margin-top: 1rem;
|
| background: rgba(255, 255, 255, 0.1);
|
| border-radius: 0.5rem;
|
| overflow: hidden;
|
| }
|
|
|
| .progress-bar {
|
| height: 6px;
|
| background: var(--primary-color);
|
| width: 0;
|
| transition: width 0.3s ease;
|
| }
|
|
|
| .loading-overlay {
|
| display: none;
|
| position: fixed;
|
| top: 0;
|
| left: 0;
|
| width: 100%;
|
| height: 100%;
|
| background: rgba(0, 0, 0, 0.95);
|
| z-index: 1000;
|
| justify-content: center;
|
| align-items: center;
|
| color: white;
|
| }
|
|
|
| .loading-content {
|
| background: var(--card-background);
|
| padding: 2rem;
|
| border-radius: 1rem;
|
| width: 90%;
|
| max-width: 500px;
|
| box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
| text-align: center;
|
| color: var(--text-primary);
|
| }
|
|
|
| .loading-header {
|
| text-align: center;
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .loading-header h3 {
|
| color: var(--primary-color);
|
| margin: 0;
|
| margin-bottom: 0.5rem;
|
| }
|
|
|
| .loading-spinner {
|
| margin: 1rem 0;
|
| font-size: 2rem;
|
| color: var(--primary-color);
|
| }
|
|
|
| .loading-progress {
|
| font-size: 1.1rem;
|
| color: var(--text-primary);
|
| }
|
|
|
| .image-modal {
|
| display: none;
|
| position: fixed;
|
| top: 0;
|
| left: 0;
|
| width: 100%;
|
| height: 100%;
|
| background: rgba(0, 0, 0, 0.9);
|
| z-index: 2000;
|
| justify-content: center;
|
| align-items: center;
|
| }
|
|
|
| .modal-content {
|
| position: relative;
|
| max-width: 90%;
|
| max-height: 90vh;
|
| margin: auto;
|
| display: flex;
|
| flex-direction: column;
|
| align-items: center;
|
| }
|
|
|
| .modal-image {
|
| max-width: 100%;
|
| max-height: 80vh;
|
| object-fit: contain;
|
| border-radius: 0.5rem;
|
| }
|
|
|
| .modal-close {
|
| position: absolute;
|
| top: -2rem;
|
| right: 0;
|
| color: white;
|
| font-size: 1.5rem;
|
| cursor: pointer;
|
| background: none;
|
| border: none;
|
| padding: 0.5rem;
|
| }
|
|
|
| .results-table {
|
| width: 100%;
|
| border-collapse: collapse;
|
| margin-top: 1.5rem;
|
| background: var(--card-background);
|
| border-radius: 0.5rem;
|
| overflow: hidden;
|
| table-layout: fixed;
|
| }
|
|
|
| .results-table th,
|
| .results-table td {
|
| padding: 0.75rem 0.5rem;
|
| vertical-align: middle;
|
| text-align: center;
|
| border-bottom: 1px solid var(--border-color);
|
| }
|
|
|
| .results-table .serial {
|
| width: 80px;
|
| }
|
|
|
| .results-table .image-col {
|
| width: 200px;
|
| }
|
|
|
| .results-table .result-col {
|
| width: 180px;
|
| }
|
|
|
| .results-table .labels-col {
|
| width: auto;
|
| min-width: 150px;
|
| padding-left: 1rem;
|
| text-align: center !important;
|
| }
|
|
|
| .results-table td {
|
| padding: 0.75rem 0.5rem;
|
| vertical-align: middle;
|
| border-bottom: 1px solid var(--border-color);
|
| text-align: left;
|
| }
|
|
|
| .results-table td.serial,
|
| .results-table td.image-col,
|
| .results-table td.result-col {
|
| text-align: center;
|
| }
|
|
|
| .results-table th.labels-col {
|
| text-align: center;
|
| }
|
|
|
| .results-table td.labels-col {
|
| text-align: center !important;
|
| padding: 1rem;
|
| }
|
|
|
| .results-table tbody tr:last-child td {
|
| border-bottom: none;
|
| }
|
|
|
| .result-pass,
|
| .result-fail {
|
| display: inline-block;
|
| padding: 0.25rem 0.75rem;
|
| border-radius: 9999px;
|
| text-align: center;
|
| }
|
|
|
| .result-pass {
|
| background: rgba(34, 197, 94, 0.1);
|
| }
|
|
|
| .result-fail {
|
| background: rgba(239, 68, 68, 0.1);
|
| }
|
|
|
| .dropzone {
|
| border: 2px dashed var(--primary-color);
|
| border-radius: 0.5rem;
|
| background: rgba(255, 255, 255, 0.1);
|
| padding: 1rem;
|
| min-height: 150px;
|
| }
|
|
|
| .dropzone .dz-preview {
|
| margin: 1rem;
|
| }
|
|
|
| .dropzone .dz-preview .dz-image {
|
| border-radius: 0.5rem;
|
| }
|
|
|
| .dropzone .dz-preview .dz-details {
|
| color: var(--text-primary);
|
| }
|
|
|
| .dropzone .dz-preview {
|
| display: inline-block;
|
| margin: 1rem;
|
| position: relative;
|
| }
|
|
|
| .dropzone .dz-preview:nth-child(n+5) {
|
| opacity: 0.3;
|
| }
|
|
|
| .dropzone .dz-preview:nth-child(n+6) {
|
| display: none !important;
|
| }
|
|
|
| .dropzone .dz-preview.dz-file-preview .dz-image,
|
| .dropzone .dz-preview .dz-image {
|
| border-radius: 8px;
|
| width: 150px;
|
| height: 150px;
|
| }
|
|
|
| .dz-more-indicator {
|
| position: absolute;
|
| top: 0;
|
| left: 0;
|
| width: 100%;
|
| height: 100%;
|
| background: rgba(0, 0, 0, 0.7);
|
| color: white;
|
| display: flex;
|
| justify-content: center;
|
| align-items: center;
|
| font-size: 1.2rem;
|
| border-radius: 8px;
|
| display: none;
|
| }
|
|
|
| .dropzone .dz-preview:nth-child(5) .dz-more-indicator {
|
| display: flex;
|
| }
|
|
|
| .dropzone .dz-preview {
|
| display: none !important;
|
| }
|
|
|
| .upload-section {
|
| background: var(--card-background);
|
| border-radius: 1rem;
|
| padding: 2rem;
|
| margin-bottom: 2rem;
|
| }
|
|
|
| .dropzone {
|
| border: 2px dashed var(--primary-color);
|
| border-radius: 1rem;
|
| padding: 2rem;
|
| text-align: center;
|
| background: rgba(255, 255, 255, 0.05);
|
| transition: all 0.3s ease;
|
| min-height: 200px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| }
|
|
|
| .dropzone .dz-message {
|
| margin: 0;
|
| }
|
|
|
| .dropzone:hover {
|
| background: rgba(255, 255, 255, 0.08);
|
| border-color: var(--primary-dark);
|
| }
|
|
|
| #thumbnail-preview-box {
|
| display: none;
|
| position: absolute;
|
| z-index: 9999;
|
| background: var(--card-background);
|
| border: 1px solid var(--border-color);
|
| border-radius: 0.75rem;
|
| padding: 1rem;
|
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
| max-width: 300px;
|
| }
|
|
|
| #thumbnail-preview-box img {
|
| max-width: 100%;
|
| border-radius: 0.5rem;
|
| display: block;
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="container">
|
| <div class="glass-card">
|
| <div class="header">
|
| <h1>Multiple Images Classification</h1>
|
| <p>Classify and segregate multiple images at once...</p>
|
| </div>
|
|
|
| <div class="upload-section">
|
| <form action="/upload_multiple" class="dropzone" id="upload-form">
|
| <div class="dz-message">
|
| <i class="fas fa-cloud-upload-alt fa-3x" style="color: var(--primary-color); margin-bottom: 1rem;"></i>
|
| <h3 style="margin: 0.5rem 0; color: var(--text-primary);">Drop files here</h3>
|
| <p style="color: var(--text-secondary); margin: 0;">or click to browse</p>
|
| <div id="file-count" style="margin-top: 1rem; color: var(--text-secondary);"></div>
|
| </div>
|
| </form>
|
| <div class="progress-container">
|
| <div class="progress-bar" id="upload-progress"></div>
|
| </div>
|
| <div class="control-panel">
|
| <button class="btn btn-clear" onclick="clearAllImages()">
|
| <i class="fas fa-trash-alt"></i>
|
| Clear All
|
| </button>
|
| <button class="btn btn-classify" id="classify-btn" onclick="highlightClassification()">
|
| <i class="fas fa-tags"></i>
|
| Classify
|
| </button>
|
|
|
| <button class="btn btn-download" id="download-html-btn" onclick="downloadPageAsHTML()">
|
| <i class="fas fa-download"></i>
|
| Download HTML
|
| </button>
|
| </div>
|
| </div>
|
|
|
| <div id="summary"></div>
|
|
|
| <div class="results-section">
|
| <table class="results-table" id="results-table">
|
| <thead>
|
| <tr>
|
| <th class="serial">S.No</th>
|
| <th class="image-col">Image</th>
|
| <th class="result-col">Result</th>
|
| <th class="labels-col">Labels</th>
|
| </tr>
|
| </thead>
|
| <tbody id="results-tbody">
|
| </tbody>
|
| </table>
|
| </div>
|
|
|
|
|
| <div class="loading-overlay" id="loading-overlay">
|
| <div class="loading-content">
|
| <div class="loading-header">
|
| <h3>Classifying Images</h3>
|
| <p>Please wait while we process your images...</p>
|
| </div>
|
| <div class="loading-spinner">
|
| <i class="fas fa-spinner fa-spin"></i>
|
| </div>
|
| <div class="loading-files" id="loading-files">
|
|
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="image-modal" id="image-modal">
|
| <div class="modal-content">
|
| <button class="modal-close" onclick="closeModal()">
|
| <i class="fas fa-times"></i>
|
| </button>
|
| <img class="modal-image" id="modal-image" src="" alt="">
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <div id="thumbnail-preview-box">
|
| <img id="thumbnail-preview-img" src="" alt="">
|
| </div>
|
|
|
| <script>
|
| let isClassifying = false;
|
|
|
| Dropzone.options.uploadForm = {
|
| paramName: "file",
|
| maxFilesize: 16,
|
| acceptedFiles: "image/*",
|
| previewsContainer: false,
|
| init: function() {
|
| let myDropzone = this;
|
|
|
|
|
| this.on("addedfile", function(file) {
|
|
|
| if (myDropzone.files.length === 1) {
|
|
|
| document.getElementById('results-tbody').innerHTML = '';
|
| document.getElementById('summary').innerHTML = '';
|
| document.getElementById('upload-progress').style.width = '0';
|
|
|
|
|
| fetch('/clear_uploads', { method: 'POST' })
|
| .then(response => response.json())
|
| .catch(error => console.error('Error:', error));
|
| }
|
| updateFileCount(myDropzone.files.length);
|
| });
|
|
|
| this.on("success", function(file, response) {
|
| updateFileCount(this.files.length);
|
| });
|
|
|
| this.on("removedfile", function() {
|
| updateFileCount(this.files.length);
|
| });
|
|
|
| this.on("totaluploadprogress", function(progress) {
|
| document.getElementById('upload-progress').style.width = progress + "%";
|
| });
|
|
|
|
|
| this.element.querySelector(".dz-message").addEventListener("click", function() {
|
|
|
| myDropzone.removeAllFiles(true);
|
| document.getElementById('results-tbody').innerHTML = '';
|
| document.getElementById('summary').innerHTML = '';
|
| document.getElementById('upload-progress').style.width = '0';
|
|
|
|
|
| fetch('/clear_uploads', { method: 'POST' })
|
| .then(response => response.json())
|
| .catch(error => console.error('Error:', error));
|
| });
|
| }
|
| };
|
|
|
| function updateFileCount(count) {
|
| const fileCount = document.getElementById('file-count');
|
| if (count > 0) {
|
| fileCount.textContent = `${count} file${count === 1 ? '' : 's'} selected`;
|
| } else {
|
| fileCount.textContent = '';
|
| }
|
| }
|
|
|
| function updateSummary() {
|
| const passCount = document.querySelectorAll('.result-pass').length;
|
| const failCount = document.querySelectorAll('.result-fail').length;
|
| const totalFiles = passCount + failCount;
|
|
|
| document.getElementById('summary').innerHTML = `
|
| <i class="fas fa-chart-pie"></i> Results:
|
| <span style="color: var(--success-color)">${passCount} passed</span>,
|
| <span style="color: var(--error-color)">${failCount} failed</span>
|
| out of ${totalFiles} total images
|
| `;
|
| }
|
|
|
| function clearAllImages() {
|
| fetch('/clear_uploads', { method: 'POST' })
|
| .then(response => response.json())
|
| .then(data => {
|
|
|
| document.getElementById('results-tbody').innerHTML = '';
|
| document.getElementById('summary').innerHTML = '';
|
| document.getElementById('upload-progress').style.width = '0';
|
|
|
|
|
| let myDropzone = Dropzone.forElement("#upload-form");
|
| myDropzone.removeAllFiles(true);
|
|
|
|
|
| document.querySelectorAll('.dz-preview').forEach(el => el.remove());
|
| })
|
| .catch(error => console.error('Error:', error));
|
| }
|
|
|
| function createTableRow(result, index) {
|
| const labels = result.status === 'Fail' ? result.labels : [];
|
|
|
| return `
|
| <tr>
|
| <td class="serial">${index + 1}</td>
|
| <td class="image-col">
|
| <img src="data:image/jpeg;base64,${result.image}"
|
| alt="${result.filename}"
|
| class="thumbnail"
|
| onclick="openModal(this)"
|
| style="cursor: pointer;">
|
| <div class="filename">${result.filename}</div>
|
| </td>
|
| <td class="result-col">
|
| <span class="result-${result.status.toLowerCase()}">${result.status}</span>
|
| </td>
|
| <td class="labels-col">
|
| ${result.status === 'Fail' ? `
|
| <div class="labels">
|
| ${labels.map(label => `
|
| <span class="label">${label}</span>
|
| `).join('')}
|
| </div>
|
| ` : '-'}
|
| </td>
|
| </tr>
|
| `;
|
| }
|
|
|
| function openModal(imageElement) {
|
| const modal = document.getElementById('image-modal');
|
| const modalImage = document.getElementById('modal-image');
|
|
|
|
|
| modalImage.src = imageElement.src;
|
| modal.style.display = 'flex';
|
|
|
|
|
| document.addEventListener('click', closeModalOnClickOutside);
|
| }
|
|
|
| function closeModal() {
|
| const modal = document.getElementById('image-modal');
|
| modal.style.display = 'none';
|
|
|
|
|
| document.removeEventListener('click', closeModalOnClickOutside);
|
| }
|
|
|
| function closeModalOnClickOutside(event) {
|
| const modal = document.getElementById('image-modal');
|
| const modalContent = document.querySelector('.modal-content');
|
|
|
|
|
| if (!modalContent.contains(event.target)) {
|
| closeModal();
|
| }
|
| }
|
|
|
| function handleEscKey(event) {
|
| if (event.key === 'Escape') {
|
| closeModal();
|
| }
|
| }
|
|
|
|
|
| document.addEventListener('keydown', handleEscKey);
|
|
|
| async function processNextImage(filesProcessed, totalFiles) {
|
| try {
|
| const response = await fetch('/classify_multiple', { method: 'POST' });
|
| const result = await response.json();
|
|
|
| if (result.error) {
|
| throw new Error(result.error);
|
| }
|
|
|
| if (result.filename) {
|
|
|
| const row = document.getElementById(`result-row-${filesProcessed}`);
|
| if (row) {
|
| row.outerHTML = createTableRow(result, filesProcessed);
|
| updateSummary();
|
| }
|
|
|
|
|
| if (filesProcessed + 1 < totalFiles) {
|
| setTimeout(() => {
|
| processNextImage(filesProcessed + 1, totalFiles);
|
| }, 100);
|
| } else {
|
| finishClassification();
|
| }
|
| } else {
|
| finishClassification();
|
| }
|
| } catch (error) {
|
| console.error('Error processing image:', error);
|
| showError(filesProcessed);
|
| finishClassification();
|
| }
|
| }
|
|
|
| function finishClassification() {
|
| isClassifying = false;
|
| const classifyBtn = document.getElementById('classify-btn');
|
| classifyBtn.disabled = false;
|
| classifyBtn.innerHTML = '<i class="fas fa-tags"></i> Classify';
|
| }
|
|
|
| async function highlightClassification() {
|
| if (isClassifying) {
|
| alert('Classification in progress...');
|
| return;
|
| }
|
|
|
| const resultsBody = document.getElementById('results-tbody');
|
| const myDropzone = Dropzone.forElement("#upload-form");
|
|
|
| if (!myDropzone.files.length) {
|
| alert("Please upload some images first!");
|
| return;
|
| }
|
|
|
|
|
| isClassifying = true;
|
| const classifyBtn = document.getElementById('classify-btn');
|
| classifyBtn.disabled = true;
|
| classifyBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Classifying...';
|
|
|
|
|
| resultsBody.innerHTML = '';
|
|
|
|
|
| try {
|
| await fetch('/clear_uploads', { method: 'POST' });
|
|
|
|
|
| for (const file of myDropzone.files) {
|
| const formData = new FormData();
|
| formData.append('file', file);
|
| await fetch('/upload_multiple', {
|
| method: 'POST',
|
| body: formData
|
| });
|
| }
|
|
|
|
|
| myDropzone.files.forEach((file, index) => {
|
| resultsBody.insertAdjacentHTML('beforeend', `
|
| <tr id="result-row-${index}">
|
| <td class="serial">${index + 1}</td>
|
| <td class="image-col">
|
| <div class="filename">${file.name}</div>
|
| <div style="color: var(--text-secondary);">Waiting...</div>
|
| </td>
|
| <td class="result-col">
|
| <i class="fas fa-clock"></i>
|
| </td>
|
| <td class="labels-col">
|
| <i class="fas fa-clock"></i>
|
| </td>
|
| </tr>
|
| `);
|
| });
|
|
|
|
|
| processNextImage(0, myDropzone.files.length);
|
|
|
| } catch (error) {
|
| console.error('Error during classification:', error);
|
| alert('Error during classification. Please try again.');
|
| finishClassification();
|
| }
|
| }
|
|
|
| function showError(index) {
|
| const row = document.getElementById(`result-row-${index}`);
|
| if (row) {
|
| row.innerHTML = `
|
| <td class="serial">${index + 1}</td>
|
| <td colspan="3" style="color: var(--error-color);">
|
| Error processing image. Please try again.
|
| </td>
|
| `;
|
| }
|
| }
|
|
|
| function toggleLabels(labelsDiv) {
|
| labelsDiv.classList.toggle('expanded');
|
| }
|
|
|
| document.getElementById('image-modal').addEventListener('click', function(e) {
|
| if (e.target === this) {
|
| closeModal();
|
| }
|
| });
|
|
|
| document.addEventListener('click', function(e) {
|
| if (e.target.classList.contains('thumbnail')) {
|
| e.preventDefault();
|
| const box = document.getElementById('thumbnail-preview-box');
|
| const img = document.getElementById('thumbnail-preview-img');
|
| img.src = e.target.src;
|
| box.style.display = 'block';
|
| box.style.left = (e.pageX + 10) + 'px';
|
| box.style.top = (e.pageY + 10) + 'px';
|
|
|
| document.addEventListener('keydown', hideOnEscape);
|
| } else if (!e.target.closest('#thumbnail-preview-box')) {
|
| hidePreview();
|
| }
|
| });
|
|
|
| function hideOnEscape(e) {
|
| if (e.key === 'Escape') {
|
| hidePreview();
|
| }
|
| }
|
|
|
| function hidePreview() {
|
| const box = document.getElementById('thumbnail-preview-box');
|
| box.style.display = 'none';
|
| document.removeEventListener('keydown', hideOnEscape);
|
| }
|
|
|
|
|
| function downloadPageAsHTML() {
|
| const now = new Date();
|
| const date = now.toLocaleDateString('en-CA');
|
| const time = now.toLocaleTimeString('en-GB')
|
| .replace(/:/g, '-')
|
| .split('.')[0];
|
|
|
|
|
| const btn = document.getElementById('download-html-btn');
|
| const originalBtnText = btn.innerHTML;
|
| btn.disabled = true;
|
| btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Preparing report...';
|
|
|
| try {
|
|
|
| const summaryContent = document.getElementById('summary').innerHTML;
|
|
|
|
|
| const rows = document.querySelectorAll('#results-tbody tr');
|
| if (!rows.length) {
|
| throw new Error('No data to save');
|
| }
|
|
|
| const tableData = Array.from(rows)
|
| .filter(row => row.querySelector('.result-col span').textContent === 'Fail')
|
| .map(row => ({
|
| serialNo: row.querySelector('.serial').textContent,
|
| image: row.querySelector('.thumbnail').src,
|
| filename: row.querySelector('.filename').textContent,
|
| status: row.querySelector('.result-col span').textContent,
|
| labels: row.querySelector('.labels') ?
|
| Array.from(row.querySelector('.labels').querySelectorAll('.label'))
|
| .map(label => label.textContent) : []
|
| }));
|
|
|
| if (tableData.length === 0) {
|
| alert('No failed images to save!');
|
| return;
|
| }
|
|
|
|
|
| const CHUNK_SIZE = 5;
|
| const chunks = [];
|
| for (let i = 0; i < tableData.length; i += CHUNK_SIZE) {
|
| chunks.push(tableData.slice(i, i + CHUNK_SIZE));
|
| }
|
|
|
|
|
| let currentChunk = 0;
|
| const totalChunks = chunks.length;
|
|
|
| async function sendChunk() {
|
| try {
|
|
|
| btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Saving ${currentChunk + 1}/${totalChunks}`;
|
|
|
| const response = await fetch('/save_report_chunk', {
|
| method: 'POST',
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| },
|
| body: JSON.stringify({
|
| chunkNumber: currentChunk,
|
| totalChunks: totalChunks,
|
| date: date,
|
| time: time,
|
| summary: currentChunk === 0 ? summaryContent : '',
|
| chunk: chunks[currentChunk]
|
| })
|
| });
|
|
|
| if (!response.ok) {
|
| const error = await response.json();
|
| throw new Error(error.error || 'Failed to save report');
|
| }
|
|
|
| const result = await response.json();
|
|
|
| if (result.error) {
|
| throw new Error(result.error);
|
| }
|
|
|
| if (currentChunk < totalChunks - 1) {
|
| currentChunk++;
|
| await sendChunk();
|
| } else {
|
| alert(`Report saved successfully in Reports/${date}/Report_${date}_${time}.html`);
|
| btn.innerHTML = originalBtnText;
|
| btn.disabled = false;
|
| }
|
| } catch (error) {
|
| console.error('Error:', error);
|
| alert('Error saving report: ' + error.message);
|
| btn.innerHTML = originalBtnText;
|
| btn.disabled = false;
|
| }
|
| }
|
|
|
|
|
| sendChunk().catch(error => {
|
| console.error('Error in chunk processing:', error);
|
| alert('Error saving report: ' + error.message);
|
| btn.innerHTML = originalBtnText;
|
| btn.disabled = false;
|
| });
|
|
|
| } catch (error) {
|
| console.error('Error:', error);
|
| alert('Error preparing report: ' + error.message);
|
| btn.innerHTML = originalBtnText;
|
| btn.disabled = false;
|
| }
|
| }
|
| </script>
|
| </body>
|
| </html> |