efawggthsa commited on
Commit
04a6616
·
verified ·
1 Parent(s): f9058b5

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +696 -19
index.html CHANGED
@@ -1,19 +1,696 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>CLIP Batch Image Classifier</title>
7
+ <style>
8
+ :root {
9
+ --bg-color: #0f172a;
10
+ --surface-color: #1e293b;
11
+ --primary-color: #3b82f6;
12
+ --primary-hover: #2563eb;
13
+ --accent-color: #10b981;
14
+ --text-main: #f8fafc;
15
+ --text-secondary: #94a3b8;
16
+ --border-color: #334155;
17
+ --radius-md: 12px;
18
+ --radius-lg: 16px;
19
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ margin: 0;
25
+ padding: 0;
26
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
27
+ }
28
+
29
+ body {
30
+ background-color: var(--bg-color);
31
+ color: var(--text-main);
32
+ min-height: 100vh;
33
+ display: flex;
34
+ flex-direction: column;
35
+ }
36
+
37
+ /* Header */
38
+ header {
39
+ background-color: rgba(30, 41, 59, 0.8);
40
+ backdrop-filter: blur(10px);
41
+ border-bottom: 1px solid var(--border-color);
42
+ padding: 1rem 2rem;
43
+ position: sticky;
44
+ top: 0;
45
+ z-index: 100;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ }
50
+
51
+ .brand {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 12px;
55
+ font-weight: 700;
56
+ font-size: 1.25rem;
57
+ color: var(--text-main);
58
+ }
59
+
60
+ .brand-icon {
61
+ width: 32px;
62
+ height: 32px;
63
+ background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
64
+ border-radius: 8px;
65
+ display: grid;
66
+ place-items: center;
67
+ font-size: 1.2rem;
68
+ }
69
+
70
+ .anycoder-link {
71
+ font-size: 0.875rem;
72
+ color: var(--text-secondary);
73
+ text-decoration: none;
74
+ transition: color 0.2s;
75
+ border: 1px solid var(--border-color);
76
+ padding: 6px 12px;
77
+ border-radius: 20px;
78
+ }
79
+
80
+ .anycoder-link:hover {
81
+ color: var(--primary-color);
82
+ border-color: var(--primary-color);
83
+ }
84
+
85
+ /* Main Layout */
86
+ main {
87
+ flex: 1;
88
+ display: grid;
89
+ grid-template-columns: 350px 1fr;
90
+ gap: 2rem;
91
+ padding: 2rem;
92
+ max-width: 1600px;
93
+ margin: 0 auto;
94
+ width: 100%;
95
+ }
96
+
97
+ @media (max-width: 900px) {
98
+ main {
99
+ grid-template-columns: 1fr;
100
+ }
101
+ }
102
+
103
+ /* Sidebar / Controls */
104
+ .sidebar {
105
+ background-color: var(--surface-color);
106
+ padding: 1.5rem;
107
+ border-radius: var(--radius-lg);
108
+ border: 1px solid var(--border-color);
109
+ height: fit-content;
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 1.5rem;
113
+ }
114
+
115
+ h2 {
116
+ font-size: 1.1rem;
117
+ margin-bottom: 0.5rem;
118
+ color: var(--text-main);
119
+ }
120
+
121
+ .control-group {
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 0.5rem;
125
+ }
126
+
127
+ label {
128
+ font-size: 0.875rem;
129
+ color: var(--text-secondary);
130
+ font-weight: 500;
131
+ }
132
+
133
+ textarea {
134
+ background-color: var(--bg-color);
135
+ border: 1px solid var(--border-color);
136
+ border-radius: var(--radius-md);
137
+ color: var(--text-main);
138
+ padding: 0.75rem;
139
+ font-size: 0.95rem;
140
+ resize: vertical;
141
+ min-height: 80px;
142
+ outline: none;
143
+ transition: border-color 0.2s;
144
+ }
145
+
146
+ textarea:focus {
147
+ border-color: var(--primary-color);
148
+ }
149
+
150
+ /* File Input Styling */
151
+ .file-drop-zone {
152
+ border: 2px dashed var(--border-color);
153
+ border-radius: var(--radius-md);
154
+ padding: 2rem;
155
+ text-align: center;
156
+ cursor: pointer;
157
+ transition: all 0.2s;
158
+ position: relative;
159
+ background-color: rgba(255,255,255,0.02);
160
+ }
161
+
162
+ .file-drop-zone:hover, .file-drop-zone.dragover {
163
+ border-color: var(--primary-color);
164
+ background-color: rgba(59, 130, 246, 0.05);
165
+ }
166
+
167
+ .file-drop-zone input {
168
+ position: absolute;
169
+ top: 0;
170
+ left: 0;
171
+ width: 100%;
172
+ height: 100%;
173
+ opacity: 0;
174
+ cursor: pointer;
175
+ }
176
+
177
+ .file-info {
178
+ font-size: 0.875rem;
179
+ color: var(--text-secondary);
180
+ pointer-events: none;
181
+ }
182
+
183
+ .file-count {
184
+ color: var(--primary-color);
185
+ font-weight: bold;
186
+ display: block;
187
+ margin-top: 5px;
188
+ }
189
+
190
+ /* Buttons */
191
+ .btn {
192
+ background-color: var(--primary-color);
193
+ color: white;
194
+ border: none;
195
+ padding: 0.875rem;
196
+ border-radius: var(--radius-md);
197
+ font-weight: 600;
198
+ cursor: pointer;
199
+ transition: background-color 0.2s, transform 0.1s;
200
+ display: flex;
201
+ justify-content: center;
202
+ align-items: center;
203
+ gap: 8px;
204
+ }
205
+
206
+ .btn:hover {
207
+ background-color: var(--primary-hover);
208
+ }
209
+
210
+ .btn:active {
211
+ transform: scale(0.98);
212
+ }
213
+
214
+ .btn:disabled {
215
+ background-color: var(--border-color);
216
+ cursor: not-allowed;
217
+ opacity: 0.7;
218
+ }
219
+
220
+ /* Model Status */
221
+ .status-badge {
222
+ display: inline-flex;
223
+ align-items: center;
224
+ gap: 6px;
225
+ padding: 6px 12px;
226
+ border-radius: 20px;
227
+ font-size: 0.75rem;
228
+ font-weight: 600;
229
+ background-color: rgba(255, 255, 255, 0.1);
230
+ }
231
+
232
+ .status-badge.loading {
233
+ color: #fbbf24;
234
+ background-color: rgba(251, 191, 36, 0.1);
235
+ }
236
+
237
+ .status-badge.ready {
238
+ color: var(--accent-color);
239
+ background-color: rgba(16, 185, 129, 0.1);
240
+ }
241
+
242
+ .status-dot {
243
+ width: 8px;
244
+ height: 8px;
245
+ border-radius: 50%;
246
+ background-color: currentColor;
247
+ }
248
+
249
+ .status-badge.loading .status-dot {
250
+ animation: pulse 1.5s infinite;
251
+ }
252
+
253
+ @keyframes pulse {
254
+ 0% { opacity: 0.4; }
255
+ 50% { opacity: 1; }
256
+ 100% { opacity: 0.4; }
257
+ }
258
+
259
+ /* Results Area */
260
+ .results-area {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 1rem;
264
+ }
265
+
266
+ .results-header {
267
+ display: flex;
268
+ justify-content: space-between;
269
+ align-items: center;
270
+ }
271
+
272
+ .empty-state {
273
+ display: flex;
274
+ flex-direction: column;
275
+ align-items: center;
276
+ justify-content: center;
277
+ height: 100%;
278
+ color: var(--text-secondary);
279
+ text-align: center;
280
+ padding: 4rem;
281
+ background-color: var(--surface-color);
282
+ border-radius: var(--radius-lg);
283
+ border: 1px dashed var(--border-color);
284
+ }
285
+
286
+ /* Grid */
287
+ .results-grid {
288
+ display: grid;
289
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
290
+ gap: 1.5rem;
291
+ }
292
+
293
+ /* Image Card */
294
+ .image-card {
295
+ background-color: var(--surface-color);
296
+ border-radius: var(--radius-md);
297
+ overflow: hidden;
298
+ border: 1px solid var(--border-color);
299
+ transition: transform 0.2s, box-shadow 0.2s;
300
+ display: flex;
301
+ flex-direction: column;
302
+ }
303
+
304
+ .image-card:hover {
305
+ transform: translateY(-2px);
306
+ box-shadow: var(--shadow);
307
+ border-color: var(--primary-color);
308
+ }
309
+
310
+ .card-img-wrapper {
311
+ width: 100%;
312
+ height: 200px;
313
+ background-color: #000;
314
+ position: relative;
315
+ overflow: hidden;
316
+ }
317
+
318
+ .card-img {
319
+ width: 100%;
320
+ height: 100%;
321
+ object-fit: cover;
322
+ transition: transform 0.3s;
323
+ }
324
+
325
+ .image-card:hover .card-img {
326
+ transform: scale(1.05);
327
+ }
328
+
329
+ .card-content {
330
+ padding: 1rem;
331
+ flex: 1;
332
+ display: flex;
333
+ flex-direction: column;
334
+ justify-content: space-between;
335
+ }
336
+
337
+ .score-row {
338
+ margin-top: 0.5rem;
339
+ }
340
+
341
+ .score-label {
342
+ display: flex;
343
+ justify-content: space-between;
344
+ font-size: 0.8rem;
345
+ color: var(--text-secondary);
346
+ margin-bottom: 4px;
347
+ }
348
+
349
+ .score-bar-bg {
350
+ height: 6px;
351
+ background-color: var(--bg-color);
352
+ border-radius: 3px;
353
+ overflow: hidden;
354
+ }
355
+
356
+ .score-bar-fill {
357
+ height: 100%;
358
+ background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
359
+ width: 0%;
360
+ transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
361
+ }
362
+
363
+ /* Toast */
364
+ .toast-container {
365
+ position: fixed;
366
+ bottom: 2rem;
367
+ right: 2rem;
368
+ z-index: 1000;
369
+ display: flex;
370
+ flex-direction: column;
371
+ gap: 0.5rem;
372
+ }
373
+
374
+ .toast {
375
+ background-color: var(--surface-color);
376
+ border-left: 4px solid var(--primary-color);
377
+ padding: 1rem 1.5rem;
378
+ border-radius: 8px;
379
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
380
+ color: var(--text-main);
381
+ font-size: 0.9rem;
382
+ animation: slideIn 0.3s ease-out;
383
+ max-width: 300px;
384
+ }
385
+
386
+ @keyframes slideIn {
387
+ from { transform: translateX(100%); opacity: 0; }
388
+ to { transform: translateX(0); opacity: 1; }
389
+ }
390
+
391
+ /* Loader */
392
+ .spinner {
393
+ width: 18px;
394
+ height: 18px;
395
+ border: 2px solid rgba(255,255,255,0.3);
396
+ border-top-color: #fff;
397
+ border-radius: 50%;
398
+ animation: spin 0.8s linear infinite;
399
+ }
400
+
401
+ @keyframes spin {
402
+ to { transform: rotate(360deg); }
403
+ }
404
+ </style>
405
+ </head>
406
+ <body>
407
+
408
+ <header>
409
+ <div class="brand">
410
+ <div class="brand-icon">🔮</div>
411
+ <span>NeuroSort CLIP</span>
412
+ </div>
413
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
414
+ Built with anycoder
415
+ </a>
416
+ </header>
417
+
418
+ <main>
419
+ <!-- Sidebar Controls -->
420
+ <aside class="sidebar">
421
+ <!-- Model Status -->
422
+ <div class="control-group">
423
+ <label>System Status</label>
424
+ <div id="modelStatus" class="status-badge loading">
425
+ <div class="status-dot"></div>
426
+ <span id="statusText">Initializing Model...</span>
427
+ </div>
428
+ </div>
429
+
430
+ <hr style="border: 0; border-top: 1px solid var(--border-color);">
431
+
432
+ <!-- Text Input -->
433
+ <div class="control-group">
434
+ <label for="textInput">Natural Language Query</label>
435
+ <textarea id="textInput" placeholder="Describe what you want to find (e.g., 'a futuristic city at night', 'a golden retriever', 'a red sports car')..."></textarea>
436
+ </div>
437
+
438
+ <!-- Image Upload -->
439
+ <div class="control-group">
440
+ <label>Batch Images</label>
441
+ <div class="file-drop-zone" id="dropZone">
442
+ <input type="file" id="fileInput" multiple accept="image/png, image/jpeg, image/webp, image/gif">
443
+ <div class="file-info">
444
+ <span>Click or Drag & Drop images here</span>
445
+ <span class="file-count" id="fileCount">0 files selected</span>
446
+ </div>
447
+ </div>
448
+ </div>
449
+
450
+ <!-- Action Button -->
451
+ <button id="classifyBtn" class="btn" disabled>
452
+ <span>Classify Images</span>
453
+ </button>
454
+ </aside>
455
+
456
+ <!-- Results Section -->
457
+ <section class="results-area">
458
+ <div class="results-header">
459
+ <h2>Classification Results</h2>
460
+ <span style="font-size: 0.875rem; color: var(--text-secondary);" id="resultCount"></span>
461
+ </div>
462
+
463
+ <div id="resultsContainer">
464
+ <div class="empty-state">
465
+ <div style="font-size: 3rem; margin-bottom: 1rem; opacity: 0.5;">🖼️</div>
466
+ <h3>No results yet</h3>
467
+ <p>Upload images and enter a prompt to start classifying.</p>
468
+ </div>
469
+ </div>
470
+ </section>
471
+ </main>
472
+
473
+ <div class="toast-container" id="toastContainer"></div>
474
+
475
+ <!-- Import Transformers.js -->
476
+ <script type="module">
477
+ import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.14.0';
478
+
479
+ // Configuration
480
+ env.allowLocalModels = false;
481
+ env.useBrowserCache = true;
482
+
483
+ // DOM Elements
484
+ const modelStatus = document.getElementById('modelStatus');
485
+ const statusText = document.getElementById('statusText');
486
+ const textInput = document.getElementById('textInput');
487
+ const fileInput = document.getElementById('fileInput');
488
+ const dropZone = document.getElementById('dropZone');
489
+ const fileCount = document.getElementById('fileCount');
490
+ const classifyBtn = document.getElementById('classifyBtn');
491
+ const resultsContainer = document.getElementById('resultsContainer');
492
+ const resultCount = document.getElementById('resultCount');
493
+ const toastContainer = document.getElementById('toastContainer');
494
+
495
+ // State
496
+ let classifier = null;
497
+ let selectedFiles = [];
498
+
499
+ // --- 1. Model Loading ---
500
+ async function loadModel() {
501
+ try {
502
+ statusText.textContent = "Downloading CLIP Model (60MB)...";
503
+ // Using Xenova's CLIP model optimized for browser
504
+ classifier = await pipeline('zero-shot-image-classification', 'Xenova/clip-vit-base-patch32', {
505
+ progress_callback: (data) => {
506
+ if (data.status === 'progress') {
507
+ const percent = Math.round(data.progress || 0);
508
+ statusText.textContent = `Loading Model... ${percent}%`;
509
+ }
510
+ }
511
+ });
512
+
513
+ // Update UI to ready
514
+ modelStatus.classList.remove('loading');
515
+ modelStatus.classList.add('ready');
516
+ statusText.textContent = "Model Ready";
517
+ showToast("Model loaded successfully!", "success");
518
+ checkReady();
519
+ } catch (error) {
520
+ console.error(error);
521
+ statusText.textContent = "Error Loading Model";
522
+ showToast("Failed to load AI model.", "error");
523
+ }
524
+ }
525
+
526
+ // --- 2. File Handling ---
527
+ function handleFiles(files) {
528
+ selectedFiles = Array.from(files).filter(f => f.type.startsWith('image/'));
529
+
530
+ if (selectedFiles.length > 0) {
531
+ fileCount.textContent = `${selectedFiles.length} files ready`;
532
+ dropZone.style.borderColor = 'var(--primary-color)';
533
+ } else {
534
+ fileCount.textContent = "0 files selected";
535
+ dropZone.style.borderColor = 'var(--border-color)';
536
+ }
537
+ checkReady();
538
+ }
539
+
540
+ fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
541
+
542
+ // Drag and Drop
543
+ dropZone.addEventListener('dragover', (e) => {
544
+ e.preventDefault();
545
+ dropZone.classList.add('dragover');
546
+ });
547
+
548
+ dropZone.addEventListener('dragleave', () => {
549
+ dropZone.classList.remove('dragover');
550
+ });
551
+
552
+ dropZone.addEventListener('drop', (e) => {
553
+ e.preventDefault();
554
+ dropZone.classList.remove('dragover');
555
+ handleFiles(e.dataTransfer.files);
556
+ });
557
+
558
+ // --- 3. Interaction Logic ---
559
+ function checkReady() {
560
+ const text = textInput.value.trim();
561
+ const hasFiles = selectedFiles.length > 0;
562
+ const isModelReady = classifier !== null;
563
+
564
+ classifyBtn.disabled = !(hasFiles && text && isModelReady);
565
+
566
+ if (isModelReady) {
567
+ if(!hasFiles) classifyBtn.title = "Please upload images";
568
+ else if(!text) classifyBtn.title = "Please enter a text prompt";
569
+ else classifyBtn.title = "Classify images";
570
+ } else {
571
+ classifyBtn.title = "Model is still loading...";
572
+ }
573
+ }
574
+
575
+ textInput.addEventListener('input', checkReady);
576
+
577
+ // --- 4. Classification Logic ---
578
+ classifyBtn.addEventListener('click', async () => {
579
+ const prompt = textInput.value.trim();
580
+ if (!classifier || !prompt || selectedFiles.length === 0) return;
581
+
582
+ // UI Loading State
583
+ const originalBtnText = classifyBtn.innerHTML;
584
+ classifyBtn.innerHTML = `<div class="spinner"></div> Processing...`;
585
+ classifyBtn.disabled = true;
586
+ resultsContainer.innerHTML = `<div style="text-align:center; padding: 2rem; color: var(--text-secondary);">Analyzing images...<br><small>This runs locally on your CPU.</small></div>`;
587
+
588
+ try {
589
+ // Prepare images for the pipeline
590
+ // The pipeline accepts URLs, Blobs, or Raw buffers.
591
+ // We have File objects (which inherit from Blob).
592
+
593
+ const startTime = performance.now();
594
+
595
+ // Run inference
596
+ // Note: We pass the array of images. Transformers.js handles batching.
597
+ const results = await classifier(selectedFiles, [prompt]);
598
+
599
+ const endTime = performance.now();
600
+ const duration = ((endTime - startTime) / 1000).toFixed(2);
601
+
602
+ renderResults(results);
603
+ showToast(`Classified ${selectedFiles.length} images in ${duration}s`, "success");
604
+
605
+ } catch (error) {
606
+ console.error(error);
607
+ showToast("An error occurred during classification.", "error");
608
+ resultsContainer.innerHTML = `<div class="empty-state"><p style="color: #ef4444">Error: ${error.message}</p></div>`;
609
+ } finally {
610
+ // Reset Button
611
+ classifyBtn.innerHTML = originalBtnText;
612
+ checkReady();
613
+ }
614
+ });
615
+
616
+ // --- 5. Rendering ---
617
+ function renderResults(data) {
618
+ // Data structure is an array of arrays if multiple images,
619
+ // but with single prompt, it might be flattened or structured depending on version.
620
+ // Usually for zero-shot-image-classification with multiple images and one label:
621
+ // It returns an array where each element corresponds to an image.
622
+
623
+ resultsContainer.innerHTML = '';
624
+ const grid = document.createElement('div');
625
+ grid.className = 'results-grid';
626
+
627
+ // Create cards
628
+ data.forEach((result, index) => {
629
+ // result is usually { label: "...", score: 0.99 } for the specific prompt label
630
+ // If we passed labels [prompt], it returns the score for that prompt.
631
+
632
+ const score = result.score; // 0 to 1
633
+ const percentage = (score * 100).toFixed(1);
634
+ const fileUrl = URL.createObjectURL(selectedFiles[index]);
635
+
636
+ const card = document.createElement('div');
637
+ card.className = 'image-card';
638
+
639
+ // Color coding based on score
640
+ let barColor = 'var(--primary-color)';
641
+ if(score > 0.8) barColor = 'var(--accent-color)';
642
+ else if(score < 0.3) barColor = '#ef4444'; // red
643
+
644
+ card.innerHTML = `
645
+ <div class="card-img-wrapper">
646
+ <img src="${fileUrl}" class="card-img" alt="Classified Image" loading="lazy">
647
+ </div>
648
+ <div class="card-content">
649
+ <div class="score-row">
650
+ <div class="score-label">
651
+ <span>Match Probability</span>
652
+ <span style="font-weight:bold; color:${barColor}">${percentage}%</span>
653
+ </div>
654
+ <div class="score-bar-bg">
655
+ <div class="score-bar-fill" style="width: 0%; background: ${barColor}"></div>
656
+ </div>
657
+ </div>
658
+ </div>
659
+ `;
660
+
661
+ grid.appendChild(card);
662
+
663
+ // Trigger animation after append
664
+ setTimeout(() => {
665
+ card.querySelector('.score-bar-fill').style.width = `${percentage}%`;
666
+ }, 50 + (index * 50));
667
+ });
668
+
669
+ resultsContainer.appendChild(grid);
670
+ resultCount.textContent = `${data.length} images processed`;
671
+ }
672
+
673
+ // --- Utilities ---
674
+ function showToast(message, type = 'info') {
675
+ const toast = document.createElement('div');
676
+ toast.className = 'toast';
677
+ toast.textContent = message;
678
+
679
+ if (type === 'error') toast.style.borderLeftColor = '#ef4444';
680
+ if (type === 'success') toast.style.borderLeftColor = 'var(--accent-color)';
681
+
682
+ toastContainer.appendChild(toast);
683
+
684
+ setTimeout(() => {
685
+ toast.style.opacity = '0';
686
+ toast.style.transform = 'translateX(100%)';
687
+ setTimeout(() => toast.remove(), 300);
688
+ }, 3000);
689
+ }
690
+
691
+ // Initialize
692
+ loadModel();
693
+
694
+ </script>
695
+ </body>
696
+ </html>