xxparthparekhxx commited on
Commit
99eb3dc
·
1 Parent(s): 794a185

added a ui

Browse files
Files changed (3) hide show
  1. .gitignore +1 -0
  2. app.py +8 -2
  3. index.html +868 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .venv
app.py CHANGED
@@ -4,7 +4,8 @@ import tempfile
4
  from io import BytesIO
5
  from PIL import Image
6
  from fastapi import FastAPI, File, UploadFile, HTTPException, Query
7
- from fastapi.responses import JSONResponse
 
8
  from pydantic import BaseModel
9
  from nudenet import NudeDetector
10
  import cv2
@@ -15,10 +16,15 @@ app = FastAPI(
15
  title="Nudenet API",
16
  description="API for detecting nudity in images and videos using Nudenet",
17
  version="1.0.0",
18
- docs_url="/",
19
  redoc_url="/redoc"
20
  )
21
 
 
 
 
 
 
22
  # Initialize NudeDetector with both models
23
  detector_320n = NudeDetector()
24
  detector_640m = NudeDetector(model_path="640m.onnx", inference_resolution=640)
 
4
  from io import BytesIO
5
  from PIL import Image
6
  from fastapi import FastAPI, File, UploadFile, HTTPException, Query
7
+ from fastapi.responses import JSONResponse, FileResponse
8
+ from fastapi.staticfiles import StaticFiles
9
  from pydantic import BaseModel
10
  from nudenet import NudeDetector
11
  import cv2
 
16
  title="Nudenet API",
17
  description="API for detecting nudity in images and videos using Nudenet",
18
  version="1.0.0",
19
+ docs_url="/docs",
20
  redoc_url="/redoc"
21
  )
22
 
23
+ # Serve static files and frontend
24
+ @app.get("/")
25
+ async def root():
26
+ return FileResponse("index.html", media_type="text/html")
27
+
28
  # Initialize NudeDetector with both models
29
  detector_320n = NudeDetector()
30
  detector_640m = NudeDetector(model_path="640m.onnx", inference_resolution=640)
