SakibAhmed commited on
Commit
ac8c4df
·
verified ·
1 Parent(s): 5c8508a

Upload 4 files

Browse files
templates/index - Copy (2).html ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>YOLO Vision AI</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
17
+ min-height: 100vh;
18
+ overflow-x: hidden;
19
+ position: relative;
20
+ }
21
+
22
+ /* Animated background particles */
23
+ .particles {
24
+ position: absolute;
25
+ width: 100%;
26
+ height: 100%;
27
+ overflow: hidden;
28
+ z-index: 0;
29
+ }
30
+
31
+ .particle {
32
+ position: absolute;
33
+ width: 2px;
34
+ height: 2px;
35
+ background: #00d4ff;
36
+ border-radius: 50%;
37
+ animation: float 6s ease-in-out infinite;
38
+ opacity: 0.6;
39
+ }
40
+
41
+ @keyframes float {
42
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
43
+ 50% { transform: translateY(-20px) rotate(180deg); }
44
+ }
45
+
46
+ .container {
47
+ position: relative;
48
+ z-index: 1;
49
+ max-width: 800px;
50
+ margin: 0 auto;
51
+ padding: 2rem;
52
+ min-height: 100vh;
53
+ display: flex;
54
+ flex-direction: column;
55
+ justify-content: center;
56
+ align-items: center;
57
+ }
58
+
59
+ .header {
60
+ text-align: center;
61
+ margin-bottom: 3rem;
62
+ animation: slideDown 1s ease-out;
63
+ }
64
+
65
+ .title {
66
+ font-size: 3.5rem;
67
+ font-weight: 700;
68
+ background: linear-gradient(45deg, #00d4ff, #ff00ff, #00ff88);
69
+ background-size: 200% 200%;
70
+ -webkit-background-clip: text;
71
+ -webkit-text-fill-color: transparent;
72
+ background-clip: text;
73
+ animation: gradientShift 3s ease-in-out infinite;
74
+ margin-bottom: 1rem;
75
+ text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
76
+ }
77
+
78
+ .subtitle {
79
+ font-size: 1.2rem;
80
+ color: #a0a0a0;
81
+ font-weight: 300;
82
+ }
83
+
84
+ .upload-area {
85
+ width: 100%;
86
+ max-width: 500px;
87
+ min-height: 300px;
88
+ border: 2px dashed #00d4ff;
89
+ border-radius: 20px;
90
+ background: rgba(0, 212, 255, 0.05);
91
+ backdrop-filter: blur(10px);
92
+ display: flex;
93
+ flex-direction: column;
94
+ justify-content: center;
95
+ align-items: center;
96
+ cursor: pointer;
97
+ transition: all 0.3s ease;
98
+ position: relative;
99
+ overflow: hidden;
100
+ animation: slideUp 1s ease-out 0.3s both;
101
+ }
102
+
103
+ .upload-area:hover {
104
+ border-color: #ff00ff;
105
+ background: rgba(255, 0, 255, 0.05);
106
+ transform: translateY(-5px);
107
+ box-shadow: 0 20px 40px rgba(0, 212, 255, 0.2);
108
+ }
109
+
110
+ .upload-area.dragover {
111
+ border-color: #00ff88;
112
+ background: rgba(0, 255, 136, 0.1);
113
+ transform: scale(1.02);
114
+ }
115
+
116
+ .upload-icon {
117
+ font-size: 4rem;
118
+ color: #00d4ff;
119
+ margin-bottom: 1rem;
120
+ transition: all 0.3s ease;
121
+ }
122
+
123
+ .upload-area:hover .upload-icon {
124
+ color: #ff00ff;
125
+ transform: scale(1.1);
126
+ }
127
+
128
+ .upload-text {
129
+ color: #ffffff;
130
+ font-size: 1.1rem;
131
+ margin-bottom: 0.5rem;
132
+ font-weight: 500;
133
+ }
134
+
135
+ .upload-subtext {
136
+ color: #a0a0a0;
137
+ font-size: 0.9rem;
138
+ }
139
+
140
+ .file-input {
141
+ display: none;
142
+ }
143
+
144
+ .preview-container {
145
+ margin-top: 2rem;
146
+ text-align: center;
147
+ animation: fadeIn 0.5s ease-out;
148
+ }
149
+
150
+ .preview-image {
151
+ max-width: 100%;
152
+ max-height: 300px;
153
+ border-radius: 15px;
154
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
155
+ border: 2px solid #00d4ff;
156
+ }
157
+
158
+ .predict-button {
159
+ background: linear-gradient(45deg, #00d4ff, #0099cc);
160
+ border: none;
161
+ color: white;
162
+ padding: 15px 40px;
163
+ font-size: 1.1rem;
164
+ font-weight: 600;
165
+ border-radius: 50px;
166
+ cursor: pointer;
167
+ margin-top: 1.5rem;
168
+ transition: all 0.3s ease;
169
+ text-transform: uppercase;
170
+ letter-spacing: 1px;
171
+ position: relative;
172
+ overflow: hidden;
173
+ }
174
+
175
+ .predict-button:hover {
176
+ transform: translateY(-2px);
177
+ box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4);
178
+ background: linear-gradient(45deg, #ff00ff, #cc0099);
179
+ }
180
+
181
+ .predict-button:disabled {
182
+ opacity: 0.6;
183
+ cursor: not-allowed;
184
+ transform: none;
185
+ }
186
+
187
+ .loading {
188
+ display: none;
189
+ margin-top: 1rem;
190
+ }
191
+
192
+ .spinner {
193
+ width: 40px;
194
+ height: 40px;
195
+ border: 4px solid rgba(0, 212, 255, 0.3);
196
+ border-top: 4px solid #00d4ff;
197
+ border-radius: 50%;
198
+ animation: spin 1s linear infinite;
199
+ margin: 0 auto;
200
+ }
201
+
202
+ .result-container {
203
+ margin-top: 2rem;
204
+ padding: 2rem;
205
+ background: rgba(255, 255, 255, 0.05);
206
+ backdrop-filter: blur(15px);
207
+ border-radius: 20px;
208
+ border: 1px solid rgba(0, 212, 255, 0.3);
209
+ animation: slideUp 0.5s ease-out;
210
+ text-align: center;
211
+ }
212
+
213
+ .result-class {
214
+ font-size: 2rem;
215
+ font-weight: 700;
216
+ color: #00ff88;
217
+ margin-bottom: 1rem;
218
+ text-transform: uppercase;
219
+ letter-spacing: 2px;
220
+ }
221
+
222
+ .result-confidence {
223
+ font-size: 1.2rem;
224
+ color: #ffffff;
225
+ margin-bottom: 0.5rem;
226
+ }
227
+
228
+ .confidence-bar {
229
+ width: 100%;
230
+ height: 10px;
231
+ background: rgba(255, 255, 255, 0.1);
232
+ border-radius: 5px;
233
+ overflow: hidden;
234
+ margin-top: 1rem;
235
+ }
236
+
237
+ .confidence-fill {
238
+ height: 100%;
239
+ background: linear-gradient(90deg, #00d4ff, #00ff88);
240
+ border-radius: 5px;
241
+ transition: width 1s ease-out;
242
+ animation: pulse 2s ease-in-out infinite;
243
+ }
244
+
245
+ .error {
246
+ color: #ff4444;
247
+ background: rgba(255, 68, 68, 0.1);
248
+ padding: 1rem;
249
+ border-radius: 10px;
250
+ border: 1px solid #ff4444;
251
+ margin-top: 1rem;
252
+ }
253
+
254
+ @keyframes slideDown {
255
+ from { opacity: 0; transform: translateY(-50px); }
256
+ to { opacity: 1; transform: translateY(0); }
257
+ }
258
+
259
+ @keyframes slideUp {
260
+ from { opacity: 0; transform: translateY(50px); }
261
+ to { opacity: 1; transform: translateY(0); }
262
+ }
263
+
264
+ @keyframes fadeIn {
265
+ from { opacity: 0; }
266
+ to { opacity: 1; }
267
+ }
268
+
269
+ @keyframes spin {
270
+ 0% { transform: rotate(0deg); }
271
+ 100% { transform: rotate(360deg); }
272
+ }
273
+
274
+ @keyframes gradientShift {
275
+ 0%, 100% { background-position: 0% 50%; }
276
+ 50% { background-position: 100% 50%; }
277
+ }
278
+
279
+ @keyframes pulse {
280
+ 0%, 100% { opacity: 1; }
281
+ 50% { opacity: 0.7; }
282
+ }
283
+
284
+ /* Responsive design */
285
+ @media (max-width: 768px) {
286
+ .title {
287
+ font-size: 2.5rem;
288
+ }
289
+
290
+ .container {
291
+ padding: 1rem;
292
+ }
293
+
294
+ .upload-area {
295
+ min-height: 250px;
296
+ }
297
+ }
298
+ </style>
299
+ </head>
300
+ <body>
301
+ <div class="particles" id="particles"></div>
302
+
303
+ <div class="container">
304
+ <div class="header">
305
+ <h1 class="title">YOLO12 Vision AI</h1>
306
+ <p class="subtitle">Advanced Image Classification with Neural Networks</p>
307
+ </div>
308
+
309
+ <div class="upload-area" id="uploadArea">
310
+ <div class="upload-icon">🔮</div>
311
+ <div class="upload-text">Drop your image here or click to upload</div>
312
+ <div class="upload-subtext">Supports PNG, JPG, JPEG formats</div>
313
+ <input type="file" id="fileInput" class="file-input" accept=".png,.jpg,.jpeg">
314
+ </div>
315
+
316
+ <div class="preview-container" id="previewContainer" style="display: none;">
317
+ <img id="previewImage" class="preview-image" alt="Preview">
318
+ <button class="predict-button" id="predictButton">🚀 Analyze Image</button>
319
+ </div>
320
+
321
+ <div class="loading" id="loading">
322
+ <div class="spinner"></div>
323
+ <p style="color: #00d4ff; margin-top: 1rem;">Processing your image...</p>
324
+ </div>
325
+
326
+ <div class="result-container" id="resultContainer" style="display: none;">
327
+ <div class="result-class" id="resultClass"></div>
328
+ <div class="result-confidence">
329
+ Confidence: <span id="confidencePercentage"></span>
330
+ </div>
331
+ <div class="confidence-bar">
332
+ <div class="confidence-fill" id="confidenceFill"></div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="error" id="errorContainer" style="display: none;"></div>
337
+ </div>
338
+
339
+ <script>
340
+ // Create animated particles
341
+ function createParticles() {
342
+ const particlesContainer = document.getElementById('particles');
343
+ const particleCount = 50;
344
+
345
+ for (let i = 0; i < particleCount; i++) {
346
+ const particle = document.createElement('div');
347
+ particle.className = 'particle';
348
+ particle.style.left = Math.random() * 100 + '%';
349
+ particle.style.top = Math.random() * 100 + '%';
350
+ particle.style.animationDelay = Math.random() * 6 + 's';
351
+ particle.style.animationDuration = (3 + Math.random() * 3) + 's';
352
+ particlesContainer.appendChild(particle);
353
+ }
354
+ }
355
+
356
+ // Initialize particles
357
+ createParticles();
358
+
359
+ // DOM elements
360
+ const uploadArea = document.getElementById('uploadArea');
361
+ const fileInput = document.getElementById('fileInput');
362
+ const previewContainer = document.getElementById('previewContainer');
363
+ const previewImage = document.getElementById('previewImage');
364
+ const predictButton = document.getElementById('predictButton');
365
+ const loading = document.getElementById('loading');
366
+ const resultContainer = document.getElementById('resultContainer');
367
+ const errorContainer = document.getElementById('errorContainer');
368
+ const resultClass = document.getElementById('resultClass');
369
+ const confidencePercentage = document.getElementById('confidencePercentage');
370
+ const confidenceFill = document.getElementById('confidenceFill');
371
+
372
+ let selectedFile = null;
373
+
374
+ // Upload area click handler
375
+ uploadArea.addEventListener('click', () => {
376
+ fileInput.click();
377
+ });
378
+
379
+ // File input change handler
380
+ fileInput.addEventListener('change', handleFileSelect);
381
+
382
+ // Drag and drop handlers
383
+ uploadArea.addEventListener('dragover', (e) => {
384
+ e.preventDefault();
385
+ uploadArea.classList.add('dragover');
386
+ });
387
+
388
+ uploadArea.addEventListener('dragleave', () => {
389
+ uploadArea.classList.remove('dragover');
390
+ });
391
+
392
+ uploadArea.addEventListener('drop', (e) => {
393
+ e.preventDefault();
394
+ uploadArea.classList.remove('dragover');
395
+ const files = e.dataTransfer.files;
396
+ if (files.length > 0) {
397
+ handleFile(files[0]);
398
+ }
399
+ });
400
+
401
+ // Predict button handler
402
+ predictButton.addEventListener('click', predictImage);
403
+
404
+ function handleFileSelect(e) {
405
+ const file = e.target.files[0];
406
+ if (file) {
407
+ handleFile(file);
408
+ }
409
+ }
410
+
411
+ function handleFile(file) {
412
+ // Validate file type
413
+ const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
414
+ if (!allowedTypes.includes(file.type)) {
415
+ showError('Please select a valid image file (PNG, JPG, JPEG)');
416
+ return;
417
+ }
418
+
419
+ // Validate file size (max 10MB)
420
+ if (file.size > 10 * 1024 * 1024) {
421
+ showError('File size too large. Please select a file smaller than 10MB.');
422
+ return;
423
+ }
424
+
425
+ selectedFile = file;
426
+
427
+ // Show preview
428
+ const reader = new FileReader();
429
+ reader.onload = (e) => {
430
+ previewImage.src = e.target.result;
431
+ previewContainer.style.display = 'block';
432
+ hideError();
433
+ hideResult();
434
+ };
435
+ reader.readAsDataURL(file);
436
+ }
437
+
438
+ async function predictImage() {
439
+ if (!selectedFile) {
440
+ showError('Please select an image first');
441
+ return;
442
+ }
443
+
444
+ // Show loading
445
+ loading.style.display = 'block';
446
+ predictButton.disabled = true;
447
+ hideError();
448
+ hideResult();
449
+
450
+ try {
451
+ const formData = new FormData();
452
+ formData.append('file', selectedFile);
453
+
454
+ const response = await fetch('/predict', {
455
+ method: 'POST',
456
+ body: formData
457
+ });
458
+
459
+ const data = await response.json();
460
+
461
+ if (response.ok) {
462
+ showResult(data);
463
+ } else {
464
+ showError(data.error || 'An error occurred during prediction');
465
+ }
466
+ } catch (error) {
467
+ showError('Failed to connect to the server. Please try again.');
468
+ console.error('Error:', error);
469
+ } finally {
470
+ loading.style.display = 'none';
471
+ predictButton.disabled = false;
472
+ }
473
+ }
474
+
475
+ function showResult(data) {
476
+ resultClass.textContent = data.class;
477
+ const confidence = Math.round(data.confidence * 100);
478
+ confidencePercentage.textContent = confidence + '%';
479
+ confidenceFill.style.width = confidence + '%';
480
+
481
+ resultContainer.style.display = 'block';
482
+
483
+ // Add some visual flair
484
+ setTimeout(() => {
485
+ confidenceFill.style.width = confidence + '%';
486
+ }, 100);
487
+ }
488
+
489
+ function showError(message) {
490
+ errorContainer.textContent = message;
491
+ errorContainer.style.display = 'block';
492
+ }
493
+
494
+ function hideError() {
495
+ errorContainer.style.display = 'none';
496
+ }
497
+
498
+ function hideResult() {
499
+ resultContainer.style.display = 'none';
500
+ }
501
+
502
+ // Add some interactive effects
503
+ document.addEventListener('mousemove', (e) => {
504
+ const particles = document.querySelectorAll('.particle');
505
+ const x = e.clientX / window.innerWidth;
506
+ const y = e.clientY / window.innerHeight;
507
+
508
+ particles.forEach((particle, index) => {
509
+ const speed = (index % 3 + 1) * 0.5;
510
+ const newX = x * speed;
511
+ const newY = y * speed;
512
+ particle.style.transform = `translate(${newX}px, ${newY}px)`;
513
+ });
514
+ });
515
+ </script>
516
+ </body>
517
+ </html>
templates/index - Copy (3).html ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>YOLO Vision AI</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
17
+ min-height: 100vh;
18
+ overflow-x: hidden;
19
+ position: relative;
20
+ }
21
+
22
+ /* Animated background particles */
23
+ .particles {
24
+ position: absolute;
25
+ width: 100%;
26
+ height: 100%;
27
+ overflow: hidden;
28
+ z-index: 0;
29
+ }
30
+
31
+ .particle {
32
+ position: absolute;
33
+ width: 2px;
34
+ height: 2px;
35
+ background: #00d4ff;
36
+ border-radius: 50%;
37
+ animation: float 6s ease-in-out infinite;
38
+ opacity: 0.6;
39
+ }
40
+
41
+ @keyframes float {
42
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
43
+ 50% { transform: translateY(-20px) rotate(180deg); }
44
+ }
45
+
46
+ .container {
47
+ position: relative;
48
+ z-index: 1;
49
+ max-width: 800px;
50
+ margin: 0 auto;
51
+ padding: 2rem;
52
+ min-height: 100vh;
53
+ display: flex;
54
+ flex-direction: column;
55
+ justify-content: center;
56
+ align-items: center;
57
+ }
58
+
59
+ .header {
60
+ text-align: center;
61
+ margin-bottom: 3rem;
62
+ animation: slideDown 1s ease-out;
63
+ }
64
+
65
+ .title {
66
+ font-size: 3.5rem;
67
+ font-weight: 700;
68
+ background: linear-gradient(45deg, #00d4ff, #ff00ff, #00ff88);
69
+ background-size: 200% 200%;
70
+ -webkit-background-clip: text;
71
+ -webkit-text-fill-color: transparent;
72
+ background-clip: text;
73
+ animation: gradientShift 3s ease-in-out infinite;
74
+ margin-bottom: 1rem;
75
+ text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
76
+ }
77
+
78
+ .subtitle {
79
+ font-size: 1.2rem;
80
+ color: #a0a0a0;
81
+ font-weight: 300;
82
+ }
83
+
84
+ .upload-area {
85
+ width: 100%;
86
+ max-width: 500px;
87
+ min-height: 300px;
88
+ border: 2px dashed #00d4ff;
89
+ border-radius: 20px;
90
+ background: rgba(0, 212, 255, 0.05);
91
+ backdrop-filter: blur(10px);
92
+ display: flex;
93
+ flex-direction: column;
94
+ justify-content: center;
95
+ align-items: center;
96
+ cursor: pointer;
97
+ transition: all 0.3s ease;
98
+ position: relative;
99
+ overflow: hidden;
100
+ animation: slideUp 1s ease-out 0.3s both;
101
+ }
102
+
103
+ .upload-area:hover {
104
+ border-color: #ff00ff;
105
+ background: rgba(255, 0, 255, 0.05);
106
+ transform: translateY(-5px);
107
+ box-shadow: 0 20px 40px rgba(0, 212, 255, 0.2);
108
+ }
109
+
110
+ .upload-area.dragover {
111
+ border-color: #00ff88;
112
+ background: rgba(0, 255, 136, 0.1);
113
+ transform: scale(1.02);
114
+ }
115
+
116
+ .upload-icon {
117
+ font-size: 4rem;
118
+ color: #00d4ff;
119
+ margin-bottom: 1rem;
120
+ transition: all 0.3s ease;
121
+ }
122
+
123
+ .upload-area:hover .upload-icon {
124
+ color: #ff00ff;
125
+ transform: scale(1.1);
126
+ }
127
+
128
+ .upload-text {
129
+ color: #ffffff;
130
+ font-size: 1.1rem;
131
+ margin-bottom: 0.5rem;
132
+ font-weight: 500;
133
+ }
134
+
135
+ .upload-subtext {
136
+ color: #a0a0a0;
137
+ font-size: 0.9rem;
138
+ }
139
+
140
+ .file-input {
141
+ display: none;
142
+ }
143
+
144
+ .preview-container {
145
+ margin-top: 2rem;
146
+ text-align: center;
147
+ animation: fadeIn 0.5s ease-out;
148
+ }
149
+
150
+ .preview-image {
151
+ max-width: 100%;
152
+ max-height: 300px;
153
+ border-radius: 15px;
154
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
155
+ border: 2px solid #00d4ff;
156
+ }
157
+
158
+ .predict-button {
159
+ background: linear-gradient(45deg, #00d4ff, #0099cc);
160
+ border: none;
161
+ color: white;
162
+ padding: 15px 40px;
163
+ font-size: 1.1rem;
164
+ font-weight: 600;
165
+ border-radius: 50px;
166
+ cursor: pointer;
167
+ margin-top: 1.5rem;
168
+ transition: all 0.3s ease;
169
+ text-transform: uppercase;
170
+ letter-spacing: 1px;
171
+ position: relative;
172
+ overflow: hidden;
173
+ }
174
+
175
+ .predict-button:hover {
176
+ transform: translateY(-2px);
177
+ box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4);
178
+ background: linear-gradient(45deg, #ff00ff, #cc0099);
179
+ }
180
+
181
+ .predict-button:disabled {
182
+ opacity: 0.6;
183
+ cursor: not-allowed;
184
+ transform: none;
185
+ }
186
+
187
+ .loading {
188
+ display: none;
189
+ margin-top: 1rem;
190
+ }
191
+
192
+ .spinner {
193
+ width: 40px;
194
+ height: 40px;
195
+ border: 4px solid rgba(0, 212, 255, 0.3);
196
+ border-top: 4px solid #00d4ff;
197
+ border-radius: 50%;
198
+ animation: spin 1s linear infinite;
199
+ margin: 0 auto;
200
+ }
201
+
202
+ .result-container {
203
+ margin-top: 2rem;
204
+ padding: 2rem;
205
+ background: rgba(255, 255, 255, 0.05);
206
+ backdrop-filter: blur(15px);
207
+ border-radius: 20px;
208
+ border: 1px solid rgba(0, 212, 255, 0.3);
209
+ animation: slideUp 0.5s ease-out;
210
+ text-align: center;
211
+ }
212
+
213
+ .result-class {
214
+ font-size: 2rem;
215
+ font-weight: 700;
216
+ color: #00ff88;
217
+ margin-bottom: 1rem;
218
+ text-transform: uppercase;
219
+ letter-spacing: 2px;
220
+ }
221
+
222
+ .result-confidence {
223
+ font-size: 1.2rem;
224
+ color: #ffffff;
225
+ margin-bottom: 0.5rem;
226
+ }
227
+
228
+ .confidence-bar {
229
+ width: 100%;
230
+ height: 10px;
231
+ background: rgba(255, 255, 255, 0.1);
232
+ border-radius: 5px;
233
+ overflow: hidden;
234
+ margin-top: 1rem;
235
+ }
236
+
237
+ .confidence-fill {
238
+ height: 100%;
239
+ background: linear-gradient(90deg, #00d4ff, #00ff88);
240
+ border-radius: 5px;
241
+ transition: width 1s ease-out;
242
+ animation: pulse 2s ease-in-out infinite;
243
+ }
244
+
245
+ .error {
246
+ color: #ff4444;
247
+ background: rgba(255, 68, 68, 0.1);
248
+ padding: 1rem;
249
+ border-radius: 10px;
250
+ border: 1px solid #ff4444;
251
+ margin-top: 1rem;
252
+ }
253
+
254
+ @keyframes slideDown {
255
+ from { opacity: 0; transform: translateY(-50px); }
256
+ to { opacity: 1; transform: translateY(0); }
257
+ }
258
+
259
+ @keyframes slideUp {
260
+ from { opacity: 0; transform: translateY(50px); }
261
+ to { opacity: 1; transform: translateY(0); }
262
+ }
263
+
264
+ @keyframes fadeIn {
265
+ from { opacity: 0; }
266
+ to { opacity: 1; }
267
+ }
268
+
269
+ @keyframes spin {
270
+ 0% { transform: rotate(0deg); }
271
+ 100% { transform: rotate(360deg); }
272
+ }
273
+
274
+ @keyframes gradientShift {
275
+ 0%, 100% { background-position: 0% 50%; }
276
+ 50% { background-position: 100% 50%; }
277
+ }
278
+
279
+ @keyframes pulse {
280
+ 0%, 100% { opacity: 1; }
281
+ 50% { opacity: 0.7; }
282
+ }
283
+
284
+ /* Responsive design */
285
+ @media (max-width: 768px) {
286
+ .title {
287
+ font-size: 2.5rem;
288
+ }
289
+
290
+ .container {
291
+ padding: 1rem;
292
+ }
293
+
294
+ .upload-area {
295
+ min-height: 250px;
296
+ }
297
+ }
298
+ </style>
299
+ </head>
300
+ <body>
301
+ <div class="particles" id="particles"></div>
302
+
303
+ <div class="container">
304
+ <div class="header">
305
+ <h1 class="title">YOLO12 Vision AI</h1>
306
+ <p class="subtitle">Advanced Image Classification with Neural Networks</p>
307
+ </div>
308
+
309
+ <div class="upload-area" id="uploadArea">
310
+ <div class="upload-icon">🔮</div>
311
+ <div class="upload-text">Drop your image here or click to upload</div>
312
+ <div class="upload-subtext">Supports PNG, JPG, JPEG formats</div>
313
+ <input type="file" id="fileInput" class="file-input" accept=".png,.jpg,.jpeg">
314
+ </div>
315
+
316
+ <div class="preview-container" id="previewContainer" style="display: none;">
317
+ <img id="previewImage" class="preview-image" alt="Preview">
318
+ <button class="predict-button" id="predictButton">🚀 Analyze Image</button>
319
+ </div>
320
+
321
+ <div class="loading" id="loading">
322
+ <div class="spinner"></div>
323
+ <p style="color: #00d4ff; margin-top: 1rem;">Processing your image...</p>
324
+ </div>
325
+
326
+ <div class="result-container" id="resultContainer" style="display: none;">
327
+ <div class="result-class" id="resultClass"></div>
328
+ <div class="result-confidence">
329
+ Confidence: <span id="confidencePercentage"></span>
330
+ </div>
331
+ <div class="confidence-bar">
332
+ <div class="confidence-fill" id="confidenceFill"></div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="error" id="errorContainer" style="display: none;"></div>
337
+ </div>
338
+
339
+ <script>
340
+ // Create animated particles
341
+ function createParticles() {
342
+ const particlesContainer = document.getElementById('particles');
343
+ const particleCount = 50;
344
+
345
+ for (let i = 0; i < particleCount; i++) {
346
+ const particle = document.createElement('div');
347
+ particle.className = 'particle';
348
+ particle.style.left = Math.random() * 100 + '%';
349
+ particle.style.top = Math.random() * 100 + '%';
350
+ particle.style.animationDelay = Math.random() * 6 + 's';
351
+ particle.style.animationDuration = (3 + Math.random() * 3) + 's';
352
+ particlesContainer.appendChild(particle);
353
+ }
354
+ }
355
+
356
+ // Initialize particles
357
+ createParticles();
358
+
359
+ // DOM elements
360
+ const uploadArea = document.getElementById('uploadArea');
361
+ const fileInput = document.getElementById('fileInput');
362
+ const previewContainer = document.getElementById('previewContainer');
363
+ const previewImage = document.getElementById('previewImage');
364
+ const predictButton = document.getElementById('predictButton');
365
+ const loading = document.getElementById('loading');
366
+ const resultContainer = document.getElementById('resultContainer');
367
+ const errorContainer = document.getElementById('errorContainer');
368
+ const resultClass = document.getElementById('resultClass');
369
+ const confidencePercentage = document.getElementById('confidencePercentage');
370
+ const confidenceFill = document.getElementById('confidenceFill');
371
+
372
+ let selectedFile = null;
373
+
374
+ // Upload area click handler
375
+ uploadArea.addEventListener('click', () => {
376
+ fileInput.click();
377
+ });
378
+
379
+ // File input change handler
380
+ fileInput.addEventListener('change', handleFileSelect);
381
+
382
+ // Drag and drop handlers
383
+ uploadArea.addEventListener('dragover', (e) => {
384
+ e.preventDefault();
385
+ uploadArea.classList.add('dragover');
386
+ });
387
+
388
+ uploadArea.addEventListener('dragleave', () => {
389
+ uploadArea.classList.remove('dragover');
390
+ });
391
+
392
+ uploadArea.addEventListener('drop', (e) => {
393
+ e.preventDefault();
394
+ uploadArea.classList.remove('dragover');
395
+ const files = e.dataTransfer.files;
396
+ if (files.length > 0) {
397
+ handleFile(files[0]);
398
+ }
399
+ });
400
+
401
+ // Predict button handler
402
+ predictButton.addEventListener('click', predictImage);
403
+
404
+ function handleFileSelect(e) {
405
+ const file = e.target.files[0];
406
+ if (file) {
407
+ handleFile(file);
408
+ }
409
+ }
410
+
411
+ function handleFile(file) {
412
+ // Validate file type
413
+ const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
414
+ if (!allowedTypes.includes(file.type)) {
415
+ showError('Please select a valid image file (PNG, JPG, JPEG)');
416
+ return;
417
+ }
418
+
419
+ // Validate file size (max 10MB)
420
+ if (file.size > 10 * 1024 * 1024) {
421
+ showError('File size too large. Please select a file smaller than 10MB.');
422
+ return;
423
+ }
424
+
425
+ selectedFile = file;
426
+
427
+ // Show preview
428
+ const reader = new FileReader();
429
+ reader.onload = (e) => {
430
+ previewImage.src = e.target.result;
431
+ previewContainer.style.display = 'block';
432
+ hideError();
433
+ hideResult();
434
+ };
435
+ reader.readAsDataURL(file);
436
+ }
437
+
438
+ async function predictImage() {
439
+ if (!selectedFile) {
440
+ showError('Please select an image first');
441
+ return;
442
+ }
443
+
444
+ // Show loading
445
+ loading.style.display = 'block';
446
+ predictButton.disabled = true;
447
+ hideError();
448
+ hideResult();
449
+
450
+ try {
451
+ const formData = new FormData();
452
+ formData.append('file', selectedFile);
453
+
454
+ const response = await fetch('/predict', {
455
+ method: 'POST',
456
+ body: formData
457
+ });
458
+
459
+ const data = await response.json();
460
+
461
+ if (response.ok) {
462
+ showResult(data);
463
+ } else {
464
+ showError(data.error || 'An error occurred during prediction');
465
+ }
466
+ } catch (error) {
467
+ showError('Failed to connect to the server. Please try again.');
468
+ console.error('Error:', error);
469
+ } finally {
470
+ loading.style.display = 'none';
471
+ predictButton.disabled = false;
472
+ }
473
+ }
474
+
475
+ function showResult(data) {
476
+ resultClass.textContent = data.class;
477
+ const confidence = Math.round(data.confidence * 100);
478
+ confidencePercentage.textContent = confidence + '%';
479
+ confidenceFill.style.width = confidence + '%';
480
+
481
+ resultContainer.style.display = 'block';
482
+
483
+ // Add some visual flair
484
+ setTimeout(() => {
485
+ confidenceFill.style.width = confidence + '%';
486
+ }, 100);
487
+ }
488
+
489
+ function showError(message) {
490
+ errorContainer.textContent = message;
491
+ errorContainer.style.display = 'block';
492
+ }
493
+
494
+ function hideError() {
495
+ errorContainer.style.display = 'none';
496
+ }
497
+
498
+ function hideResult() {
499
+ resultContainer.style.display = 'none';
500
+ }
501
+
502
+ // Add some interactive effects
503
+ document.addEventListener('mousemove', (e) => {
504
+ const particles = document.querySelectorAll('.particle');
505
+ const x = e.clientX / window.innerWidth;
506
+ const y = e.clientY / window.innerHeight;
507
+
508
+ particles.forEach((particle, index) => {
509
+ const speed = (index % 3 + 1) * 0.5;
510
+ const newX = x * speed;
511
+ const newY = y * speed;
512
+ particle.style.transform = `translate(${newX}px, ${newY}px)`;
513
+ });
514
+ });
515
+ </script>
516
+ </body>
517
+ </html>
templates/index - Copy.html ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>YOLO Vision AI</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
17
+ min-height: 100vh;
18
+ overflow-x: hidden;
19
+ position: relative;
20
+ }
21
+
22
+ /* Animated background particles */
23
+ .particles {
24
+ position: absolute;
25
+ width: 100%;
26
+ height: 100%;
27
+ overflow: hidden;
28
+ z-index: 0;
29
+ }
30
+
31
+ .particle {
32
+ position: absolute;
33
+ width: 2px;
34
+ height: 2px;
35
+ background: #00d4ff;
36
+ border-radius: 50%;
37
+ animation: float 6s ease-in-out infinite;
38
+ opacity: 0.6;
39
+ }
40
+
41
+ @keyframes float {
42
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
43
+ 50% { transform: translateY(-20px) rotate(180deg); }
44
+ }
45
+
46
+ .container {
47
+ position: relative;
48
+ z-index: 1;
49
+ max-width: 800px;
50
+ margin: 0 auto;
51
+ padding: 2rem;
52
+ min-height: 100vh;
53
+ display: flex;
54
+ flex-direction: column;
55
+ justify-content: center;
56
+ align-items: center;
57
+ }
58
+
59
+ .header {
60
+ text-align: center;
61
+ margin-bottom: 3rem;
62
+ animation: slideDown 1s ease-out;
63
+ }
64
+
65
+ .title {
66
+ font-size: 3.5rem;
67
+ font-weight: 700;
68
+ background: linear-gradient(45deg, #00d4ff, #ff00ff, #00ff88);
69
+ background-size: 200% 200%;
70
+ -webkit-background-clip: text;
71
+ -webkit-text-fill-color: transparent;
72
+ background-clip: text;
73
+ animation: gradientShift 3s ease-in-out infinite;
74
+ margin-bottom: 1rem;
75
+ text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
76
+ }
77
+
78
+ .subtitle {
79
+ font-size: 1.2rem;
80
+ color: #a0a0a0;
81
+ font-weight: 300;
82
+ }
83
+
84
+ .upload-area {
85
+ width: 100%;
86
+ max-width: 500px;
87
+ min-height: 300px;
88
+ border: 2px dashed #00d4ff;
89
+ border-radius: 20px;
90
+ background: rgba(0, 212, 255, 0.05);
91
+ backdrop-filter: blur(10px);
92
+ display: flex;
93
+ flex-direction: column;
94
+ justify-content: center;
95
+ align-items: center;
96
+ cursor: pointer;
97
+ transition: all 0.3s ease;
98
+ position: relative;
99
+ overflow: hidden;
100
+ animation: slideUp 1s ease-out 0.3s both;
101
+ }
102
+
103
+ .upload-area:hover {
104
+ border-color: #ff00ff;
105
+ background: rgba(255, 0, 255, 0.05);
106
+ transform: translateY(-5px);
107
+ box-shadow: 0 20px 40px rgba(0, 212, 255, 0.2);
108
+ }
109
+
110
+ .upload-area.dragover {
111
+ border-color: #00ff88;
112
+ background: rgba(0, 255, 136, 0.1);
113
+ transform: scale(1.02);
114
+ }
115
+
116
+ .upload-icon {
117
+ font-size: 4rem;
118
+ color: #00d4ff;
119
+ margin-bottom: 1rem;
120
+ transition: all 0.3s ease;
121
+ }
122
+
123
+ .upload-area:hover .upload-icon {
124
+ color: #ff00ff;
125
+ transform: scale(1.1);
126
+ }
127
+
128
+ .upload-text {
129
+ color: #ffffff;
130
+ font-size: 1.1rem;
131
+ margin-bottom: 0.5rem;
132
+ font-weight: 500;
133
+ }
134
+
135
+ .upload-subtext {
136
+ color: #a0a0a0;
137
+ font-size: 0.9rem;
138
+ }
139
+
140
+ .file-input {
141
+ display: none;
142
+ }
143
+
144
+ .preview-container {
145
+ margin-top: 2rem;
146
+ text-align: center;
147
+ animation: fadeIn 0.5s ease-out;
148
+ }
149
+
150
+ .preview-image {
151
+ max-width: 100%;
152
+ max-height: 300px;
153
+ border-radius: 15px;
154
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
155
+ border: 2px solid #00d4ff;
156
+ }
157
+
158
+ .predict-button {
159
+ background: linear-gradient(45deg, #00d4ff, #0099cc);
160
+ border: none;
161
+ color: white;
162
+ padding: 15px 40px;
163
+ font-size: 1.1rem;
164
+ font-weight: 600;
165
+ border-radius: 50px;
166
+ cursor: pointer;
167
+ margin-top: 1.5rem;
168
+ transition: all 0.3s ease;
169
+ text-transform: uppercase;
170
+ letter-spacing: 1px;
171
+ position: relative;
172
+ overflow: hidden;
173
+ }
174
+
175
+ .predict-button:hover {
176
+ transform: translateY(-2px);
177
+ box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4);
178
+ background: linear-gradient(45deg, #ff00ff, #cc0099);
179
+ }
180
+
181
+ .predict-button:disabled {
182
+ opacity: 0.6;
183
+ cursor: not-allowed;
184
+ transform: none;
185
+ }
186
+
187
+ .loading {
188
+ display: none;
189
+ margin-top: 1rem;
190
+ }
191
+
192
+ .spinner {
193
+ width: 40px;
194
+ height: 40px;
195
+ border: 4px solid rgba(0, 212, 255, 0.3);
196
+ border-top: 4px solid #00d4ff;
197
+ border-radius: 50%;
198
+ animation: spin 1s linear infinite;
199
+ margin: 0 auto;
200
+ }
201
+
202
+ .result-container {
203
+ margin-top: 2rem;
204
+ padding: 2rem;
205
+ background: rgba(255, 255, 255, 0.05);
206
+ backdrop-filter: blur(15px);
207
+ border-radius: 20px;
208
+ border: 1px solid rgba(0, 212, 255, 0.3);
209
+ animation: slideUp 0.5s ease-out;
210
+ text-align: center;
211
+ }
212
+
213
+ .result-class {
214
+ font-size: 2rem;
215
+ font-weight: 700;
216
+ color: #00ff88;
217
+ margin-bottom: 1rem;
218
+ text-transform: uppercase;
219
+ letter-spacing: 2px;
220
+ }
221
+
222
+ .result-confidence {
223
+ font-size: 1.2rem;
224
+ color: #ffffff;
225
+ margin-bottom: 0.5rem;
226
+ }
227
+
228
+ .confidence-bar {
229
+ width: 100%;
230
+ height: 10px;
231
+ background: rgba(255, 255, 255, 0.1);
232
+ border-radius: 5px;
233
+ overflow: hidden;
234
+ margin-top: 1rem;
235
+ }
236
+
237
+ .confidence-fill {
238
+ height: 100%;
239
+ background: linear-gradient(90deg, #00d4ff, #00ff88);
240
+ border-radius: 5px;
241
+ transition: width 1s ease-out;
242
+ animation: pulse 2s ease-in-out infinite;
243
+ }
244
+
245
+ .error {
246
+ color: #ff4444;
247
+ background: rgba(255, 68, 68, 0.1);
248
+ padding: 1rem;
249
+ border-radius: 10px;
250
+ border: 1px solid #ff4444;
251
+ margin-top: 1rem;
252
+ }
253
+
254
+ @keyframes slideDown {
255
+ from { opacity: 0; transform: translateY(-50px); }
256
+ to { opacity: 1; transform: translateY(0); }
257
+ }
258
+
259
+ @keyframes slideUp {
260
+ from { opacity: 0; transform: translateY(50px); }
261
+ to { opacity: 1; transform: translateY(0); }
262
+ }
263
+
264
+ @keyframes fadeIn {
265
+ from { opacity: 0; }
266
+ to { opacity: 1; }
267
+ }
268
+
269
+ @keyframes spin {
270
+ 0% { transform: rotate(0deg); }
271
+ 100% { transform: rotate(360deg); }
272
+ }
273
+
274
+ @keyframes gradientShift {
275
+ 0%, 100% { background-position: 0% 50%; }
276
+ 50% { background-position: 100% 50%; }
277
+ }
278
+
279
+ @keyframes pulse {
280
+ 0%, 100% { opacity: 1; }
281
+ 50% { opacity: 0.7; }
282
+ }
283
+
284
+ /* Responsive design */
285
+ @media (max-width: 768px) {
286
+ .title {
287
+ font-size: 2.5rem;
288
+ }
289
+
290
+ .container {
291
+ padding: 1rem;
292
+ }
293
+
294
+ .upload-area {
295
+ min-height: 250px;
296
+ }
297
+ }
298
+ </style>
299
+ </head>
300
+ <body>
301
+ <div class="particles" id="particles"></div>
302
+
303
+ <div class="container">
304
+ <div class="header">
305
+ <h1 class="title">YOLO12 Vision AI</h1>
306
+ <p class="subtitle">Advanced Image Classification with Neural Networks</p>
307
+ </div>
308
+
309
+ <div class="upload-area" id="uploadArea">
310
+ <div class="upload-icon">🔮</div>
311
+ <div class="upload-text">Drop your image here or click to upload</div>
312
+ <div class="upload-subtext">Supports PNG, JPG, JPEG formats</div>
313
+ <input type="file" id="fileInput" class="file-input" accept=".png,.jpg,.jpeg">
314
+ </div>
315
+
316
+ <div class="preview-container" id="previewContainer" style="display: none;">
317
+ <img id="previewImage" class="preview-image" alt="Preview">
318
+ <button class="predict-button" id="predictButton">🚀 Analyze Image</button>
319
+ </div>
320
+
321
+ <div class="loading" id="loading">
322
+ <div class="spinner"></div>
323
+ <p style="color: #00d4ff; margin-top: 1rem;">Processing your image...</p>
324
+ </div>
325
+
326
+ <div class="result-container" id="resultContainer" style="display: none;">
327
+ <div class="result-class" id="resultClass"></div>
328
+ <div class="result-confidence">
329
+ Confidence: <span id="confidencePercentage"></span>
330
+ </div>
331
+ <div class="confidence-bar">
332
+ <div class="confidence-fill" id="confidenceFill"></div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="error" id="errorContainer" style="display: none;"></div>
337
+ </div>
338
+
339
+ <script>
340
+ // Create animated particles
341
+ function createParticles() {
342
+ const particlesContainer = document.getElementById('particles');
343
+ const particleCount = 50;
344
+
345
+ for (let i = 0; i < particleCount; i++) {
346
+ const particle = document.createElement('div');
347
+ particle.className = 'particle';
348
+ particle.style.left = Math.random() * 100 + '%';
349
+ particle.style.top = Math.random() * 100 + '%';
350
+ particle.style.animationDelay = Math.random() * 6 + 's';
351
+ particle.style.animationDuration = (3 + Math.random() * 3) + 's';
352
+ particlesContainer.appendChild(particle);
353
+ }
354
+ }
355
+
356
+ // Initialize particles
357
+ createParticles();
358
+
359
+ // DOM elements
360
+ const uploadArea = document.getElementById('uploadArea');
361
+ const fileInput = document.getElementById('fileInput');
362
+ const previewContainer = document.getElementById('previewContainer');
363
+ const previewImage = document.getElementById('previewImage');
364
+ const predictButton = document.getElementById('predictButton');
365
+ const loading = document.getElementById('loading');
366
+ const resultContainer = document.getElementById('resultContainer');
367
+ const errorContainer = document.getElementById('errorContainer');
368
+ const resultClass = document.getElementById('resultClass');
369
+ const confidencePercentage = document.getElementById('confidencePercentage');
370
+ const confidenceFill = document.getElementById('confidenceFill');
371
+
372
+ let selectedFile = null;
373
+
374
+ // Upload area click handler
375
+ uploadArea.addEventListener('click', () => {
376
+ fileInput.click();
377
+ });
378
+
379
+ // File input change handler
380
+ fileInput.addEventListener('change', handleFileSelect);
381
+
382
+ // Drag and drop handlers
383
+ uploadArea.addEventListener('dragover', (e) => {
384
+ e.preventDefault();
385
+ uploadArea.classList.add('dragover');
386
+ });
387
+
388
+ uploadArea.addEventListener('dragleave', () => {
389
+ uploadArea.classList.remove('dragover');
390
+ });
391
+
392
+ uploadArea.addEventListener('drop', (e) => {
393
+ e.preventDefault();
394
+ uploadArea.classList.remove('dragover');
395
+ const files = e.dataTransfer.files;
396
+ if (files.length > 0) {
397
+ handleFile(files[0]);
398
+ }
399
+ });
400
+
401
+ // Predict button handler
402
+ predictButton.addEventListener('click', predictImage);
403
+
404
+ function handleFileSelect(e) {
405
+ const file = e.target.files[0];
406
+ if (file) {
407
+ handleFile(file);
408
+ }
409
+ }
410
+
411
+ function handleFile(file) {
412
+ // Validate file type
413
+ const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
414
+ if (!allowedTypes.includes(file.type)) {
415
+ showError('Please select a valid image file (PNG, JPG, JPEG)');
416
+ return;
417
+ }
418
+
419
+ // Validate file size (max 10MB)
420
+ if (file.size > 10 * 1024 * 1024) {
421
+ showError('File size too large. Please select a file smaller than 10MB.');
422
+ return;
423
+ }
424
+
425
+ selectedFile = file;
426
+
427
+ // Show preview
428
+ const reader = new FileReader();
429
+ reader.onload = (e) => {
430
+ previewImage.src = e.target.result;
431
+ previewContainer.style.display = 'block';
432
+ hideError();
433
+ hideResult();
434
+ };
435
+ reader.readAsDataURL(file);
436
+ }
437
+
438
+ async function predictImage() {
439
+ if (!selectedFile) {
440
+ showError('Please select an image first');
441
+ return;
442
+ }
443
+
444
+ // Show loading
445
+ loading.style.display = 'block';
446
+ predictButton.disabled = true;
447
+ hideError();
448
+ hideResult();
449
+
450
+ try {
451
+ const formData = new FormData();
452
+ formData.append('file', selectedFile);
453
+
454
+ const response = await fetch('/predict', {
455
+ method: 'POST',
456
+ body: formData
457
+ });
458
+
459
+ const data = await response.json();
460
+
461
+ if (response.ok) {
462
+ showResult(data);
463
+ } else {
464
+ showError(data.error || 'An error occurred during prediction');
465
+ }
466
+ } catch (error) {
467
+ showError('Failed to connect to the server. Please try again.');
468
+ console.error('Error:', error);
469
+ } finally {
470
+ loading.style.display = 'none';
471
+ predictButton.disabled = false;
472
+ }
473
+ }
474
+
475
+ function showResult(data) {
476
+ resultClass.textContent = data.class;
477
+ const confidence = Math.round(data.confidence * 100);
478
+ confidencePercentage.textContent = confidence + '%';
479
+ confidenceFill.style.width = confidence + '%';
480
+
481
+ resultContainer.style.display = 'block';
482
+
483
+ // Add some visual flair
484
+ setTimeout(() => {
485
+ confidenceFill.style.width = confidence + '%';
486
+ }, 100);
487
+ }
488
+
489
+ function showError(message) {
490
+ errorContainer.textContent = message;
491
+ errorContainer.style.display = 'block';
492
+ }
493
+
494
+ function hideError() {
495
+ errorContainer.style.display = 'none';
496
+ }
497
+
498
+ function hideResult() {
499
+ resultContainer.style.display = 'none';
500
+ }
501
+
502
+ // Add some interactive effects
503
+ document.addEventListener('mousemove', (e) => {
504
+ const particles = document.querySelectorAll('.particle');
505
+ const x = e.clientX / window.innerWidth;
506
+ const y = e.clientY / window.innerHeight;
507
+
508
+ particles.forEach((particle, index) => {
509
+ const speed = (index % 3 + 1) * 0.5;
510
+ const newX = x * speed;
511
+ const newY = y * speed;
512
+ particle.style.transform = `translate(${newX}px, ${newY}px)`;
513
+ });
514
+ });
515
+ </script>
516
+ </body>
517
+ </html>
templates/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>YOLO Vision AI</title>
7
  <style>
8
  * {
9
  margin: 0;
@@ -17,41 +17,9 @@
17
  min-height: 100vh;
18
  overflow-x: hidden;
19
  position: relative;
 
20
  }
21
 
22
- /* --- NEW: Tab Styles --- */
23
- .tabs {
24
- display: flex;
25
- justify-content: center;
26
- margin-bottom: 2rem;
27
- animation: slideDown 1s ease-out;
28
- }
29
-
30
- .tab-button {
31
- background: transparent;
32
- border: 2px solid #00d4ff;
33
- color: #00d4ff;
34
- padding: 10px 25px;
35
- margin: 0 10px;
36
- font-size: 1rem;
37
- font-weight: 600;
38
- border-radius: 50px;
39
- cursor: pointer;
40
- transition: all 0.3s ease;
41
- }
42
-
43
- .tab-button:hover {
44
- background: rgba(0, 212, 255, 0.2);
45
- transform: translateY(-2px);
46
- }
47
-
48
- .tab-button.active {
49
- background: #00d4ff;
50
- color: #1a1a2e;
51
- box-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
52
- }
53
- /* --- End Tab Styles --- */
54
-
55
  .particles {
56
  position: absolute; width: 100%; height: 100%; overflow: hidden; z-index: 0;
57
  }
@@ -62,11 +30,13 @@
62
  0%, 100% { transform: translateY(0px) rotate(0deg); }
63
  50% { transform: translateY(-20px) rotate(180deg); }
64
  }
 
65
  .container {
66
- position: relative; z-index: 1; max-width: 800px; margin: 0 auto; padding: 2rem; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center;
67
  }
 
68
  .header {
69
- text-align: center; margin-bottom: 2rem; animation: slideDown 1s ease-out;
70
  }
71
  .title {
72
  font-size: 3.5rem; font-weight: 700; background: linear-gradient(45deg, #00d4ff, #ff00ff, #00ff88); background-size: 200% 200%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; animation: gradientShift 3s ease-in-out infinite; margin-bottom: 1rem; text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
@@ -74,8 +44,9 @@
74
  .subtitle {
75
  font-size: 1.2rem; color: #a0a0a0; font-weight: 300;
76
  }
 
77
  .upload-area {
78
- width: 100%; max-width: 500px; min-height: 300px; border: 2px dashed #00d4ff; border-radius: 20px; background: rgba(0, 212, 255, 0.05); backdrop-filter: blur(10px); display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; animation: slideUp 1s ease-out 0.3s both;
79
  }
80
  .upload-area:hover {
81
  border-color: #ff00ff; background: rgba(255, 0, 255, 0.05); transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0, 212, 255, 0.2);
@@ -98,72 +69,128 @@
98
  .file-input {
99
  display: none;
100
  }
101
- .preview-container {
102
- margin-top: 2rem; text-align: center; animation: fadeIn 0.5s ease-out;
103
- }
104
- .preview-image {
105
- max-width: 100%; max-height: 300px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); border: 2px solid #00d4ff;
106
- }
107
- .predict-button {
108
- background: linear-gradient(45deg, #00d4ff, #0099cc); border: none; color: white; padding: 15px 40px; font-size: 1.1rem; font-weight: 600; border-radius: 50px; cursor: pointer; margin-top: 1.5rem; transition: all 0.3s ease; text-transform: uppercase; letter-spacing: 1px; position: relative; overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
- .predict-button:hover {
111
  transform: translateY(-2px); box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4); background: linear-gradient(45deg, #ff00ff, #cc0099);
112
  }
113
- .predict-button:disabled {
114
- opacity: 0.6; cursor: not-allowed; transform: none;
115
  }
 
116
  .loading {
117
- display: none; margin-top: 1rem;
118
  }
119
  .spinner {
120
  width: 40px; height: 40px; border: 4px solid rgba(0, 212, 255, 0.3); border-top: 4px solid #00d4ff; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;
121
  }
122
 
123
- /* --- NEW: Result Area Styles --- */
124
- .results-wrapper {
125
  margin-top: 2rem;
126
  width: 100%;
127
- max-width: 550px;
 
 
 
 
 
 
 
 
 
128
  }
129
- .result-container {
130
- padding: 2rem; background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(15px); border-radius: 20px; border: 1px solid rgba(0, 212, 255, 0.3); animation: slideUp 0.5s ease-out; text-align: center;
 
 
 
 
 
 
 
 
131
  }
132
- .result-title {
133
- font-size: 1.1rem;
 
134
  color: #00d4ff;
135
- font-weight: 400;
136
- text-transform: uppercase;
137
- letter-spacing: 1px;
138
  margin-bottom: 1.5rem;
 
139
  border-bottom: 1px solid rgba(0, 212, 255, 0.2);
140
- padding-bottom: 0.8rem;
 
 
 
 
141
  }
142
- .result-class {
143
- font-size: 2rem; font-weight: 700; color: #00ff88; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 2px;
 
 
 
 
 
 
 
144
  }
145
- .result-confidence {
146
- font-size: 1.2rem; color: #ffffff; margin-bottom: 0.5rem;
 
 
 
 
147
  }
148
- .confidence-bar {
149
- width: 100%; height: 10px; background: rgba(255, 255, 255, 0.1); border-radius: 5px; overflow: hidden; margin-top: 1rem;
 
150
  }
151
- .confidence-fill {
152
- height: 100%; background: linear-gradient(90deg, #00d4ff, #00ff88); border-radius: 5px; transition: width 1s ease-out; animation: pulse 2s ease-in-out infinite;
 
 
 
153
  }
154
- /* --- End Result Area Styles --- */
155
 
156
  .error {
157
- color: #ff4444; background: rgba(255, 68, 68, 0.1); padding: 1rem; border-radius: 10px; border: 1px solid #ff4444; margin-top: 1rem;
158
  }
 
159
  @keyframes slideDown { from { opacity: 0; transform: translateY(-50px); } to { opacity: 1; transform: translateY(0); } }
160
  @keyframes slideUp { from { opacity: 0; transform: translateY(50px); } to { opacity: 1; transform: translateY(0); } }
161
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
162
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
163
  @keyframes gradientShift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
164
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
165
  @media (max-width: 768px) {
166
- .title { font-size: 2.5rem; } .container { padding: 1rem; } .upload-area { min-height: 250px; } .tabs { flex-wrap: wrap; } .tab-button { margin-bottom: 10px;}
167
  }
168
  </style>
169
  </head>
@@ -173,59 +200,27 @@
173
  <div class="container">
174
  <div class="header">
175
  <h1 class="title">YOLO Vision AI</h1>
176
- <p class="subtitle">Advanced Image Classification with Neural Networks</p>
177
- </div>
178
-
179
- <!-- NEW: Tabs for model selection -->
180
- <div class="tabs">
181
- <button class="tab-button active" data-mode="model1">Model 1</button>
182
- <button class="tab-button" data-mode="model2">Model 2 (Tyre/Alloy)</button>
183
- <button class="tab-button" data-mode="combined">Combined</button>
184
  </div>
185
 
186
  <div class="upload-area" id="uploadArea">
187
  <div class="upload-icon">🔮</div>
188
- <div class="upload-text">Drop your image here or click to upload</div>
189
  <div class="upload-subtext">Supports PNG, JPG, JPEG formats</div>
190
- <input type="file" id="fileInput" class="file-input" accept=".png,.jpg,.jpeg">
191
  </div>
192
 
193
- <div class="preview-container" id="previewContainer" style="display: none;">
194
- <img id="previewImage" class="preview-image" alt="Preview">
195
- <button class="predict-button" id="predictButton">🚀 Analyze Image</button>
196
  </div>
197
 
198
  <div class="loading" id="loading">
199
  <div class="spinner"></div>
200
- <p style="color: #00d4ff; margin-top: 1rem;">Processing your image...</p>
201
  </div>
202
 
203
- <!-- NEW: Wrapper for all result blocks -->
204
- <div class="results-wrapper" id="resultsWrapper" style="display: none;">
205
- <!-- Block for Model 1 / Single Result -->
206
- <div class="result-container" id="resultBlock1" style="display: none;">
207
- <h3 class="result-title" id="resultTitle1">Prediction</h3>
208
- <div class="result-class" id="resultClass1"></div>
209
- <div class="result-confidence">
210
- Confidence: <span id="confidencePercentage1"></span>
211
- </div>
212
- <div class="confidence-bar">
213
- <div class="confidence-fill" id="confidenceFill1"></div>
214
- </div>
215
- </div>
216
-
217
- <!-- Block for Model 2 (in combined mode) -->
218
- <div class="result-container" id="resultBlock2" style="display: none; margin-top: 1.5rem;">
219
- <h3 class="result-title">Model 2 Prediction</h3>
220
- <div class="result-class" id="resultClass2"></div>
221
- <div class="result-confidence">
222
- Confidence: <span id="confidencePercentage2"></span>
223
- </div>
224
- <div class="confidence-bar">
225
- <div class="confidence-fill" id="confidenceFill2"></div>
226
- </div>
227
- </div>
228
- </div>
229
 
230
  <div class="error" id="errorContainer" style="display: none;"></div>
231
  </div>
@@ -234,6 +229,7 @@
234
  // Create animated particles
235
  function createParticles() {
236
  const container = document.getElementById('particles');
 
237
  for (let i = 0; i < 50; i++) {
238
  const particle = document.createElement('div');
239
  particle.className = 'particle';
@@ -249,149 +245,156 @@
249
  // DOM elements
250
  const uploadArea = document.getElementById('uploadArea');
251
  const fileInput = document.getElementById('fileInput');
252
- const previewContainer = document.getElementById('previewContainer');
253
- const previewImage = document.getElementById('previewImage');
254
- const predictButton = document.getElementById('predictButton');
255
  const loading = document.getElementById('loading');
256
  const errorContainer = document.getElementById('errorContainer');
257
- const resultsWrapper = document.getElementById('resultsWrapper');
258
-
259
- // Result Block 1 Elements
260
- const resultBlock1 = document.getElementById('resultBlock1');
261
- const resultTitle1 = document.getElementById('resultTitle1');
262
- const resultClass1 = document.getElementById('resultClass1');
263
- const confidencePercentage1 = document.getElementById('confidencePercentage1');
264
- const confidenceFill1 = document.getElementById('confidenceFill1');
265
 
266
- // Result Block 2 Elements
267
- const resultBlock2 = document.getElementById('resultBlock2');
268
- const resultClass2 = document.getElementById('resultClass2');
269
- const confidencePercentage2 = document.getElementById('confidencePercentage2');
270
- const confidenceFill2 = document.getElementById('confidenceFill2');
271
-
272
- let selectedFile = null;
273
- let currentMode = 'model1'; // NEW: state for current model
274
-
275
- // --- NEW: Tab switching logic ---
276
- document.querySelectorAll('.tab-button').forEach(button => {
277
- button.addEventListener('click', (e) => {
278
- // Update active button
279
- document.querySelector('.tab-button.active').classList.remove('active');
280
- e.target.classList.add('active');
281
-
282
- // Update mode
283
- currentMode = e.target.dataset.mode;
284
-
285
- // Reset UI
286
- resetUI();
287
- });
288
- });
289
 
290
  // Event Listeners
291
  uploadArea.addEventListener('click', () => fileInput.click());
292
  fileInput.addEventListener('change', handleFileSelect);
293
- uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); });
294
- uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover'));
295
- uploadArea.addEventListener('drop', (e) => {
296
- e.preventDefault();
297
- uploadArea.classList.remove('dragover');
298
- const files = e.dataTransfer.files;
299
- if (files.length > 0) handleFile(files[0]);
300
  });
301
- predictButton.addEventListener('click', predictImage);
 
 
 
 
302
 
303
- function handleFileSelect(e) {
304
- if (e.target.files.length > 0) handleFile(e.target.files[0]);
 
305
  }
306
 
307
- function handleFile(file) {
308
- const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
309
- if (!allowedTypes.includes(file.type)) {
310
- showError('Please select a valid image file (PNG, JPG, JPEG)');
311
- return;
312
- }
313
- if (file.size > 10 * 1024 * 1024) {
314
- showError('File size too large. Please select a file smaller than 10MB.');
315
- return;
316
- }
317
 
318
- selectedFile = file;
319
- const reader = new FileReader();
320
- reader.onload = (e) => {
321
- previewImage.src = e.target.result;
322
- previewContainer.style.display = 'block';
323
- hideError();
324
- hideResults();
325
- };
326
- reader.readAsDataURL(file);
327
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
- async function predictImage() {
330
- if (!selectedFile) {
331
- showError('Please select an image first');
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  return;
333
  }
334
 
335
  loading.style.display = 'block';
336
- predictButton.disabled = true;
337
  hideError();
338
- hideResults();
339
 
340
  try {
341
  const formData = new FormData();
342
- formData.append('file', selectedFile);
343
- formData.append('model_type', currentMode); // NEW: Send current mode to backend
 
344
 
345
  const response = await fetch('/predict', { method: 'POST', body: formData });
346
  const data = await response.json();
347
 
348
  if (response.ok) {
349
- showResult(data);
350
  } else {
351
- showError(data.error || 'An error occurred during prediction');
352
  }
353
  } catch (error) {
354
- showError('Failed to connect to the server. Please try again.');
355
  console.error('Error:', error);
356
  } finally {
357
  loading.style.display = 'none';
358
- predictButton.disabled = false;
 
359
  }
360
  }
361
-
362
- function showResult(data) {
363
- resultsWrapper.style.display = 'block';
364
-
365
- if (currentMode === 'combined') {
366
- // Display two results
367
- if (data.model1_result) {
368
- displaySingleResult(data.model1_result, 'Model 1 Prediction', resultBlock1, resultTitle1, resultClass1, confidencePercentage1, confidenceFill1);
369
- }
370
- if (data.model2_result) {
371
- displaySingleResult(data.model2_result, 'Model 2 Prediction', resultBlock2, null, resultClass2, confidencePercentage2, confidenceFill2);
372
- }
373
- } else {
374
- // Display single result
375
- const title = currentMode === 'model1' ? 'Model 1 Prediction' : 'Model 2 Prediction';
376
- displaySingleResult(data, title, resultBlock1, resultTitle1, resultClass1, confidencePercentage1, confidenceFill1);
377
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  }
379
 
380
- // NEW: Helper function to populate a result block
381
- function displaySingleResult(data, title, block, titleEl, classEl, percentEl, fillEl) {
382
- if (!data) return;
383
- if (titleEl) titleEl.textContent = title;
384
- classEl.textContent = data.class;
385
- const confidence = Math.round(data.confidence * 100);
386
- percentEl.textContent = confidence + '%';
387
-
388
- block.style.display = 'block';
389
-
390
- // Animate confidence bar
391
- fillEl.style.width = '0%';
392
- setTimeout(() => {
393
- fillEl.style.width = confidence + '%';
394
- }, 100);
395
  }
396
 
397
  function showError(message) {
@@ -402,22 +405,6 @@
402
  function hideError() {
403
  errorContainer.style.display = 'none';
404
  }
405
-
406
- function hideResults() {
407
- resultsWrapper.style.display = 'none';
408
- resultBlock1.style.display = 'none';
409
- resultBlock2.style.display = 'none';
410
- }
411
-
412
- // NEW: Function to reset the entire UI state
413
- function resetUI() {
414
- hideResults();
415
- hideError();
416
- loading.style.display = 'none';
417
- previewContainer.style.display = 'none';
418
- fileInput.value = ''; // Clear file input
419
- selectedFile = null;
420
- }
421
  </script>
422
  </body>
423
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>YOLO Vision AI - Multi-Image Analysis</title>
7
  <style>
8
  * {
9
  margin: 0;
 
17
  min-height: 100vh;
18
  overflow-x: hidden;
19
  position: relative;
20
+ color: #e0e0e0;
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  .particles {
24
  position: absolute; width: 100%; height: 100%; overflow: hidden; z-index: 0;
25
  }
 
30
  0%, 100% { transform: translateY(0px) rotate(0deg); }
31
  50% { transform: translateY(-20px) rotate(180deg); }
32
  }
33
+
34
  .container {
35
+ position: relative; z-index: 1; max-width: 800px; margin: 0 auto; padding: 2rem; min-height: 100vh; display: flex; flex-direction: column; justify-content: flex-start; align-items: center;
36
  }
37
+
38
  .header {
39
+ text-align: center; margin-bottom: 2rem; animation: slideDown 1s ease-out; width: 100%;
40
  }
41
  .title {
42
  font-size: 3.5rem; font-weight: 700; background: linear-gradient(45deg, #00d4ff, #ff00ff, #00ff88); background-size: 200% 200%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; animation: gradientShift 3s ease-in-out infinite; margin-bottom: 1rem; text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
 
44
  .subtitle {
45
  font-size: 1.2rem; color: #a0a0a0; font-weight: 300;
46
  }
47
+
48
  .upload-area {
49
+ width: 100%; max-width: 550px; min-height: 300px; border: 2px dashed #00d4ff; border-radius: 20px; background: rgba(0, 212, 255, 0.05); backdrop-filter: blur(10px); display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; animation: slideUp 1s ease-out 0.3s both;
50
  }
51
  .upload-area:hover {
52
  border-color: #ff00ff; background: rgba(255, 0, 255, 0.05); transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0, 212, 255, 0.2);
 
69
  .file-input {
70
  display: none;
71
  }
72
+
73
+ .file-list-container {
74
+ display: none;
75
+ width: 100%;
76
+ max-width: 550px;
77
+ margin-top: 2rem;
78
+ animation: fadeIn 0.5s ease;
79
+ }
80
+ #fileList {
81
+ list-style: none;
82
+ background: rgba(0, 212, 255, 0.05);
83
+ border-radius: 10px;
84
+ padding: 1rem;
85
+ max-height: 200px;
86
+ overflow-y: auto;
87
+ border: 1px solid rgba(0, 212, 255, 0.2);
88
+ }
89
+ #fileList li {
90
+ padding: 0.5rem;
91
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
92
+ color: #c0c0c0;
93
+ }
94
+ #fileList li:last-child {
95
+ border-bottom: none;
96
+ }
97
+ .analyze-button {
98
+ display: block;
99
+ width: 100%;
100
+ background: linear-gradient(45deg, #00d4ff, #0099cc); border: none; color: white; padding: 15px 40px; font-size: 1.1rem; font-weight: 600; border-radius: 50px; cursor: pointer; margin-top: 1.5rem; transition: all 0.3s ease; text-transform: uppercase; letter-spacing: 1px;
101
  }
102
+ .analyze-button:hover {
103
  transform: translateY(-2px); box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4); background: linear-gradient(45deg, #ff00ff, #cc0099);
104
  }
105
+ .analyze-button:disabled {
106
+ opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; background: #555;
107
  }
108
+
109
  .loading {
110
+ display: none; margin-top: 2rem; text-align: center;
111
  }
112
  .spinner {
113
  width: 40px; height: 40px; border: 4px solid rgba(0, 212, 255, 0.3); border-top: 4px solid #00d4ff; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;
114
  }
115
 
116
+ #results-container {
 
117
  margin-top: 2rem;
118
  width: 100%;
119
+ max-width: 550px; /* Adjusted max-width */
120
+ }
121
+ .result-card {
122
+ padding: 1.5rem;
123
+ background: rgba(255, 255, 255, 0.05);
124
+ backdrop-filter: blur(15px);
125
+ border: 1px solid rgba(0, 212, 255, 0.3);
126
+ animation: slideUp 0.5s ease-out;
127
+ margin-bottom: 2rem;
128
+ border-radius: 15px; /* Unified border radius */
129
  }
130
+
131
+ /* --- NEW: Image style within the card --- */
132
+ .result-image {
133
+ width: 100%;
134
+ height: auto;
135
+ max-height: 400px;
136
+ object-fit: contain;
137
+ border-radius: 10px;
138
+ margin-bottom: 1.5rem;
139
+ background-color: rgba(0,0,0,0.2);
140
  }
141
+
142
+ .result-card h3 {
143
+ font-size: 1.2rem;
144
  color: #00d4ff;
 
 
 
145
  margin-bottom: 1.5rem;
146
+ padding-bottom: 1rem;
147
  border-bottom: 1px solid rgba(0, 212, 255, 0.2);
148
+ font-weight: 600;
149
+ word-wrap: break-word;
150
+ }
151
+ .prediction-block {
152
+ margin-bottom: 1.5rem;
153
  }
154
+ .prediction-block:last-child {
155
+ margin-bottom: 0;
156
+ }
157
+ .prediction-title {
158
+ font-size: 0.9rem;
159
+ color: #a0a0a0;
160
+ text-transform: uppercase;
161
+ letter-spacing: 1px;
162
+ margin-bottom: 0.5rem;
163
  }
164
+ .prediction-class {
165
+ font-size: 1.8rem; /* Made class name larger */
166
+ font-weight: 700;
167
+ color: #00ff88;
168
+ text-transform: capitalize;
169
+ line-height: 1.2;
170
  }
171
+ .prediction-confidence {
172
+ font-size: 1rem; /* Slightly larger confidence text */
173
+ color: #e0e0e0;
174
  }
175
+ .damage-note {
176
+ font-size: 0.8rem;
177
+ color: #aaa;
178
+ font-style: italic;
179
+ margin-top: 4px;
180
  }
 
181
 
182
  .error {
183
+ color: #ff4444; background: rgba(255, 68, 68, 0.1); padding: 1rem; border-radius: 10px; border: 1px solid #ff4444; margin-top: 2rem; width: 100%; max-width: 550px;
184
  }
185
+
186
  @keyframes slideDown { from { opacity: 0; transform: translateY(-50px); } to { opacity: 1; transform: translateY(0); } }
187
  @keyframes slideUp { from { opacity: 0; transform: translateY(50px); } to { opacity: 1; transform: translateY(0); } }
188
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
189
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
190
  @keyframes gradientShift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
191
+
192
  @media (max-width: 768px) {
193
+ .title { font-size: 2.5rem; } .container { padding: 1rem; } .upload-area { min-height: 250px; }
194
  }
195
  </style>
196
  </head>
 
200
  <div class="container">
201
  <div class="header">
202
  <h1 class="title">YOLO Vision AI</h1>
203
+ <p class="subtitle">Multi-Image Vehicle Part & Damage Analysis</p>
 
 
 
 
 
 
 
204
  </div>
205
 
206
  <div class="upload-area" id="uploadArea">
207
  <div class="upload-icon">🔮</div>
208
+ <div class="upload-text">Drop your images here or click to upload</div>
209
  <div class="upload-subtext">Supports PNG, JPG, JPEG formats</div>
210
+ <input type="file" id="fileInput" class="file-input" accept=".png,.jpg,.jpeg" multiple>
211
  </div>
212
 
213
+ <div class="file-list-container" id="fileListContainer">
214
+ <ul id="fileList"></ul>
215
+ <button class="analyze-button" id="analyzeButton">🚀 Analyze Images</button>
216
  </div>
217
 
218
  <div class="loading" id="loading">
219
  <div class="spinner"></div>
220
+ <p style="color: #00d4ff; margin-top: 1rem;">Processing your images...</p>
221
  </div>
222
 
223
+ <div id="results-container"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  <div class="error" id="errorContainer" style="display: none;"></div>
226
  </div>
 
229
  // Create animated particles
230
  function createParticles() {
231
  const container = document.getElementById('particles');
232
+ if (container.children.length > 0) return;
233
  for (let i = 0; i < 50; i++) {
234
  const particle = document.createElement('div');
235
  particle.className = 'particle';
 
245
  // DOM elements
246
  const uploadArea = document.getElementById('uploadArea');
247
  const fileInput = document.getElementById('fileInput');
248
+ const fileListContainer = document.getElementById('fileListContainer');
249
+ const fileList = document.getElementById('fileList');
250
+ const analyzeButton = document.getElementById('analyzeButton');
251
  const loading = document.getElementById('loading');
252
  const errorContainer = document.getElementById('errorContainer');
253
+ const resultsContainer = document.getElementById('results-container');
 
 
 
 
 
 
 
254
 
255
+ // --- NEW: Store for file objects and their data URLs for preview ---
256
+ let fileDataStore = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
  // Event Listeners
259
  uploadArea.addEventListener('click', () => fileInput.click());
260
  fileInput.addEventListener('change', handleFileSelect);
261
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
262
+ uploadArea.addEventListener(eventName, preventDefaults, false);
263
+ });
264
+ ['dragenter', 'dragover'].forEach(eventName => {
265
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.add('dragover'), false);
 
 
266
  });
267
+ ['dragleave', 'drop'].forEach(eventName => {
268
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('dragover'), false);
269
+ });
270
+ uploadArea.addEventListener('drop', handleDrop, false);
271
+ analyzeButton.addEventListener('click', analyzeImages);
272
 
273
+ function preventDefaults(e) {
274
+ e.preventDefault();
275
+ e.stopPropagation();
276
  }
277
 
278
+ function handleDrop(e) {
279
+ handleFiles(e.dataTransfer.files);
280
+ }
 
 
 
 
 
 
 
281
 
282
+ function handleFileSelect(e) {
283
+ handleFiles(e.target.files);
 
 
 
 
 
 
 
284
  }
285
+
286
+ // --- UPDATED: Reads files and generates data URLs for previews ---
287
+ async function handleFiles(files) {
288
+ if (files.length === 0) return;
289
+
290
+ // Clear previous selections and results
291
+ resetUI();
292
+ fileDataStore = [];
293
+
294
+ const filePromises = Array.from(files).map(file => {
295
+ return new Promise((resolve, reject) => {
296
+ const reader = new FileReader();
297
+ reader.onload = (e) => {
298
+ fileDataStore.push({ file: file, dataURL: e.target.result });
299
+ resolve();
300
+ };
301
+ reader.onerror = reject;
302
+ reader.readAsDataURL(file);
303
+ });
304
+ });
305
 
306
+ await Promise.all(filePromises);
307
+
308
+ // Update the UI list
309
+ fileDataStore.forEach(item => {
310
+ const listItem = document.createElement('li');
311
+ listItem.textContent = `${item.file.name} (${(item.file.size / 1024).toFixed(1)} KB)`;
312
+ fileList.appendChild(listItem);
313
+ });
314
+
315
+ fileListContainer.style.display = 'block';
316
+ uploadArea.style.display = 'none'; // Hide upload area after selection
317
+ }
318
+
319
+ async function analyzeImages() {
320
+ if (fileDataStore.length === 0) {
321
+ showError('Please select one or more images first');
322
  return;
323
  }
324
 
325
  loading.style.display = 'block';
326
+ analyzeButton.disabled = true;
327
  hideError();
328
+ resultsContainer.innerHTML = '';
329
 
330
  try {
331
  const formData = new FormData();
332
+ fileDataStore.forEach(item => {
333
+ formData.append('file', item.file);
334
+ });
335
 
336
  const response = await fetch('/predict', { method: 'POST', body: formData });
337
  const data = await response.json();
338
 
339
  if (response.ok) {
340
+ displayResults(data);
341
  } else {
342
+ showError(data.error || 'An unknown error occurred during prediction');
343
  }
344
  } catch (error) {
345
+ showError('Failed to connect to the server. Please check your connection and try again.');
346
  console.error('Error:', error);
347
  } finally {
348
  loading.style.display = 'none';
349
+ analyzeButton.disabled = false;
350
+ fileInput.value = '';
351
  }
352
  }
353
+
354
+ // --- UPDATED: Displays results with image previews ---
355
+ function displayResults(results) {
356
+ if (!Array.isArray(results) || results.length === 0) {
357
+ resultsContainer.innerHTML = '<p>No results were returned from the server.</p>';
358
+ return;
 
 
 
 
 
 
 
 
 
 
359
  }
360
+
361
+ results.forEach(result => {
362
+ // Find the corresponding image data URL from our store
363
+ const fileData = fileDataStore.find(item => item.file.name === result.filename);
364
+ if (!fileData) return; // Skip if no matching image found
365
+
366
+ const card = document.createElement('div');
367
+ card.className = 'result-card';
368
+
369
+ const partPred = result.part_prediction;
370
+ const damagePred = result.damage_prediction;
371
+ const damageNote = damagePred.note ? `<div class="damage-note">${damagePred.note}</div>` : '';
372
+
373
+ card.innerHTML = `
374
+ <img src="${fileData.dataURL}" alt="${result.filename}" class="result-image">
375
+ <h3>${result.filename}</h3>
376
+ <div class="prediction-block">
377
+ <div class="prediction-title">Part Detected</div>
378
+ <div class="prediction-class">${partPred.class.replace(/_/g, ' ')}</div>
379
+ <div class="prediction-confidence">Confidence: ${(partPred.confidence * 100).toFixed(2)}%</div>
380
+ </div>
381
+ <div class="prediction-block">
382
+ <div class="prediction-title">Damage Status</div>
383
+ <div class="prediction-class" style="color: ${damagePred.class === 'correct' ? '#00ff88' : '#ff4444'};">${damagePred.class}</div>
384
+ <div class="prediction-confidence">Confidence: ${(damagePred.confidence * 100).toFixed(2)}%</div>
385
+ ${damageNote}
386
+ </div>
387
+ `;
388
+ resultsContainer.appendChild(card);
389
+ });
390
  }
391
 
392
+ function resetUI() {
393
+ fileList.innerHTML = '';
394
+ resultsContainer.innerHTML = '';
395
+ fileListContainer.style.display = 'none';
396
+ uploadArea.style.display = 'flex';
397
+ hideError();
 
 
 
 
 
 
 
 
 
398
  }
399
 
400
  function showError(message) {
 
405
  function hideError() {
406
  errorContainer.style.display = 'none';
407
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  </script>
409
  </body>
410
  </html>