Download templates/multiple.html from devranx/PRISM: direct link, hf CLI and curl.
- Browser
- Download file 40.4 kB
-
https://huggingface.co/spaces/devranx/PRISM/resolve/main/templates/multiple.html
- Command line
-
hf download hf://spaces/devranx/PRISM/templates/multiple.html
-
curl -L -o multiple.html https://huggingface.co/spaces/devranx/PRISM/resolve/main/templates/multiple.html
40.4 kB
| <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 ; | |
| height: 120px ; | |
| border-radius: 0.75rem ; | |
| 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 ; | |
| } | |
| .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 ; | |
| 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 ; | |
| } | |
| .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 ; | |
| } | |
| .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> | |
| <!-- New Download HTML 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> | |
| <!-- Loading Overlay --> | |
| <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"> | |
| <!-- Loading file items will be added here dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Image Modal --> | |
| <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; // Track if classification is in progress | |
| Dropzone.options.uploadForm = { | |
| paramName: "file", | |
| maxFilesize: 16, | |
| acceptedFiles: "image/*", | |
| previewsContainer: false, | |
| init: function() { | |
| let myDropzone = this; | |
| // Add event handler for when files are dropped or selected | |
| this.on("addedfile", function(file) { | |
| // If this is the first file in a new batch | |
| if (myDropzone.files.length === 1) { | |
| // Clear previous results | |
| document.getElementById('results-tbody').innerHTML = ''; | |
| document.getElementById('summary').innerHTML = ''; | |
| document.getElementById('upload-progress').style.width = '0'; | |
| // Clear server-side uploads | |
| 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 + "%"; | |
| }); | |
| // Add event handler for when the dropzone is clicked | |
| this.element.querySelector(".dz-message").addEventListener("click", function() { | |
| // Clear everything before opening file dialog | |
| myDropzone.removeAllFiles(true); | |
| document.getElementById('results-tbody').innerHTML = ''; | |
| document.getElementById('summary').innerHTML = ''; | |
| document.getElementById('upload-progress').style.width = '0'; | |
| // Clear server-side uploads | |
| 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 => { | |
| // Clear table | |
| document.getElementById('results-tbody').innerHTML = ''; | |
| document.getElementById('summary').innerHTML = ''; | |
| document.getElementById('upload-progress').style.width = '0'; | |
| // Clear dropzone | |
| let myDropzone = Dropzone.forElement("#upload-form"); | |
| myDropzone.removeAllFiles(true); | |
| // Remove any remaining previews manually | |
| 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'); | |
| // Set the modal image source to the clicked thumbnail's source | |
| modalImage.src = imageElement.src; | |
| modal.style.display = 'flex'; // Show the modal | |
| // Add event listener to close the modal when clicking outside the image | |
| document.addEventListener('click', closeModalOnClickOutside); | |
| } | |
| function closeModal() { | |
| const modal = document.getElementById('image-modal'); | |
| modal.style.display = 'none'; // Hide the modal | |
| // Remove the event listener for closing the modal | |
| document.removeEventListener('click', closeModalOnClickOutside); | |
| } | |
| function closeModalOnClickOutside(event) { | |
| const modal = document.getElementById('image-modal'); | |
| const modalContent = document.querySelector('.modal-content'); | |
| // Check if the click is outside the modal content | |
| if (!modalContent.contains(event.target)) { | |
| closeModal(); | |
| } | |
| } | |
| function handleEscKey(event) { | |
| if (event.key === 'Escape') { | |
| closeModal(); | |
| } | |
| } | |
| // Add event listener for Escape key | |
| 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) { | |
| // Update the corresponding row | |
| const row = document.getElementById(`result-row-${filesProcessed}`); | |
| if (row) { | |
| row.outerHTML = createTableRow(result, filesProcessed); | |
| updateSummary(); | |
| } | |
| // Process next image if more remain | |
| 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; | |
| } | |
| // Reset classification state and UI | |
| isClassifying = true; | |
| const classifyBtn = document.getElementById('classify-btn'); | |
| classifyBtn.disabled = true; | |
| classifyBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Classifying...'; | |
| // Clear previous results | |
| resultsBody.innerHTML = ''; | |
| // Reset server-side state | |
| try { | |
| await fetch('/clear_uploads', { method: 'POST' }); | |
| // Re-upload all files | |
| for (const file of myDropzone.files) { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| await fetch('/upload_multiple', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| } | |
| // Create placeholder rows | |
| 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> | |
| `); | |
| }); | |
| // Start processing | |
| 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 to download the entire page as HTML | |
| function downloadPageAsHTML() { | |
| const now = new Date(); | |
| const date = now.toLocaleDateString('en-CA'); | |
| const time = now.toLocaleTimeString('en-GB') | |
| .replace(/:/g, '-') | |
| .split('.')[0]; | |
| // Show progress message | |
| 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 { | |
| // Get summary content once | |
| const summaryContent = document.getElementById('summary').innerHTML; | |
| // Collect only failed images | |
| 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; | |
| } | |
| // Split data into smaller chunks (5 rows per chunk) | |
| const CHUNK_SIZE = 5; | |
| const chunks = []; | |
| for (let i = 0; i < tableData.length; i += CHUNK_SIZE) { | |
| chunks.push(tableData.slice(i, i + CHUNK_SIZE)); | |
| } | |
| // Progress tracking | |
| let currentChunk = 0; | |
| const totalChunks = chunks.length; | |
| async function sendChunk() { | |
| try { | |
| // Update button text with progress | |
| 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; | |
| } | |
| } | |
| // Start sending chunks | |
| 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> |