Sara-Adjo commited on
Commit
0bbc45f
·
verified ·
1 Parent(s): a18e884

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +597 -0
templates/index.html ADDED
@@ -0,0 +1,597 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Intel Scene Classifier</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet" />
9
+
10
+ <style>
11
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
12
+
13
+ :root {
14
+ --bg: #0a0a0f;
15
+ --surface: #111118;
16
+ --border: rgba(255,255,255,0.08);
17
+ --accent: #7c6aff;
18
+ --accent2: #ff6ab0;
19
+ --text: #e8e8f0;
20
+ --muted: #6b6b80;
21
+ --success: #4dffb0;
22
+ --radius: 16px;
23
+ --mono: 'Space Mono', monospace;
24
+ --sans: 'Syne', sans-serif;
25
+ }
26
+
27
+ body {
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ font-family: var(--sans);
31
+ min-height: 100vh;
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ padding: 40px 20px 80px;
36
+ }
37
+
38
+
39
+ body::before {
40
+ content: '';
41
+ position: fixed;
42
+ inset: 0;
43
+ background-image:
44
+ linear-gradient(rgba(124,106,255,0.04) 1px, transparent 1px),
45
+ linear-gradient(90deg, rgba(124,106,255,0.04) 1px, transparent 1px);
46
+ background-size: 48px 48px;
47
+ pointer-events: none;
48
+ z-index: 0;
49
+ }
50
+
51
+
52
+ header {
53
+ text-align: center;
54
+ margin-bottom: 48px;
55
+ position: relative;
56
+ z-index: 1;
57
+ }
58
+
59
+ .logo-badge {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ gap: 8px;
63
+ font-family: var(--mono);
64
+ font-size: 11px;
65
+ letter-spacing: 0.15em;
66
+ color: var(--accent);
67
+ background: rgba(124,106,255,0.1);
68
+ border: 1px solid rgba(124,106,255,0.3);
69
+ padding: 6px 16px;
70
+ border-radius: 100px;
71
+ margin-bottom: 20px;
72
+ }
73
+
74
+ .logo-badge::before {
75
+ content: '';
76
+ width: 6px; height: 6px;
77
+ background: var(--accent);
78
+ border-radius: 50%;
79
+ animation: pulse 2s infinite;
80
+ }
81
+
82
+ @keyframes pulse {
83
+ 0%, 100% { opacity: 1; transform: scale(1); }
84
+ 50% { opacity: 0.4; transform: scale(0.8); }
85
+ }
86
+
87
+ h1 {
88
+ font-size: clamp(2rem, 5vw, 3.4rem);
89
+ font-weight: 800;
90
+ line-height: 1.1;
91
+ background: linear-gradient(135deg, #fff 0%, var(--accent) 60%, var(--accent2) 100%);
92
+ -webkit-background-clip: text;
93
+ -webkit-text-fill-color: transparent;
94
+ background-clip: text;
95
+ }
96
+
97
+ .subtitle {
98
+ color: var(--muted);
99
+ font-family: var(--mono);
100
+ font-size: 13px;
101
+ margin-top: 12px;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+
106
+ .card {
107
+ width: 100%;
108
+ max-width: 640px;
109
+ background: var(--surface);
110
+ border: 1px solid var(--border);
111
+ border-radius: var(--radius);
112
+ padding: 36px;
113
+ position: relative;
114
+ z-index: 1;
115
+ backdrop-filter: blur(12px);
116
+ }
117
+
118
+ .card + .card { margin-top: 24px; }
119
+
120
+
121
+ .field-label {
122
+ font-size: 11px;
123
+ letter-spacing: 0.12em;
124
+ color: var(--muted);
125
+ font-family: var(--mono);
126
+ text-transform: uppercase;
127
+ margin-bottom: 10px;
128
+ display: block;
129
+ }
130
+
131
+
132
+ .model-selector {
133
+ display: grid;
134
+ grid-template-columns: 1fr 1fr;
135
+ gap: 12px;
136
+ margin-bottom: 28px;
137
+ }
138
+
139
+ .model-option { display: none; }
140
+
141
+ .model-btn {
142
+ display: flex;
143
+ flex-direction: column;
144
+ align-items: center;
145
+ gap: 8px;
146
+ padding: 18px 12px;
147
+ border: 1px solid var(--border);
148
+ border-radius: 12px;
149
+ cursor: pointer;
150
+ transition: all 0.2s ease;
151
+ background: transparent;
152
+ user-select: none;
153
+ }
154
+
155
+ .model-btn:hover {
156
+ border-color: var(--accent);
157
+ background: rgba(124,106,255,0.06);
158
+ }
159
+
160
+ .model-option:checked + .model-btn {
161
+ border-color: var(--accent);
162
+ background: rgba(124,106,255,0.12);
163
+ box-shadow: 0 0 0 1px var(--accent);
164
+ }
165
+
166
+ .model-btn .icon {
167
+ font-size: 28px;
168
+ }
169
+
170
+ .model-btn .name {
171
+ font-weight: 600;
172
+ font-size: 14px;
173
+ }
174
+
175
+ .model-btn .tag {
176
+ font-family: var(--mono);
177
+ font-size: 10px;
178
+ color: var(--muted);
179
+ }
180
+
181
+ /* File upload zone */
182
+ .upload-zone {
183
+ border: 2px dashed var(--border);
184
+ border-radius: 12px;
185
+ padding: 40px 20px;
186
+ text-align: center;
187
+ cursor: pointer;
188
+ transition: all 0.2s ease;
189
+ position: relative;
190
+ margin-bottom: 24px;
191
+ }
192
+
193
+ .upload-zone:hover, .upload-zone.drag-over {
194
+ border-color: var(--accent);
195
+ background: rgba(124,106,255,0.05);
196
+ }
197
+
198
+ .upload-zone input[type="file"] {
199
+ position: absolute;
200
+ inset: 0;
201
+ opacity: 0;
202
+ cursor: pointer;
203
+ width: 100%;
204
+ height: 100%;
205
+ }
206
+
207
+ .upload-icon { font-size: 40px; margin-bottom: 12px; }
208
+
209
+ .upload-text {
210
+ font-size: 15px;
211
+ font-weight: 600;
212
+ }
213
+
214
+ .upload-hint {
215
+ font-size: 12px;
216
+ font-family: var(--mono);
217
+ color: var(--muted);
218
+ margin-top: 6px;
219
+ }
220
+
221
+ #file-name {
222
+ margin-top: 10px;
223
+ font-size: 12px;
224
+ font-family: var(--mono);
225
+ color: var(--accent);
226
+ word-break: break-all;
227
+ }
228
+
229
+ /* Preview image */
230
+ #preview-wrap {
231
+ display: none;
232
+ margin-bottom: 24px;
233
+ border-radius: 12px;
234
+ overflow: hidden;
235
+ border: 1px solid var(--border);
236
+ }
237
+
238
+ #preview-wrap img {
239
+ width: 100%;
240
+ max-height: 260px;
241
+ object-fit: cover;
242
+ display: block;
243
+ }
244
+
245
+ /* Submit button */
246
+ .btn-predict {
247
+ width: 100%;
248
+ padding: 16px;
249
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
250
+ color: #fff;
251
+ font-family: var(--sans);
252
+ font-size: 16px;
253
+ font-weight: 700;
254
+ border: none;
255
+ border-radius: 12px;
256
+ cursor: pointer;
257
+ transition: opacity 0.2s, transform 0.15s;
258
+ letter-spacing: 0.03em;
259
+ }
260
+
261
+ .btn-predict:hover { opacity: 0.9; transform: translateY(-1px); }
262
+ .btn-predict:active { transform: translateY(0); }
263
+
264
+ /* Loading state */
265
+ .btn-predict.loading {
266
+ pointer-events: none;
267
+ opacity: 0.7;
268
+ }
269
+
270
+ .error-banner {
271
+ background: rgba(255, 80, 80, 0.1);
272
+ border: 1px solid rgba(255, 80, 80, 0.3);
273
+ color: #ff7070;
274
+ padding: 14px 18px;
275
+ border-radius: 10px;
276
+ font-family: var(--mono);
277
+ font-size: 13px;
278
+ margin-bottom: 24px;
279
+ }
280
+
281
+
282
+ .result-header {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 16px;
286
+ margin-bottom: 28px;
287
+ }
288
+
289
+ .result-thumb {
290
+ width: 80px;
291
+ height: 80px;
292
+ border-radius: 10px;
293
+ object-fit: cover;
294
+ border: 2px solid var(--accent);
295
+ flex-shrink: 0;
296
+ }
297
+
298
+ .result-meta { flex: 1; }
299
+
300
+ .result-label {
301
+ font-size: 11px;
302
+ font-family: var(--mono);
303
+ color: var(--muted);
304
+ letter-spacing: 0.1em;
305
+ text-transform: uppercase;
306
+ margin-bottom: 4px;
307
+ }
308
+
309
+ .result-class {
310
+ font-size: 28px;
311
+ font-weight: 800;
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 10px;
315
+ }
316
+
317
+ .result-conf {
318
+ display: inline-flex;
319
+ align-items: center;
320
+ gap: 6px;
321
+ margin-top: 6px;
322
+ font-family: var(--mono);
323
+ font-size: 13px;
324
+ color: var(--success);
325
+ }
326
+
327
+ .model-pill {
328
+ display: inline-flex;
329
+ align-items: center;
330
+ gap: 5px;
331
+ font-family: var(--mono);
332
+ font-size: 10px;
333
+ letter-spacing: 0.1em;
334
+ padding: 3px 10px;
335
+ border-radius: 100px;
336
+ background: rgba(124,106,255,0.15);
337
+ border: 1px solid rgba(124,106,255,0.3);
338
+ color: var(--accent);
339
+ text-transform: uppercase;
340
+ margin-top: 8px;
341
+ }
342
+
343
+ .prob-list { display: flex; flex-direction: column; gap: 10px; }
344
+
345
+ .prob-row {
346
+ display: grid;
347
+ grid-template-columns: 90px 1fr 54px;
348
+ align-items: center;
349
+ gap: 12px;
350
+ }
351
+
352
+ .prob-name {
353
+ font-size: 13px;
354
+ font-family: var(--mono);
355
+ color: var(--muted);
356
+ white-space: nowrap;
357
+ }
358
+
359
+ .prob-bar-bg {
360
+ height: 8px;
361
+ background: rgba(255,255,255,0.05);
362
+ border-radius: 100px;
363
+ overflow: hidden;
364
+ }
365
+
366
+ .prob-bar-fill {
367
+ height: 100%;
368
+ border-radius: 100px;
369
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
370
+ transition: width 0.6s cubic-bezier(0.4,0,0.2,1);
371
+ }
372
+
373
+ .prob-val {
374
+ font-family: var(--mono);
375
+ font-size: 12px;
376
+ color: var(--text);
377
+ text-align: right;
378
+ }
379
+
380
+ .prob-row.top .prob-name { color: var(--text); font-weight: bold; }
381
+ .prob-row.top .prob-val { color: var(--success); }
382
+
383
+
384
+ .btn-reset {
385
+ margin-top: 24px;
386
+ display: block;
387
+ text-align: center;
388
+ padding: 12px;
389
+ border: 1px solid var(--border);
390
+ border-radius: 10px;
391
+ color: var(--muted);
392
+ font-size: 13px;
393
+ font-family: var(--mono);
394
+ text-decoration: none;
395
+ transition: all 0.2s;
396
+ }
397
+
398
+ .btn-reset:hover {
399
+ border-color: var(--accent);
400
+ color: var(--accent);
401
+ }
402
+
403
+ footer {
404
+ margin-top: 48px;
405
+ font-size: 11px;
406
+ font-family: var(--mono);
407
+ color: var(--muted);
408
+ text-align: center;
409
+ position: relative;
410
+ z-index: 1;
411
+ }
412
+
413
+ footer span { color: var(--accent); }
414
+
415
+
416
+ @media (max-width: 480px) {
417
+ .card { padding: 24px 18px; }
418
+ .model-btn .icon { font-size: 22px; }
419
+ }
420
+ </style>
421
+ </head>
422
+ <body>
423
+
424
+ <!-- Header -->
425
+ <header>
426
+ <div class="logo-badge">NEURAL SCENE CLASSIFIER</div>
427
+ <h1>Intel Image<br/>Classification</h1>
428
+ <p class="subtitle">// 6 classes · PyTorch & TensorFlow · Custom CNN</p>
429
+ </header>
430
+
431
+
432
+ {% if error %}
433
+ <div class="card" style="max-width:640px;width:100%;position:relative;z-index:1;">
434
+ <div class="error-banner">⚠ {{ error }}</div>
435
+ </div>
436
+ {% endif %}
437
+
438
+
439
+ {% if result %}
440
+ <div class="card">
441
+
442
+ <div class="result-header">
443
+ <img class="result-thumb"
444
+ src="data:image/jpeg;base64,{{ img_b64 }}"
445
+ alt="uploaded image" />
446
+ <div class="result-meta">
447
+ <div class="result-label">Predicted Scene</div>
448
+ <div class="result-class">
449
+ {{ class_icons.get(result.predicted_class, '🔍') }}
450
+ {{ result.predicted_class | capitalize }}
451
+ </div>
452
+ <div class="result-conf">
453
+ ✓ {{ result.confidence }}% confidence
454
+ </div>
455
+ <div class="model-pill">
456
+ {% if model_used == 'pytorch' %}🔥 PyTorch{% else %}⚡ TensorFlow{% endif %}
457
+ </div>
458
+ </div>
459
+ </div>
460
+
461
+ <!-- Probability bars -->
462
+ <span class="field-label">All class probabilities</span>
463
+ <div class="prob-list">
464
+ {% for cls, prob in sorted_probs %}
465
+ <div class="prob-row {% if cls == result.predicted_class %}top{% endif %}">
466
+ <span class="prob-name">
467
+ {{ class_icons.get(cls, '') }} {{ cls }}
468
+ </span>
469
+ <div class="prob-bar-bg">
470
+ <div class="prob-bar-fill" style="width: {{ prob }}%"></div>
471
+ </div>
472
+ <span class="prob-val">{{ prob }}%</span>
473
+ </div>
474
+ {% endfor %}
475
+ </div>
476
+
477
+ <a href="/" class="btn-reset">↩ Classify another image</a>
478
+ </div>
479
+
480
+ <!-- UPLOAD FORM -->
481
+ {% else %}
482
+ <div class="card">
483
+ <form method="POST" action="/predict" enctype="multipart/form-data" id="upload-form">
484
+
485
+ <!-- Model selector -->
486
+ <span class="field-label">Select model backend</span>
487
+ <div class="model-selector">
488
+
489
+ <input class="model-option" type="radio" name="model"
490
+ id="opt-pytorch" value="pytorch" checked />
491
+ <label class="model-btn" for="opt-pytorch">
492
+ <span class="icon">🔥</span>
493
+ <span class="name">PyTorch</span>
494
+ <span class="tag">sara_model.pth</span>
495
+ </label>
496
+
497
+ <input class="model-option" type="radio" name="model"
498
+ id="opt-tensorflow" value="tensorflow" />
499
+ <label class="model-btn" for="opt-tensorflow">
500
+ <span class="icon">⚡</span>
501
+ <span class="name">TensorFlow</span>
502
+ <span class="tag">sara_model.keras</span>
503
+ </label>
504
+
505
+ </div>
506
+
507
+ <!-- File upload -->
508
+ <span class="field-label">Upload image</span>
509
+ <div class="upload-zone" id="drop-zone">
510
+ <input type="file" name="image" id="file-input"
511
+ accept=".jpg,.jpeg,.png,.webp,.bmp" required />
512
+ <div class="upload-icon">📸</div>
513
+ <div class="upload-text">Drop your image here</div>
514
+ <div class="upload-hint">or click to browse · JPG PNG WEBP · max 5 MB</div>
515
+ <div id="file-name"></div>
516
+ </div>
517
+
518
+ <!-- Preview -->
519
+ <div id="preview-wrap">
520
+ <img id="preview-img" src="" alt="preview" />
521
+ </div>
522
+
523
+ <!-- Submit -->
524
+ <button type="submit" class="btn-predict" id="submit-btn">
525
+ ⚡ Classify Image
526
+ </button>
527
+
528
+ </form>
529
+ </div>
530
+ {% endif %}
531
+
532
+ <footer>
533
+ Built with <span>♥</span> · Intel Image Classification Dataset ·
534
+ Custom CNN — no pretrained weights
535
+ </footer>
536
+
537
+
538
+ <script>
539
+ const dropZone = document.getElementById('drop-zone');
540
+ const fileInput = document.getElementById('file-input');
541
+ const fileName = document.getElementById('file-name');
542
+ const preview = document.getElementById('preview-img');
543
+ const previewW = document.getElementById('preview-wrap');
544
+ const submitBtn = document.getElementById('submit-btn');
545
+ const form = document.getElementById('upload-form');
546
+
547
+ if (!fileInput) { /* result page — skip */ }
548
+ else {
549
+
550
+ // Show file name + preview on selection
551
+ fileInput.addEventListener('change', () => {
552
+ const file = fileInput.files[0];
553
+ if (!file) return;
554
+ fileName.textContent = '✓ ' + file.name;
555
+
556
+ const reader = new FileReader();
557
+ reader.onload = e => {
558
+ preview.src = e.target.result;
559
+ previewW.style.display = 'block';
560
+ };
561
+ reader.readAsDataURL(file);
562
+ });
563
+
564
+ // Drag-and-drop visual feedback
565
+ ['dragenter','dragover'].forEach(ev =>
566
+ dropZone.addEventListener(ev, e => {
567
+ e.preventDefault();
568
+ dropZone.classList.add('drag-over');
569
+ })
570
+ );
571
+ ['dragleave','drop'].forEach(ev =>
572
+ dropZone.addEventListener(ev, e => {
573
+ e.preventDefault();
574
+ dropZone.classList.remove('drag-over');
575
+ })
576
+ );
577
+ dropZone.addEventListener('drop', e => {
578
+ const dt = e.dataTransfer;
579
+ if (dt.files[0]) {
580
+ // Assign dropped file to input
581
+ const dtTransfer = new DataTransfer();
582
+ dtTransfer.items.add(dt.files[0]);
583
+ fileInput.files = dtTransfer.files;
584
+ fileInput.dispatchEvent(new Event('change'));
585
+ }
586
+ });
587
+
588
+ // Loading state on submit
589
+ form.addEventListener('submit', () => {
590
+ submitBtn.textContent = '⏳ Classifying…';
591
+ submitBtn.classList.add('loading');
592
+ });
593
+ }
594
+ </script>
595
+
596
+ </body>
597
+ </html>