thejagstudio commited on
Commit
8fe61e6
·
verified ·
1 Parent(s): 43ccef6

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +499 -449
templates/index.html CHANGED
@@ -1,450 +1,500 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>AI Sneaker Category Predictor</title>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <script>
10
- tailwind.config = {
11
- theme: {
12
- extend: {
13
- colors: {
14
- 'neo-black': '#0F1116',
15
- 'neo-blue': '#2E3BFF'
16
- }
17
- }
18
- }
19
- }
20
- </script>
21
- <style>
22
- .glass-effect {
23
- background: rgba(255, 255, 255, 0.05);
24
- backdrop-filter: blur(10px);
25
- border: 1px solid rgba(255, 255, 255, 0.1);
26
- transition: all 0.3s ease;
27
- }
28
-
29
- .glass-effect:hover {
30
- background: rgba(255, 255, 255, 0.08);
31
- }
32
-
33
- .gradient-border {
34
- position: relative;
35
- border: double 1px transparent;
36
- border-radius: 0.5rem;
37
- background-image: linear-gradient(#0F1116, #0F1116),
38
- linear-gradient(to right, #2E3BFF, #7C3AED);
39
- background-origin: border-box;
40
- background-clip: padding-box, border-box;
41
- transition: all 0.3s ease;
42
- }
43
-
44
- .gradient-border:hover {
45
- background-image: linear-gradient(#0F1116, #0F1116),
46
- linear-gradient(to right, #3E4BFF, #8C4AFD);
47
- }
48
-
49
- .custom-select {
50
- position: relative;
51
- display: inline-block;
52
- width: 100%;
53
- }
54
-
55
- .custom-select select {
56
- display: none;
57
- }
58
-
59
- .select-selected {
60
- background-color: rgba(255, 255, 255, 0.05);
61
- padding: 0.5rem 1rem;
62
- border-radius: 0.5rem;
63
- cursor: pointer;
64
- }
65
-
66
- .select-items {
67
- position: absolute;
68
- padding: 3px;
69
- top: 100%;
70
- left: 0;
71
- right: 0;
72
- z-index: 99;
73
- background: rgb(0, 0, 0);
74
- backdrop-filter: blur(10px);
75
- border-radius: 0.5rem;
76
- margin-top: 0.5rem;
77
- max-height: 200px;
78
- overflow-y: auto;
79
- display: none;
80
- }
81
-
82
- .select-items div {
83
- padding: 0.5rem 1rem;
84
- cursor: pointer;
85
- transition: all 0.2s;
86
- }
87
-
88
- .select-items div:hover {
89
- background: #534dad96;
90
- border-radius: 0.5rem;
91
- }
92
-
93
- .drop-zone {
94
- border: 2px dashed rgba(46, 59, 255, 0.3);
95
- border-radius: 1rem;
96
- padding: 1rem;
97
- text-align: center;
98
- transition: all 0.3s ease;
99
- }
100
-
101
- .drop-zone.drag-over {
102
- border-color: #2E3BFF;
103
- background: rgba(46, 59, 255, 0.1);
104
- }
105
-
106
- .pulse {
107
- animation: pulse 2s infinite;
108
- }
109
-
110
- @keyframes pulse {
111
- 0% {
112
- transform: scale(1);
113
- }
114
-
115
- 50% {
116
- transform: scale(1.05);
117
- }
118
-
119
- 100% {
120
- transform: scale(1);
121
- }
122
- }
123
-
124
- /* Custom scrollbar */
125
- .select-items::-webkit-scrollbar {
126
- width: 6px;
127
- }
128
-
129
- .select-items::-webkit-scrollbar-track {
130
- background: rgba(255, 255, 255, 0.1);
131
- border-radius: 3px;
132
- }
133
-
134
- .select-items::-webkit-scrollbar-thumb {
135
- background: rgba(46, 59, 255, 0.5);
136
- border-radius: 3px;
137
- }
138
-
139
- .select-search {
140
- padding: 0.5rem;
141
- width: 100%;
142
- background: rgba(255, 255, 255, 0.05);
143
- border: 1px solid rgba(255, 255, 255, 0.1);
144
- border-radius: 0.25rem;
145
- color: white;
146
- margin-bottom: 0.5rem;
147
- }
148
-
149
- .select-search:focus {
150
- outline: none;
151
- border-color: rgba(46, 59, 255, 0.5);
152
- }
153
-
154
- .select-option-hidden {
155
- display: none;
156
- }
157
- </style>
158
- </head>
159
-
160
- <body class="bg-neo-black text-gray-100 min-h-screen">
161
- <div class="fixed w-full h-full">
162
- <div class="absolute top-0 left-0 w-96 h-96 bg-blue-500 rounded-full filter blur-[128px] opacity-20"></div>
163
- <div class="absolute bottom-0 right-0 w-96 h-96 bg-purple-500 rounded-full filter blur-[128px] opacity-20"></div>
164
- </div>
165
-
166
- <div class="container mx-auto px-4 py-8 w-full relative">
167
- <h1
168
- class="text-5xl font-bold text-center mb-12 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500 pulse">
169
- AI Sneaker Predictor
170
- </h1>
171
-
172
- <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
173
- <div class="glass-effect p-8 rounded-xl space-y-6 col-span-1 md:col-span-2 w-full h-fit">
174
- <div class="flex items-start justify-center gap-5 w-full">
175
- <div id="dropZone" class="drop-zone h-full aspect-square w-full flex items-center justify-center">
176
- <div id="imagePreview" class="hidden w-full h-full">
177
- <img id="preview" class="w-full h-full rounded-lg shadow-lg border border-blue-500/20 bg-gradient-to-tr from-blue-500/15 to-purple-500/15" alt="Preview">
178
- </div>
179
- <div id="dropText" class="text-blue-300">
180
- <svg class="w-12 h-12 mx-auto mb-4 text-blue-500" fill="none" stroke="currentColor"
181
- viewBox="0 0 24 24">
182
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
183
- d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
184
- </svg>
185
- <p class="text-lg">Drag and drop your sneaker image here</p>
186
- <p class="text-sm text-blue-400 mt-2">or click to browse</p>
187
- <input type="file" id="imageUpload" class="hidden" accept="image/*">
188
- </div>
189
- </div>
190
-
191
- <div class="space-y-6 w-full">
192
- <div class="custom-select">
193
- <label class="block text-sm font-medium text-blue-300 mb-2">Brand</label>
194
- <select id="brand" required>
195
- {% for brand in metadata.brand %}
196
- <option value="{{brand}}">{{brand}}</option>
197
- {% endfor %}
198
- </select>
199
- </div>
200
-
201
- <div class="custom-select">
202
- <label class="block text-sm font-medium text-blue-300 mb-2">Color</label>
203
- <select id="color" required>
204
- {% for color in metadata.color %}
205
- <option value="{{color}}">{{color}}</option>
206
- {% endfor %}
207
- </select>
208
- </div>
209
-
210
- <div class="custom-select">
211
- <label class="block text-sm font-medium text-blue-300 mb-2">Gender</label>
212
- <select id="gender" required>
213
- {% for gender in metadata.gender %}
214
- <option value="{{gender}}">{{gender}}</option>
215
- {% endfor %}
216
- </select>
217
- </div>
218
-
219
- <div class="custom-select">
220
- <label class="block text-sm font-medium text-blue-300 mb-2">Midsole</label>
221
- <select id="midsole" required>
222
- {% for midsole in metadata.midsole %}
223
- <option value="{{midsole}}">{% if midsole == "" %}Null{%else%}{{midsole}}{%endif%}</option>
224
- {% endfor %}
225
- </select>
226
- </div>
227
-
228
- <div class="custom-select">
229
- <label class="block text-sm font-medium text-blue-300 mb-2">Upper Material</label>
230
- <select id="upperMaterial" required>
231
- {% for upperMaterial in metadata.upperMaterial %}
232
- <option value="{{upperMaterial}}">{% if upperMaterial == "" %}Null{%else%}{{upperMaterial}}{%endif%}</option>
233
- {% endfor %}
234
- </select>
235
- </div>
236
- </div>
237
- </div>
238
- <button
239
- class="w-full py-4 px-6 rounded-lg font-medium transition-all duration-300 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 focus:ring-offset-neo-black transform hover:scale-105"
240
- onclick="predict()">
241
- <div class="flex items-center justify-center space-x-3">
242
- <svg class="w-6 h-6 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
243
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
244
- d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
245
- </svg>
246
- <span class="text-lg">Predict Category</span>
247
- </div>
248
- </button>
249
- </div>
250
-
251
- <div>
252
- <div id="result" class="hidden glass-effect p-6 rounded-xl">
253
- <h3 class="text-xl font-semibold mb-4 text-blue-300">AI Prediction</h3>
254
- <div id="predictions" class="space-y-4">
255
- <!-- Predictions will be inserted here -->
256
- </div>
257
- </div>
258
- </div>
259
- </div>
260
- </div>
261
-
262
- <script>
263
- function initCustomSelects() {
264
- document.querySelectorAll('.custom-select select').forEach(select => {
265
- const div = document.createElement('div');
266
- div.classList.add('select-selected', 'gradient-border');
267
- div.textContent = select.options[select.selectedIndex].text;
268
- select.parentElement.appendChild(div);
269
-
270
- const itemsDiv = document.createElement('div');
271
- itemsDiv.classList.add('select-items');
272
-
273
- // Add search input
274
- const searchInput = document.createElement('input');
275
- searchInput.type = 'text';
276
- searchInput.placeholder = 'Search...';
277
- searchInput.classList.add('select-search');
278
- itemsDiv.appendChild(searchInput);
279
-
280
- const optionsContainer = document.createElement('div');
281
- Array.from(select.options).forEach(option => {
282
- const optionDiv = document.createElement('div');
283
- optionDiv.textContent = option.text;
284
- optionDiv.addEventListener('click', () => {
285
- select.value = option.value;
286
- div.textContent = option.text;
287
- itemsDiv.style.display = 'none';
288
- });
289
- optionsContainer.appendChild(optionDiv);
290
- });
291
- itemsDiv.appendChild(optionsContainer);
292
-
293
- // Add search functionality
294
- searchInput.addEventListener('input', (e) => {
295
- const searchText = e.target.value.toLowerCase();
296
- Array.from(optionsContainer.children).forEach(optionDiv => {
297
- const text = optionDiv.textContent.toLowerCase();
298
- optionDiv.classList.toggle('select-option-hidden', !text.includes(searchText));
299
- });
300
- });
301
-
302
- // Prevent dropdown from closing when clicking search
303
- searchInput.addEventListener('click', (e) => {
304
- e.stopPropagation();
305
- });
306
-
307
- select.parentElement.appendChild(itemsDiv);
308
-
309
- div.addEventListener('click', (e) => {
310
- e.stopPropagation();
311
- closeAllSelect(itemsDiv);
312
- itemsDiv.style.display = itemsDiv.style.display === 'block' ? 'none' : 'block';
313
- if (itemsDiv.style.display === 'block') {
314
- searchInput.focus();
315
- searchInput.value = '';
316
- // Show all options when opening dropdown
317
- Array.from(optionsContainer.children).forEach(optionDiv => {
318
- optionDiv.classList.remove('select-option-hidden');
319
- });
320
- }
321
- });
322
- });
323
-
324
- document.addEventListener('click', () => closeAllSelect(null));
325
- }
326
-
327
- function closeAllSelect(elmnt) {
328
- document.querySelectorAll('.select-items').forEach(item => {
329
- if (item !== elmnt) item.style.display = 'none';
330
- });
331
- }
332
-
333
- const dropZone = document.getElementById('dropZone');
334
- const imageUpload = document.getElementById('imageUpload');
335
-
336
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
337
- dropZone.addEventListener(eventName, preventDefaults, false);
338
- });
339
-
340
- function preventDefaults(e) {
341
- e.preventDefault();
342
- e.stopPropagation();
343
- }
344
-
345
- ['dragenter', 'dragover'].forEach(eventName => {
346
- dropZone.addEventListener(eventName, () => dropZone.classList.add('drag-over'));
347
- });
348
-
349
- ['dragleave', 'drop'].forEach(eventName => {
350
- dropZone.addEventListener(eventName, () => dropZone.classList.remove('drag-over'));
351
- });
352
-
353
- dropZone.addEventListener('drop', handleDrop);
354
- dropZone.addEventListener('click', () => imageUpload.click());
355
-
356
- function handleDrop(e) {
357
- const dt = e.dataTransfer;
358
- const file = dt.files[0];
359
- handleFile(file);
360
- }
361
-
362
- document.getElementById('imageUpload').addEventListener('change', function (e) {
363
- const file = e.target.files[0];
364
- if (file) handleFile(file);
365
- });
366
-
367
- function handleFile(file) {
368
- if (file) {
369
- const reader = new FileReader();
370
- reader.onload = function (e) {
371
- document.getElementById('preview').src = e.target.result;
372
- document.getElementById('imagePreview').classList.remove('hidden');
373
- document.getElementById('dropText').classList.add('hidden');
374
- }
375
- reader.readAsDataURL(file);
376
- }
377
- }
378
-
379
- document.addEventListener('DOMContentLoaded', initCustomSelects);
380
-
381
- async function predict() {
382
- const imageFile = document.getElementById('imageUpload').files[0];
383
- if (!imageFile) {
384
- alert('Please select an image');
385
- return;
386
- }
387
-
388
- const reader = new FileReader();
389
- reader.onload = async function (e) {
390
- const base64Image = e.target.result.split(',')[1];
391
-
392
- const data = {
393
- image: base64Image,
394
- brand: document.getElementById('brand').value,
395
- color: document.getElementById('color').value,
396
- gender: document.getElementById('gender').value,
397
- midsole: document.getElementById('midsole').value,
398
- upperMaterial: document.getElementById('upperMaterial').value
399
- };
400
-
401
- try {
402
- const response = await fetch('/predict', {
403
- method: 'POST',
404
- headers: {
405
- 'Content-Type': 'application/json'
406
- },
407
- body: JSON.stringify(data)
408
- });
409
-
410
- const result = await response.json();
411
- if (result.error) {
412
- alert('Error: ' + result.error);
413
- } else {
414
- document.getElementById('result').classList.remove('hidden');
415
- const predictionsContainer = document.getElementById('predictions');
416
- predictionsContainer.innerHTML = '';
417
-
418
- // Sort categories by confidence
419
- const predictions = result.categories.map((category, index) => ({
420
- category,
421
- confidence: result.confidence[index]
422
- })).sort((a, b) => b.confidence - a.confidence);
423
-
424
- predictions.forEach(({ category, confidence }) => {
425
- const confidencePercent = (confidence * 100).toFixed(2);
426
- const predictionHtml = `
427
- <div class="gradient-border p-4">
428
- <div class="flex items-center justify-between">
429
- <p>Category: <span class="font-semibold text-blue-400">${category}</span></p>
430
- <p>Confidence: <span class="font-semibold text-blue-400">${confidencePercent}</span>%</p>
431
- </div>
432
- <div class="w-full bg-gray-700/30 rounded-full h-4 mt-2">
433
- <div class="bg-gradient-to-r from-blue-500 to-purple-500 h-4 rounded-full transition-all duration-500"
434
- style="width: ${confidencePercent}%"></div>
435
- </div>
436
-
437
- </div>`;
438
- predictionsContainer.innerHTML += predictionHtml;
439
- });
440
- }
441
- } catch (error) {
442
- alert('Error: ' + error.message);
443
- }
444
- };
445
- reader.readAsDataURL(imageFile);
446
- }
447
- </script>
448
- </body>
449
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>AI Sneaker Category Predictor</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ 'neo-black': '#0F1116',
15
+ 'neo-blue': '#2E3BFF'
16
+ }
17
+ }
18
+ }
19
+ }
20
+ </script>
21
+ <style>
22
+ .glass-effect {
23
+ background: rgba(255, 255, 255, 0.05);
24
+ backdrop-filter: blur(10px);
25
+ border: 1px solid rgba(255, 255, 255, 0.1);
26
+ transition: all 0.3s ease;
27
+ }
28
+
29
+ .glass-effect:hover {
30
+ background: rgba(255, 255, 255, 0.08);
31
+ }
32
+
33
+ .gradient-border {
34
+ position: relative;
35
+ border: double 1px transparent;
36
+ border-radius: 0.5rem;
37
+ background-image: linear-gradient(#0F1116, #0F1116),
38
+ linear-gradient(to right, #2E3BFF, #7C3AED);
39
+ background-origin: border-box;
40
+ background-clip: padding-box, border-box;
41
+ transition: all 0.3s ease;
42
+ }
43
+
44
+ .gradient-border:hover {
45
+ background-image: linear-gradient(#0F1116, #0F1116),
46
+ linear-gradient(to right, #3E4BFF, #8C4AFD);
47
+ }
48
+
49
+ .custom-select {
50
+ position: relative;
51
+ display: inline-block;
52
+ width: 100%;
53
+ }
54
+
55
+ .custom-select select {
56
+ display: none;
57
+ }
58
+
59
+ .select-selected {
60
+ background-color: rgba(255, 255, 255, 0.05);
61
+ padding: 0.5rem 1rem;
62
+ border-radius: 0.5rem;
63
+ cursor: pointer;
64
+ }
65
+
66
+ .select-items {
67
+ position: absolute;
68
+ padding: 3px;
69
+ top: 100%;
70
+ left: 0;
71
+ right: 0;
72
+ z-index: 99;
73
+ background: rgb(0, 0, 0);
74
+ backdrop-filter: blur(10px);
75
+ border-radius: 0.5rem;
76
+ margin-top: 0.5rem;
77
+ max-height: 200px;
78
+ overflow-y: auto;
79
+ display: none;
80
+ }
81
+
82
+ .select-items div {
83
+ padding: 0.5rem 1rem;
84
+ cursor: pointer;
85
+ transition: all 0.2s;
86
+ }
87
+
88
+ .select-items div:hover {
89
+ background: #534dad96;
90
+ border-radius: 0.5rem;
91
+ }
92
+
93
+ .drop-zone {
94
+ border: 2px dashed rgba(46, 59, 255, 0.3);
95
+ border-radius: 1rem;
96
+ padding: 1rem;
97
+ text-align: center;
98
+ transition: all 0.3s ease;
99
+ }
100
+
101
+ .drop-zone.drag-over {
102
+ border-color: #2E3BFF;
103
+ background: rgba(46, 59, 255, 0.1);
104
+ }
105
+
106
+ .pulse {
107
+ animation: pulse 2s infinite;
108
+ }
109
+
110
+ @keyframes pulse {
111
+ 0% {
112
+ transform: scale(1);
113
+ }
114
+
115
+ 50% {
116
+ transform: scale(1.05);
117
+ }
118
+
119
+ 100% {
120
+ transform: scale(1);
121
+ }
122
+ }
123
+
124
+ /* Custom scrollbar */
125
+ .select-items::-webkit-scrollbar {
126
+ width: 6px;
127
+ }
128
+
129
+ .select-items::-webkit-scrollbar-track {
130
+ background: rgba(255, 255, 255, 0.1);
131
+ border-radius: 3px;
132
+ }
133
+
134
+ .select-items::-webkit-scrollbar-thumb {
135
+ background: rgba(46, 59, 255, 0.5);
136
+ border-radius: 3px;
137
+ }
138
+
139
+ .select-search {
140
+ padding: 0.5rem;
141
+ width: 100%;
142
+ background: rgba(255, 255, 255, 0.05);
143
+ border: 1px solid rgba(255, 255, 255, 0.1);
144
+ border-radius: 0.25rem;
145
+ color: white;
146
+ margin-bottom: 0.5rem;
147
+ }
148
+
149
+ .select-search:focus {
150
+ outline: none;
151
+ border-color: rgba(46, 59, 255, 0.5);
152
+ }
153
+
154
+ .select-option-hidden {
155
+ display: none;
156
+ }
157
+ </style>
158
+ </head>
159
+
160
+ <body class="bg-neo-black text-gray-100 min-h-screen">
161
+ <div class="fixed w-full h-full">
162
+ <div class="absolute top-0 left-0 w-96 h-96 bg-blue-500 rounded-full filter blur-[128px] opacity-20"></div>
163
+ <div class="absolute bottom-0 right-0 w-96 h-96 bg-purple-500 rounded-full filter blur-[128px] opacity-20"></div>
164
+ </div>
165
+
166
+ <div class="container mx-auto px-4 py-8 w-full relative">
167
+ <h1
168
+ class="text-5xl font-bold text-center mb-12 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500">
169
+ AI Sneaker Predictor
170
+ </h1>
171
+
172
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
173
+ <div class="flex flex-col items-center justify-center w-full col-span-1 md:col-span-2">
174
+ <div class="glass-effect p-8 rounded-xl space-y-6 w-full h-fit">
175
+ <div class="flex items-start justify-center gap-5 w-full">
176
+ <div id="dropZone" class="drop-zone h-full aspect-square w-full flex items-center justify-center">
177
+ <div id="imagePreview" class="hidden w-full h-full">
178
+ <img id="preview" class="w-full h-full rounded-lg shadow-lg border border-blue-500/20 bg-gradient-to-tr from-blue-500/15 to-purple-500/15" alt="Preview">
179
+ </div>
180
+ <div id="dropText" class="text-blue-300">
181
+ <svg class="w-12 h-12 mx-auto mb-4 text-blue-500" fill="none" stroke="currentColor"
182
+ viewBox="0 0 24 24">
183
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
184
+ d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
185
+ </svg>
186
+ <p class="text-lg">Drag and drop your sneaker image here</p>
187
+ <p class="text-sm text-blue-400 mt-2">or click to browse</p>
188
+ <input type="file" id="imageUpload" class="hidden" accept="image/*">
189
+ </div>
190
+ </div>
191
+
192
+ <div class="space-y-6 w-full">
193
+ <div class="custom-select">
194
+ <label class="block text-sm font-medium text-blue-300 mb-2">Brand</label>
195
+ <select id="brand" required>
196
+ {% for brand in metadata.brand %}
197
+ <option value="{{brand}}">{{brand}}</option>
198
+ {% endfor %}
199
+ </select>
200
+ </div>
201
+
202
+ <div class="custom-select">
203
+ <label class="block text-sm font-medium text-blue-300 mb-2">Color</label>
204
+ <select id="color" required>
205
+ {% for color in metadata.color %}
206
+ <option value="{{color}}">{{color}}</option>
207
+ {% endfor %}
208
+ </select>
209
+ </div>
210
+
211
+ <div class="custom-select">
212
+ <label class="block text-sm font-medium text-blue-300 mb-2">Gender</label>
213
+ <select id="gender" required>
214
+ {% for gender in metadata.gender %}
215
+ <option value="{{gender}}">{{gender}}</option>
216
+ {% endfor %}
217
+ </select>
218
+ </div>
219
+
220
+ <div class="custom-select">
221
+ <label class="block text-sm font-medium text-blue-300 mb-2">Midsole</label>
222
+ <select id="midsole" required>
223
+ {% for midsole in metadata.midsole %}
224
+ <option value="{{midsole}}">{% if midsole == "" %}Null{%else%}{{midsole}}{%endif%}</option>
225
+ {% endfor %}
226
+ </select>
227
+ </div>
228
+
229
+ <div class="custom-select">
230
+ <label class="block text-sm font-medium text-blue-300 mb-2">Upper Material</label>
231
+ <select id="upperMaterial" required>
232
+ {% for upperMaterial in metadata.upperMaterial %}
233
+ <option value="{{upperMaterial}}">{% if upperMaterial == "" %}Null{%else%}{{upperMaterial}}{%endif%}</option>
234
+ {% endfor %}
235
+ </select>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ <button
240
+ class="w-full py-4 px-6 rounded-lg font-medium transition-all duration-300 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 focus:ring-offset-neo-black transform hover:scale-105"
241
+ onclick="predict()">
242
+ <div class="flex items-center justify-center space-x-3">
243
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
244
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
245
+ d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
246
+ </svg>
247
+ <span class="text-lg">Predict Category</span>
248
+ </div>
249
+ </button>
250
+ </div>
251
+ <div class="mt-8">
252
+ <h3 class="text-xl font-semibold mb-4 text-blue-300">Example Sneakers</h3>
253
+ <div class="grid grid-cols-3 gap-4">
254
+ {% for product in products %}
255
+ <div class="glass-effect p-4 rounded-xl cursor-pointer hover:scale-105 transition-transform" onclick="loadExample('{{loop.index0}}')">
256
+ <img src="{{product.img}}" alt="{{product.name}}" class="w-full rounded-lg mb-2">
257
+ <p class="text-sm text-blue-300">{{product.name}} ({{product.category}})</p>
258
+ </div>
259
+ {% endfor %}
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <div>
265
+ <div id="result" class="hidden glass-effect p-6 rounded-xl">
266
+ <h3 class="text-xl font-semibold mb-4 text-blue-300">AI Prediction</h3>
267
+ <div id="predictions" class="space-y-4">
268
+ <!-- Predictions will be inserted here -->
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <script>
276
+ function initCustomSelects() {
277
+ document.querySelectorAll('.custom-select select').forEach(select => {
278
+ const div = document.createElement('div');
279
+ div.classList.add('select-selected', 'gradient-border');
280
+ div.textContent = select.options[select.selectedIndex].text;
281
+ select.parentElement.appendChild(div);
282
+
283
+ const itemsDiv = document.createElement('div');
284
+ itemsDiv.classList.add('select-items');
285
+
286
+ // Add search input
287
+ const searchInput = document.createElement('input');
288
+ searchInput.type = 'text';
289
+ searchInput.placeholder = 'Search...';
290
+ searchInput.classList.add('select-search');
291
+ itemsDiv.appendChild(searchInput);
292
+
293
+ const optionsContainer = document.createElement('div');
294
+ Array.from(select.options).forEach(option => {
295
+ const optionDiv = document.createElement('div');
296
+ optionDiv.textContent = option.text;
297
+ optionDiv.addEventListener('click', () => {
298
+ select.value = option.value;
299
+ div.textContent = option.text;
300
+ itemsDiv.style.display = 'none';
301
+ });
302
+ optionsContainer.appendChild(optionDiv);
303
+ });
304
+ itemsDiv.appendChild(optionsContainer);
305
+
306
+ // Add search functionality
307
+ searchInput.addEventListener('input', (e) => {
308
+ const searchText = e.target.value.toLowerCase();
309
+ Array.from(optionsContainer.children).forEach(optionDiv => {
310
+ const text = optionDiv.textContent.toLowerCase();
311
+ optionDiv.classList.toggle('select-option-hidden', !text.includes(searchText));
312
+ });
313
+ });
314
+
315
+ // Prevent dropdown from closing when clicking search
316
+ searchInput.addEventListener('click', (e) => {
317
+ e.stopPropagation();
318
+ });
319
+
320
+ select.parentElement.appendChild(itemsDiv);
321
+
322
+ div.addEventListener('click', (e) => {
323
+ e.stopPropagation();
324
+ closeAllSelect(itemsDiv);
325
+ itemsDiv.style.display = itemsDiv.style.display === 'block' ? 'none' : 'block';
326
+ if (itemsDiv.style.display === 'block') {
327
+ searchInput.focus();
328
+ searchInput.value = '';
329
+ // Show all options when opening dropdown
330
+ Array.from(optionsContainer.children).forEach(optionDiv => {
331
+ optionDiv.classList.remove('select-option-hidden');
332
+ });
333
+ }
334
+ });
335
+ });
336
+
337
+ document.addEventListener('click', () => closeAllSelect(null));
338
+ }
339
+
340
+ function closeAllSelect(elmnt) {
341
+ document.querySelectorAll('.select-items').forEach(item => {
342
+ if (item !== elmnt) item.style.display = 'none';
343
+ });
344
+ }
345
+
346
+ const dropZone = document.getElementById('dropZone');
347
+ const imageUpload = document.getElementById('imageUpload');
348
+
349
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
350
+ dropZone.addEventListener(eventName, preventDefaults, false);
351
+ });
352
+
353
+ function preventDefaults(e) {
354
+ e.preventDefault();
355
+ e.stopPropagation();
356
+ }
357
+
358
+ ['dragenter', 'dragover'].forEach(eventName => {
359
+ dropZone.addEventListener(eventName, () => dropZone.classList.add('drag-over'));
360
+ });
361
+
362
+ ['dragleave', 'drop'].forEach(eventName => {
363
+ dropZone.addEventListener(eventName, () => dropZone.classList.remove('drag-over'));
364
+ });
365
+
366
+ dropZone.addEventListener('drop', handleDrop);
367
+ dropZone.addEventListener('click', () => imageUpload.click());
368
+
369
+ function handleDrop(e) {
370
+ const dt = e.dataTransfer;
371
+ const file = dt.files[0];
372
+ handleFile(file);
373
+ }
374
+
375
+ document.getElementById('imageUpload').addEventListener('change', function (e) {
376
+ const file = e.target.files[0];
377
+ if (file) handleFile(file);
378
+ });
379
+
380
+ function handleFile(file) {
381
+ if (file) {
382
+ const reader = new FileReader();
383
+ reader.onload = function (e) {
384
+ document.getElementById('preview').src = e.target.result;
385
+ document.getElementById('imagePreview').classList.remove('hidden');
386
+ document.getElementById('dropText').classList.add('hidden');
387
+ }
388
+ reader.readAsDataURL(file);
389
+ }
390
+ }
391
+
392
+ document.addEventListener('DOMContentLoaded', () => {
393
+ initCustomSelects();
394
+
395
+ // Add examples data
396
+ window.examples = `{{ productsString | safe }}`;
397
+ window.examples = JSON.parse(window.examples);
398
+ });
399
+
400
+ async function loadExample(index) {
401
+ index = parseInt(index);
402
+ const example = window.examples[index];
403
+
404
+ // Set form values
405
+ document.getElementById('brand').value = example.brand;
406
+ document.getElementById('color').value = example.color;
407
+ document.getElementById('gender').value = example.gender;
408
+ document.getElementById('midsole').value = example.midsole;
409
+ document.getElementById('upperMaterial').value = example.upperMaterial;
410
+
411
+ // Update custom select displays
412
+ document.querySelectorAll('.select-selected').forEach(div => {
413
+ const select = div.previousElementSibling;
414
+ div.textContent = select.options[select.selectedIndex].text;
415
+ });
416
+
417
+ // Load and display image
418
+ const response = await fetch(example.img);
419
+ const blob = await response.blob();
420
+ const file = new File([blob], 'example.png', { type: 'image/png' });
421
+
422
+ // Trigger file handler
423
+ handleFile(file);
424
+
425
+ // Update the hidden file input
426
+ const dataTransfer = new DataTransfer();
427
+ dataTransfer.items.add(file);
428
+ document.getElementById('imageUpload').files = dataTransfer.files;
429
+ }
430
+
431
+ async function predict() {
432
+ const imageFile = document.getElementById('imageUpload').files[0];
433
+ if (!imageFile) {
434
+ alert('Please select an image');
435
+ return;
436
+ }
437
+
438
+ const reader = new FileReader();
439
+ reader.onload = async function (e) {
440
+ const base64Image = e.target.result.split(',')[1];
441
+
442
+ const data = {
443
+ image: base64Image,
444
+ brand: document.getElementById('brand').value,
445
+ color: document.getElementById('color').value,
446
+ gender: document.getElementById('gender').value,
447
+ midsole: document.getElementById('midsole').value,
448
+ upperMaterial: document.getElementById('upperMaterial').value
449
+ };
450
+
451
+ try {
452
+ const response = await fetch('/predict', {
453
+ method: 'POST',
454
+ headers: {
455
+ 'Content-Type': 'application/json'
456
+ },
457
+ body: JSON.stringify(data)
458
+ });
459
+
460
+ const result = await response.json();
461
+ if (result.error) {
462
+ alert('Error: ' + result.error);
463
+ } else {
464
+ document.getElementById('result').classList.remove('hidden');
465
+ const predictionsContainer = document.getElementById('predictions');
466
+ predictionsContainer.innerHTML = '';
467
+
468
+ // Sort categories by confidence
469
+ const predictions = result.categories.map((category, index) => ({
470
+ category,
471
+ confidence: result.confidence[index]
472
+ })).sort((a, b) => b.confidence - a.confidence);
473
+
474
+ predictions.forEach(({ category, confidence }) => {
475
+ const confidencePercent = (confidence * 100).toFixed(2);
476
+ const predictionHtml = `
477
+ <div class="gradient-border p-4">
478
+ <div class="flex items-center justify-between">
479
+ <p>Category: <span class="font-semibold text-blue-400">${category}</span></p>
480
+ <p>Confidence: <span class="font-semibold text-blue-400">${confidencePercent}</span>%</p>
481
+ </div>
482
+ <div class="w-full bg-gray-700/30 rounded-full h-4 mt-2">
483
+ <div class="bg-gradient-to-r from-blue-500 to-purple-500 h-4 rounded-full transition-all duration-500"
484
+ style="width: ${confidencePercent}%"></div>
485
+ </div>
486
+
487
+ </div>`;
488
+ predictionsContainer.innerHTML += predictionHtml;
489
+ });
490
+ }
491
+ } catch (error) {
492
+ alert('Error: ' + error.message);
493
+ }
494
+ };
495
+ reader.readAsDataURL(imageFile);
496
+ }
497
+ </script>
498
+ </body>
499
+
500
  </html>