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