index.html ADDED
@@ -0,0 +1,868 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>NudeNet - Nudity Detection API</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, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 12px;
27
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
28
+ max-width: 900px;
29
+ width: 100%;
30
+ padding: 40px;
31
+ }
32
+
33
+ .header {
34
+ text-align: center;
35
+ margin-bottom: 40px;
36
+ }
37
+
38
+ .header h1 {
39
+ color: #333;
40
+ font-size: 2.5em;
41
+ margin-bottom: 10px;
42
+ }
43
+
44
+ .header p {
45
+ color: #666;
46
+ font-size: 1.1em;
47
+ }
48
+
49
+ .documentation-link {
50
+ display: inline-block;
51
+ margin-top: 15px;
52
+ padding: 10px 20px;
53
+ background-color: #667eea;
54
+ color: white;
55
+ text-decoration: none;
56
+ border-radius: 6px;
57
+ font-size: 0.9em;
58
+ transition: background-color 0.3s;
59
+ }
60
+
61
+ .documentation-link:hover {
62
+ background-color: #764ba2;
63
+ }
64
+
65
+ .tabs {
66
+ display: flex;
67
+ gap: 10px;
68
+ margin-bottom: 30px;
69
+ border-bottom: 2px solid #e0e0e0;
70
+ }
71
+
72
+ .tab-button {
73
+ padding: 12px 24px;
74
+ background: none;
75
+ border: none;
76
+ cursor: pointer;
77
+ font-size: 1em;
78
+ color: #666;
79
+ border-bottom: 3px solid transparent;
80
+ transition: all 0.3s;
81
+ font-weight: 500;
82
+ }
83
+
84
+ .tab-button:hover {
85
+ color: #667eea;
86
+ }
87
+
88
+ .tab-button.active {
89
+ color: #667eea;
90
+ border-bottom-color: #667eea;
91
+ }
92
+
93
+ .tab-content {
94
+ display: none;
95
+ }
96
+
97
+ .tab-content.active {
98
+ display: block;
99
+ }
100
+
101
+ .upload-area {
102
+ border: 3px dashed #667eea;
103
+ border-radius: 8px;
104
+ padding: 40px;
105
+ text-align: center;
106
+ cursor: pointer;
107
+ transition: all 0.3s;
108
+ margin-bottom: 20px;
109
+ }
110
+
111
+ .upload-area:hover {
112
+ background-color: #f5f5f5;
113
+ border-color: #764ba2;
114
+ }
115
+
116
+ .upload-area.dragover {
117
+ background-color: #f0f0ff;
118
+ border-color: #764ba2;
119
+ }
120
+
121
+ .upload-icon {
122
+ font-size: 3em;
123
+ margin-bottom: 15px;
124
+ }
125
+
126
+ .upload-area h3 {
127
+ color: #333;
128
+ margin-bottom: 10px;
129
+ }
130
+
131
+ .upload-area p {
132
+ color: #666;
133
+ margin-bottom: 15px;
134
+ }
135
+
136
+ input[type="file"] {
137
+ display: none;
138
+ }
139
+
140
+ .model-selector {
141
+ margin-bottom: 20px;
142
+ }
143
+
144
+ .model-selector label {
145
+ display: inline-block;
146
+ margin-right: 20px;
147
+ color: #333;
148
+ font-weight: 500;
149
+ }
150
+
151
+ .radio-group {
152
+ display: flex;
153
+ gap: 20px;
154
+ }
155
+
156
+ .radio-group label {
157
+ display: flex;
158
+ align-items: center;
159
+ margin: 0;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .radio-group input[type="radio"] {
164
+ margin-right: 8px;
165
+ cursor: pointer;
166
+ }
167
+
168
+ .button-group {
169
+ display: flex;
170
+ gap: 10px;
171
+ margin-bottom: 20px;
172
+ }
173
+
174
+ button {
175
+ padding: 12px 24px;
176
+ border: none;
177
+ border-radius: 6px;
178
+ cursor: pointer;
179
+ font-size: 1em;
180
+ font-weight: 600;
181
+ transition: all 0.3s;
182
+ }
183
+
184
+ .btn-primary {
185
+ background-color: #667eea;
186
+ color: white;
187
+ flex: 1;
188
+ }
189
+
190
+ .btn-primary:hover:not(:disabled) {
191
+ background-color: #764ba2;
192
+ transform: translateY(-2px);
193
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
194
+ }
195
+
196
+ .btn-primary:disabled {
197
+ background-color: #ccc;
198
+ cursor: not-allowed;
199
+ }
200
+
201
+ .btn-secondary {
202
+ background-color: #e0e0e0;
203
+ color: #333;
204
+ }
205
+
206
+ .btn-secondary:hover {
207
+ background-color: #d0d0d0;
208
+ }
209
+
210
+ .preview-section {
211
+ margin-bottom: 30px;
212
+ }
213
+
214
+ .preview-section h3 {
215
+ color: #333;
216
+ margin-bottom: 15px;
217
+ }
218
+
219
+ .preview-image {
220
+ max-width: 100%;
221
+ max-height: 400px;
222
+ border-radius: 8px;
223
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
224
+ }
225
+
226
+ .results-section {
227
+ background-color: #f5f5f5;
228
+ padding: 20px;
229
+ border-radius: 8px;
230
+ margin-top: 20px;
231
+ }
232
+
233
+ .results-section h3 {
234
+ color: #333;
235
+ margin-bottom: 15px;
236
+ }
237
+
238
+ .result-item {
239
+ background: white;
240
+ padding: 15px;
241
+ border-radius: 6px;
242
+ margin-bottom: 10px;
243
+ border-left: 4px solid #667eea;
244
+ }
245
+
246
+ .result-label {
247
+ font-weight: 600;
248
+ color: #333;
249
+ }
250
+
251
+ .result-value {
252
+ color: #666;
253
+ margin-top: 5px;
254
+ }
255
+
256
+ .loading {
257
+ display: none;
258
+ text-align: center;
259
+ padding: 20px;
260
+ }
261
+
262
+ .loading.active {
263
+ display: block;
264
+ }
265
+
266
+ .spinner {
267
+ border: 4px solid #f3f3f3;
268
+ border-top: 4px solid #667eea;
269
+ border-radius: 50%;
270
+ width: 40px;
271
+ height: 40px;
272
+ animation: spin 1s linear infinite;
273
+ margin: 0 auto 10px;
274
+ }
275
+
276
+ @keyframes spin {
277
+ 0% { transform: rotate(0deg); }
278
+ 100% { transform: rotate(360deg); }
279
+ }
280
+
281
+ .error {
282
+ background-color: #fee;
283
+ border-left: 4px solid #f44;
284
+ color: #c33;
285
+ padding: 15px;
286
+ border-radius: 6px;
287
+ margin-bottom: 20px;
288
+ }
289
+
290
+ .success {
291
+ background-color: #efe;
292
+ border-left: 4px solid #4a4;
293
+ color: #3a3;
294
+ padding: 15px;
295
+ border-radius: 6px;
296
+ margin-bottom: 20px;
297
+ }
298
+
299
+ .detection-box {
300
+ background: white;
301
+ padding: 12px;
302
+ border-radius: 6px;
303
+ margin-bottom: 8px;
304
+ border-left: 4px solid #667eea;
305
+ }
306
+
307
+ .detection-box strong {
308
+ color: #667eea;
309
+ }
310
+
311
+ .base64-input {
312
+ width: 100%;
313
+ min-height: 150px;
314
+ padding: 15px;
315
+ border: 1px solid #ddd;
316
+ border-radius: 6px;
317
+ font-family: monospace;
318
+ font-size: 0.9em;
319
+ resize: vertical;
320
+ margin-bottom: 15px;
321
+ }
322
+
323
+ .filename {
324
+ color: #667eea;
325
+ font-weight: 600;
326
+ margin-bottom: 10px;
327
+ }
328
+
329
+ .video-info {
330
+ background-color: #f9f9f9;
331
+ padding: 15px;
332
+ border-radius: 6px;
333
+ margin-bottom: 15px;
334
+ border-left: 4px solid #667eea;
335
+ }
336
+
337
+ .video-info p {
338
+ color: #666;
339
+ margin-bottom: 5px;
340
+ }
341
+
342
+ .timestamp {
343
+ color: #667eea;
344
+ font-weight: 600;
345
+ }
346
+
347
+ @media (max-width: 768px) {
348
+ .container {
349
+ padding: 20px;
350
+ }
351
+
352
+ .header h1 {
353
+ font-size: 1.8em;
354
+ }
355
+
356
+ .tabs {
357
+ flex-wrap: wrap;
358
+ }
359
+
360
+ .tab-button {
361
+ padding: 10px 16px;
362
+ font-size: 0.9em;
363
+ }
364
+
365
+ .button-group {
366
+ flex-direction: column;
367
+ }
368
+
369
+ .radio-group {
370
+ flex-direction: column;
371
+ gap: 10px;
372
+ }
373
+
374
+ .upload-area {
375
+ padding: 20px;
376
+ }
377
+ }
378
+ </style>
379
+ </head>
380
+ <body>
381
+ <div class="container">
382
+ <div class="header">
383
+ <h1>🔍 NudeNet Detection API</h1>
384
+ <p>Test nudity detection in images and videos</p>
385
+ <a href="/docs" class="documentation-link">📖 View API Documentation</a>
386
+ </div>
387
+
388
+ <div class="tabs">
389
+ <button class="tab-button active" data-tab="image">Image Detection</button>
390
+ <button class="tab-button" data-tab="base64">Base64 Image</button>
391
+ <button class="tab-button" data-tab="video">Video Detection</button>
392
+ </div>
393
+
394
+ <!-- Image Detection Tab -->
395
+ <div id="image" class="tab-content active">
396
+ <div class="model-selector">
397
+ <label>Select Model:</label>
398
+ <div class="radio-group">
399
+ <label>
400
+ <input type="radio" name="imageModel" value="320n" checked>
401
+ 320n (Faster, Less Accurate)
402
+ </label>
403
+ <label>
404
+ <input type="radio" name="imageModel" value="640m">
405
+ 640m (Slower, More Accurate)
406
+ </label>
407
+ </div>
408
+ </div>
409
+
410
+ <div class="upload-area" id="imageUploadArea">
411
+ <div class="upload-icon">📸</div>
412
+ <h3>Drag and drop your image here</h3>
413
+ <p>or click to select an image</p>
414
+ <p style="font-size: 0.9em; color: #999;">Supported formats: JPG, PNG, GIF, BMP</p>
415
+ <input type="file" id="imageInput" accept="image/*">
416
+ </div>
417
+
418
+ <div id="imageFilename" class="filename" style="display: none;"></div>
419
+
420
+ <div class="preview-section" id="imagePreviewSection" style="display: none;">
421
+ <h3>Preview</h3>
422
+ <img id="imagePreview" class="preview-image" alt="Preview">
423
+ </div>
424
+
425
+ <div class="button-group">
426
+ <button class="btn-primary" id="detectImageBtn" style="display: none;">Detect Nudity</button>
427
+ <button class="btn-secondary" id="clearImageBtn" onclick="clearImage()">Clear</button>
428
+ </div>
429
+
430
+ <div class="loading" id="imageLoading">
431
+ <div class="spinner"></div>
432
+ <p>Analyzing image...</p>
433
+ </div>
434
+
435
+ <div id="imageError" class="error" style="display: none;"></div>
436
+ <div id="imageResults" class="results-section" style="display: none;">
437
+ <h3>Detection Results</h3>
438
+ <div id="imageResultsContent"></div>
439
+ </div>
440
+ </div>
441
+
442
+ <!-- Base64 Image Tab -->
443
+ <div id="base64" class="tab-content">
444
+ <div class="model-selector">
445
+ <label>Select Model:</label>
446
+ <div class="radio-group">
447
+ <label>
448
+ <input type="radio" name="base64Model" value="320n" checked>
449
+ 320n (Faster, Less Accurate)
450
+ </label>
451
+ <label>
452
+ <input type="radio" name="base64Model" value="640m">
453
+ 640m (Slower, More Accurate)
454
+ </label>
455
+ </div>
456
+ </div>
457
+
458
+ <h3>Base64 Encoded Image</h3>
459
+ <textarea id="base64Input" class="base64-input" placeholder="Paste your base64 encoded image here..."></textarea>
460
+
461
+ <div class="button-group">
462
+ <button class="btn-primary" id="detectBase64Btn">Detect Nudity</button>
463
+ <button class="btn-secondary" onclick="clearBase64()">Clear</button>
464
+ </div>
465
+
466
+ <div class="loading" id="base64Loading">
467
+ <div class="spinner"></div>
468
+ <p>Analyzing image...</p>
469
+ </div>
470
+
471
+ <div id="base64Error" class="error" style="display: none;"></div>
472
+ <div id="base64Results" class="results-section" style="display: none;">
473
+ <h3>Detection Results</h3>
474
+ <div id="base64ResultsContent"></div>
475
+ </div>
476
+ </div>
477
+
478
+ <!-- Video Detection Tab -->
479
+ <div id="video" class="tab-content">
480
+ <div class="model-selector">
481
+ <label>Select Model:</label>
482
+ <div class="radio-group">
483
+ <label>
484
+ <input type="radio" name="videoModel" value="320n" checked>
485
+ 320n (Faster, Less Accurate)
486
+ </label>
487
+ <label>
488
+ <input type="radio" name="videoModel" value="640m">
489
+ 640m (Slower, More Accurate)
490
+ </label>
491
+ </div>
492
+ </div>
493
+
494
+ <div class="model-selector">
495
+ <label for="frameInterval">Frame Interval (seconds):</label>
496
+ <input type="number" id="frameInterval" value="1" min="0" max="30" step="0.1" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 100px;">
497
+ <p style="font-size: 0.9em; color: #666; margin-top: 5px;">0 = every frame, 1 = every second, etc.</p>
498
+ </div>
499
+
500
+ <div class="upload-area" id="videoUploadArea">
501
+ <div class="upload-icon">🎥</div>
502
+ <h3>Drag and drop your video here</h3>
503
+ <p>or click to select a video</p>
504
+ <p style="font-size: 0.9em; color: #999;">Supported formats: MP4, AVI, MOV, MKV</p>
505
+ <input type="file" id="videoInput" accept="video/*">
506
+ </div>
507
+
508
+ <div id="videoFilename" class="filename" style="display: none;"></div>
509
+
510
+ <div class="button-group">
511
+ <button class="btn-primary" id="detectVideoBtn" style="display: none;">Detect Nudity in Video</button>
512
+ <button class="btn-secondary" id="clearVideoBtn" onclick="clearVideo()">Clear</button>
513
+ </div>
514
+
515
+ <div class="loading" id="videoLoading">
516
+ <div class="spinner"></div>
517
+ <p>Analyzing video... This may take a while</p>
518
+ </div>
519
+
520
+ <div id="videoError" class="error" style="display: none;"></div>
521
+ <div id="videoResults" class="results-section" style="display: none;">
522
+ <h3>Detection Results</h3>
523
+ <div id="videoResultsContent"></div>
524
+ </div>
525
+ </div>
526
+ </div>
527
+
528
+ <script>
529
+ // Tab switching
530
+ document.querySelectorAll('.tab-button').forEach(button => {
531
+ button.addEventListener('click', () => {
532
+ const tabName = button.dataset.tab;
533
+
534
+ // Hide all tabs
535
+ document.querySelectorAll('.tab-content').forEach(tab => {
536
+ tab.classList.remove('active');
537
+ });
538
+
539
+ // Remove active class from all buttons
540
+ document.querySelectorAll('.tab-button').forEach(btn => {
541
+ btn.classList.remove('active');
542
+ });
543
+
544
+ // Show selected tab and mark button as active
545
+ document.getElementById(tabName).classList.add('active');
546
+ button.classList.add('active');
547
+ });
548
+ });
549
+
550
+ // Image Upload
551
+ const imageUploadArea = document.getElementById('imageUploadArea');
552
+ const imageInput = document.getElementById('imageInput');
553
+ let selectedImageFile = null;
554
+
555
+ imageUploadArea.addEventListener('click', () => imageInput.click());
556
+ imageUploadArea.addEventListener('dragover', (e) => {
557
+ e.preventDefault();
558
+ imageUploadArea.classList.add('dragover');
559
+ });
560
+ imageUploadArea.addEventListener('dragleave', () => {
561
+ imageUploadArea.classList.remove('dragover');
562
+ });
563
+ imageUploadArea.addEventListener('drop', (e) => {
564
+ e.preventDefault();
565
+ imageUploadArea.classList.remove('dragover');
566
+ handleImageUpload(e.dataTransfer.files[0]);
567
+ });
568
+
569
+ imageInput.addEventListener('change', (e) => {
570
+ if (e.target.files[0]) {
571
+ handleImageUpload(e.target.files[0]);
572
+ }
573
+ });
574
+
575
+ function handleImageUpload(file) {
576
+ if (!file.type.startsWith('image/')) {
577
+ showImageError('Please upload a valid image file');
578
+ return;
579
+ }
580
+
581
+ selectedImageFile = file;
582
+ document.getElementById('imageFilename').textContent = `File: ${file.name}`;
583
+ document.getElementById('imageFilename').style.display = 'block';
584
+ document.getElementById('imagePreviewSection').style.display = 'block';
585
+ document.getElementById('detectImageBtn').style.display = 'block';
586
+
587
+ const reader = new FileReader();
588
+ reader.onload = (e) => {
589
+ document.getElementById('imagePreview').src = e.target.result;
590
+ };
591
+ reader.readAsDataURL(file);
592
+ }
593
+
594
+ document.getElementById('detectImageBtn').addEventListener('click', async () => {
595
+ if (!selectedImageFile) return;
596
+
597
+ const model = document.querySelector('input[name="imageModel"]:checked').value;
598
+ const use640m = model === '640m';
599
+
600
+ const formData = new FormData();
601
+ formData.append('image', selectedImageFile);
602
+
603
+ try {
604
+ showImageLoading(true);
605
+ hideImageError();
606
+ document.getElementById('imageResults').style.display = 'none';
607
+
608
+ const response = await fetch(`/detect?use_640m=${use640m}`, {
609
+ method: 'POST',
610
+ body: formData
611
+ });
612
+
613
+ if (!response.ok) {
614
+ throw new Error(`HTTP error! status: ${response.status}`);
615
+ }
616
+
617
+ const data = await response.json();
618
+ displayImageResults(data.result);
619
+ } catch (error) {
620
+ showImageError(`Error: ${error.message}`);
621
+ } finally {
622
+ showImageLoading(false);
623
+ }
624
+ });
625
+
626
+ function clearImage() {
627
+ selectedImageFile = null;
628
+ imageInput.value = '';
629
+ document.getElementById('imageFilename').style.display = 'none';
630
+ document.getElementById('imagePreviewSection').style.display = 'none';
631
+ document.getElementById('detectImageBtn').style.display = 'none';
632
+ document.getElementById('imageResults').style.display = 'none';
633
+ hideImageError();
634
+ }
635
+
636
+ function displayImageResults(results) {
637
+ const resultsContent = document.getElementById('imageResultsContent');
638
+ if (!results || results.length === 0) {
639
+ resultsContent.innerHTML = '<div class="success">✓ No nudity detected!</div>';
640
+ } else {
641
+ let html = '';
642
+ results.forEach((detection, index) => {
643
+ html += `
644
+ <div class="detection-box">
645
+ <strong>Detection ${index + 1}</strong><br>
646
+ Class: ${detection.class}<br>
647
+ Confidence: ${(detection.confidence * 100).toFixed(2)}%
648
+ </div>
649
+ `;
650
+ });
651
+ resultsContent.innerHTML = html;
652
+ }
653
+ document.getElementById('imageResults').style.display = 'block';
654
+ }
655
+
656
+ function showImageError(message) {
657
+ const errorDiv = document.getElementById('imageError');
658
+ errorDiv.textContent = message;
659
+ errorDiv.style.display = 'block';
660
+ }
661
+
662
+ function hideImageError() {
663
+ document.getElementById('imageError').style.display = 'none';
664
+ }
665
+
666
+ function showImageLoading(show) {
667
+ document.getElementById('imageLoading').classList.toggle('active', show);
668
+ }
669
+
670
+ // Base64 Upload
671
+ document.getElementById('detectBase64Btn').addEventListener('click', async () => {
672
+ const base64Input = document.getElementById('base64Input').value.trim();
673
+ if (!base64Input) {
674
+ showBase64Error('Please paste a base64 encoded image');
675
+ return;
676
+ }
677
+
678
+ const model = document.querySelector('input[name="base64Model"]:checked').value;
679
+ const use640m = model === '640m';
680
+
681
+ try {
682
+ showBase64Loading(true);
683
+ hideBase64Error();
684
+ document.getElementById('base64Results').style.display = 'none';
685
+
686
+ const response = await fetch(`/detect_base64?use_640m=${use640m}`, {
687
+ method: 'POST',
688
+ headers: {
689
+ 'Content-Type': 'application/json'
690
+ },
691
+ body: JSON.stringify({ image: base64Input })
692
+ });
693
+
694
+ if (!response.ok) {
695
+ throw new Error(`HTTP error! status: ${response.status}`);
696
+ }
697
+
698
+ const data = await response.json();
699
+ displayBase64Results(data.result);
700
+ } catch (error) {
701
+ showBase64Error(`Error: ${error.message}`);
702
+ } finally {
703
+ showBase64Loading(false);
704
+ }
705
+ });
706
+
707
+ function clearBase64() {
708
+ document.getElementById('base64Input').value = '';
709
+ document.getElementById('base64Results').style.display = 'none';
710
+ hideBase64Error();
711
+ }
712
+
713
+ function displayBase64Results(results) {
714
+ const resultsContent = document.getElementById('base64ResultsContent');
715
+ if (!results || results.length === 0) {
716
+ resultsContent.innerHTML = '<div class="success">✓ No nudity detected!</div>';
717
+ } else {
718
+ let html = '';
719
+ results.forEach((detection, index) => {
720
+ html += `
721
+ <div class="detection-box">
722
+ <strong>Detection ${index + 1}</strong><br>
723
+ Class: ${detection.class}<br>
724
+ Confidence: ${(detection.confidence * 100).toFixed(2)}%
725
+ </div>
726
+ `;
727
+ });
728
+ resultsContent.innerHTML = html;
729
+ }
730
+ document.getElementById('base64Results').style.display = 'block';
731
+ }
732
+
733
+ function showBase64Error(message) {
734
+ const errorDiv = document.getElementById('base64Error');
735
+ errorDiv.textContent = message;
736
+ errorDiv.style.display = 'block';
737
+ }
738
+
739
+ function hideBase64Error() {
740
+ document.getElementById('base64Error').style.display = 'none';
741
+ }
742
+
743
+ function showBase64Loading(show) {
744
+ document.getElementById('base64Loading').classList.toggle('active', show);
745
+ }
746
+
747
+ // Video Upload
748
+ const videoUploadArea = document.getElementById('videoUploadArea');
749
+ const videoInput = document.getElementById('videoInput');
750
+ let selectedVideoFile = null;
751
+
752
+ videoUploadArea.addEventListener('click', () => videoInput.click());
753
+ videoUploadArea.addEventListener('dragover', (e) => {
754
+ e.preventDefault();
755
+ videoUploadArea.classList.add('dragover');
756
+ });
757
+ videoUploadArea.addEventListener('dragleave', () => {
758
+ videoUploadArea.classList.remove('dragover');
759
+ });
760
+ videoUploadArea.addEventListener('drop', (e) => {
761
+ e.preventDefault();
762
+ videoUploadArea.classList.remove('dragover');
763
+ handleVideoUpload(e.dataTransfer.files[0]);
764
+ });
765
+
766
+ videoInput.addEventListener('change', (e) => {
767
+ if (e.target.files[0]) {
768
+ handleVideoUpload(e.target.files[0]);
769
+ }
770
+ });
771
+
772
+ function handleVideoUpload(file) {
773
+ if (!file.type.startsWith('video/')) {
774
+ showVideoError('Please upload a valid video file');
775
+ return;
776
+ }
777
+
778
+ selectedVideoFile = file;
779
+ document.getElementById('videoFilename').textContent = `File: ${file.name}`;
780
+ document.getElementById('videoFilename').style.display = 'block';
781
+ document.getElementById('detectVideoBtn').style.display = 'block';
782
+ }
783
+
784
+ document.getElementById('detectVideoBtn').addEventListener('click', async () => {
785
+ if (!selectedVideoFile) return;
786
+
787
+ const model = document.querySelector('input[name="videoModel"]:checked').value;
788
+ const use640m = model === '640m';
789
+ const frameInterval = document.getElementById('frameInterval').value;
790
+
791
+ const formData = new FormData();
792
+ formData.append('video', selectedVideoFile);
793
+
794
+ try {
795
+ showVideoLoading(true);
796
+ hideVideoError();
797
+ document.getElementById('videoResults').style.display = 'none';
798
+
799
+ const response = await fetch(`/detect_video?frame_interval=${frameInterval}&use_640m=${use640m}`, {
800
+ method: 'POST',
801
+ body: formData
802
+ });
803
+
804
+ if (!response.ok) {
805
+ throw new Error(`HTTP error! status: ${response.status}`);
806
+ }
807
+
808
+ const data = await response.json();
809
+ displayVideoResults(data.results);
810
+ } catch (error) {
811
+ showVideoError(`Error: ${error.message}`);
812
+ } finally {
813
+ showVideoLoading(false);
814
+ }
815
+ });
816
+
817
+ function clearVideo() {
818
+ selectedVideoFile = null;
819
+ videoInput.value = '';
820
+ document.getElementById('videoFilename').style.display = 'none';
821
+ document.getElementById('detectVideoBtn').style.display = 'none';
822
+ document.getElementById('videoResults').style.display = 'none';
823
+ hideVideoError();
824
+ }
825
+
826
+ function displayVideoResults(results) {
827
+ const resultsContent = document.getElementById('videoResultsContent');
828
+ if (!results || results.length === 0) {
829
+ resultsContent.innerHTML = '<div class="success">✓ No nudity detected in the video!</div>';
830
+ } else {
831
+ let html = '';
832
+ results.forEach((result) => {
833
+ html += `
834
+ <div class="video-info">
835
+ <p><span class="timestamp">Timestamp: ${result.timestamp}s</span></p>
836
+ `;
837
+ result.detections.forEach((detection, index) => {
838
+ html += `
839
+ <div class="detection-box">
840
+ <strong>Detection ${index + 1}</strong><br>
841
+ Class: ${detection.class}<br>
842
+ Confidence: ${(detection.confidence * 100).toFixed(2)}%
843
+ </div>
844
+ `;
845
+ });
846
+ html += '</div>';
847
+ });
848
+ resultsContent.innerHTML = html;
849
+ }
850
+ document.getElementById('videoResults').style.display = 'block';
851
+ }
852
+
853
+ function showVideoError(message) {
854
+ const errorDiv = document.getElementById('videoError');
855
+ errorDiv.textContent = message;
856
+ errorDiv.style.display = 'block';
857
+ }
858
+
859
+ function hideVideoError() {
860
+ document.getElementById('videoError').style.display = 'none';
861
+ }
862
+
863
+ function showVideoLoading(show) {
864
+ document.getElementById('videoLoading').classList.toggle('active', show);
865
+ }
866
+ </script>
867
+ </body>
868
+ </html>