ramsi-k commited on
Commit
ebd3d7d
·
verified ·
1 Parent(s): c071c28

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +476 -19
index.html CHANGED
@@ -1,19 +1,476 @@
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, maximum-scale=1.0, user-scalable=no">
6
+ <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
7
+ <title>Korean Object Scanner</title>
8
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]/dist/coco-ssd.min.js"></script>
12
+ <style>
13
+ body {
14
+ touch-action: none;
15
+ overscroll-behavior: none;
16
+ -webkit-overflow-scrolling: touch;
17
+ }
18
+ #videoContainer {
19
+ position: relative;
20
+ width: 100%;
21
+ height: 100vh;
22
+ overflow: hidden;
23
+ background-color: #000;
24
+ }
25
+ #videoElement, #processedElement {
26
+ position: absolute;
27
+ top: 0;
28
+ left: 0;
29
+ width: 100%;
30
+ height: 100%;
31
+ object-fit: cover;
32
+ }
33
+ #processedElement {
34
+ display: block !important;
35
+ pointer-events: none;
36
+ }
37
+
38
+ .detection-box {
39
+ position: absolute;
40
+ border: 3px solid #4ADE80;
41
+ border-radius: 4px;
42
+ z-index: 10;
43
+ }
44
+
45
+ .detection-label {
46
+ position: absolute;
47
+ background-color: #4ADE80;
48
+ color: #1F2937;
49
+ padding: 2px 8px;
50
+ border-radius: 4px;
51
+ font-size: 14px;
52
+ font-weight: 500;
53
+ transform: translateY(-100%);
54
+ z-index: 11;
55
+ }
56
+
57
+ .pulse {
58
+ animation: pulse 2s infinite;
59
+ }
60
+
61
+ @keyframes pulse {
62
+ 0% { opacity: 1; }
63
+ 50% { opacity: 0.5; }
64
+ 100% { opacity: 1; }
65
+ }
66
+
67
+ .loading-spinner {
68
+ width: 40px;
69
+ height: 40px;
70
+ border: 4px solid rgba(255,255,255,0.3);
71
+ border-radius: 50%;
72
+ border-top-color: #fff;
73
+ animation: spin 1s ease-in-out infinite;
74
+ }
75
+
76
+ @keyframes spin {
77
+ to { transform: rotate(360deg); }
78
+ }
79
+
80
+ .progress-bar {
81
+ width: 200px;
82
+ height: 8px;
83
+ background-color: rgba(255,255,255,0.2);
84
+ border-radius: 4px;
85
+ margin-top: 16px;
86
+ overflow: hidden;
87
+ }
88
+
89
+ .progress-fill {
90
+ height: 100%;
91
+ background-color: #4ADE80;
92
+ width: 0%;
93
+ transition: width 0.3s ease;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body class="bg-gray-900 text-white">
98
+ <!-- Loading overlay -->
99
+ <div id="loadingOverlay" class="fixed inset-0 bg-black bg-opacity-90 flex flex-col items-center justify-center z-50">
100
+ <div class="loading-spinner mb-4"></div>
101
+ <p id="loadingText" class="text-lg font-medium">Loading AI model...</p>
102
+ <div class="progress-bar">
103
+ <div id="modelProgress" class="progress-fill"></div>
104
+ </div>
105
+ <p id="loadingSubtext" class="text-sm text-gray-400 mt-2">Initializing object detection</p>
106
+ </div>
107
+
108
+ <!-- Error overlay -->
109
+ <div id="errorOverlay" class="fixed inset-0 bg-black bg-opacity-90 flex flex-col items-center justify-center z-50 hidden">
110
+ <div class="bg-red-500 rounded-full p-4 mb-4">
111
+ <span class="material-icons text-white text-4xl">error</span>
112
+ </div>
113
+ <h2 class="text-xl font-bold mb-2">Model Failed to Load</h2>
114
+ <p id="errorMessage" class="text-center max-w-xs mb-6">There was an issue loading the AI model.</p>
115
+ <button id="retryButton" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-full flex items-center">
116
+ <span class="material-icons mr-2">refresh</span>
117
+ Try Again
118
+ </button>
119
+ </div>
120
+
121
+ <!-- Camera permission overlay -->
122
+ <div id="permissionOverlay" class="fixed inset-0 bg-black bg-opacity-90 flex flex-col items-center justify-center z-50 hidden">
123
+ <div class="bg-blue-500 rounded-full p-4 mb-4">
124
+ <span class="material-icons text-white text-4xl">camera_alt</span>
125
+ </div>
126
+ <h2 class="text-xl font-bold mb-2">Camera Access Needed</h2>
127
+ <p class="text-center max-w-xs mb-6">Please allow camera access to use the object scanner.</p>
128
+ <button id="startCameraButton" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-full flex items-center">
129
+ <span class="material-icons mr-2">camera</span>
130
+ Start Camera
131
+ </button>
132
+ </div>
133
+
134
+ <!-- Main UI -->
135
+ <div id="videoContainer">
136
+ <video id="videoElement" autoplay playsinline muted></video>
137
+ <canvas id="processedElement"></canvas>
138
+
139
+ <!-- Detection results container -->
140
+ <div id="detectionsContainer"></div>
141
+ </div>
142
+
143
+ <!-- Bottom controls -->
144
+ <div class="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent pb-4 pt-8 px-4 flex justify-center z-40">
145
+ <div class="flex space-x-4">
146
+ <button id="toggleDetectionButton" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-full flex items-center hidden">
147
+ <span class="material-icons mr-2">visibility</span>
148
+ Toggle Detection
149
+ </button>
150
+
151
+ <button id="switchCameraButton" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-3 rounded-full flex items-center hidden">
152
+ <span class="material-icons">flip_camera_ios</span>
153
+ </button>
154
+ </div>
155
+ </div>
156
+
157
+ <!-- Status indicator -->
158
+ <div id="statusIndicator" class="fixed top-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full text-sm flex items-center hidden">
159
+ <span class="material-icons mr-1 text-green-400">circle</span>
160
+ <span id="statusText">Ready</span>
161
+ </div>
162
+
163
+ <script>
164
+ document.addEventListener('DOMContentLoaded', async function() {
165
+ // DOM elements
166
+ const videoElement = document.getElementById('videoElement');
167
+ const processedElement = document.getElementById('processedElement');
168
+ const detectionsContainer = document.getElementById('detectionsContainer');
169
+ const loadingOverlay = document.getElementById('loadingOverlay');
170
+ const loadingText = document.getElementById('loadingText');
171
+ const loadingSubtext = document.getElementById('loadingSubtext');
172
+ const modelProgress = document.getElementById('modelProgress');
173
+ const errorOverlay = document.getElementById('errorOverlay');
174
+ const errorMessage = document.getElementById('errorMessage');
175
+ const retryButton = document.getElementById('retryButton');
176
+ const permissionOverlay = document.getElementById('permissionOverlay');
177
+ const startCameraButton = document.getElementById('startCameraButton');
178
+ const statusIndicator = document.getElementById('statusIndicator');
179
+ const statusText = document.getElementById('statusText');
180
+ const toggleDetectionButton = document.getElementById('toggleDetectionButton');
181
+ const switchCameraButton = document.getElementById('switchCameraButton');
182
+
183
+ // App state
184
+ let objectDetector = null;
185
+ let isDetecting = true;
186
+ let lastDetectionTime = 0;
187
+ const detectionInterval = 300; // ms between detections
188
+ let currentStream = null;
189
+ let facingMode = "environment";
190
+
191
+ // Korean labels dictionary (expanded)
192
+ const koreanLabels = {
193
+ "person": "사람",
194
+ "bicycle": "자전거",
195
+ "car": "자동차",
196
+ "motorcycle": "오토바이",
197
+ "airplane": "비행기",
198
+ "bus": "버스",
199
+ "train": "기차",
200
+ "truck": "트럭",
201
+ "boat": "보트",
202
+ "traffic light": "신호등",
203
+ "fire hydrant": "소화전",
204
+ "stop sign": "정지 표지판",
205
+ "parking meter": "주차 요금기",
206
+ "bench": "벤치",
207
+ "bird": "새",
208
+ "cat": "고양이",
209
+ "dog": "강아지",
210
+ "horse": "말",
211
+ "sheep": "양",
212
+ "cow": "소",
213
+ "elephant": "코끼리",
214
+ "bear": "곰",
215
+ "zebra": "얼룩말",
216
+ "giraffe": "기린",
217
+ "backpack": "배낭",
218
+ "umbrella": "우산",
219
+ "handbag": "핸드백",
220
+ "tie": "묶다",
221
+ "suitcase": "여행 가방",
222
+ "frisbee": "프리스비",
223
+ "skis": "스키",
224
+ "snowboard": "스노우보드",
225
+ "sports ball": "스포츠 공",
226
+ "kite": "연",
227
+ "baseball bat": "야구방망이",
228
+ "baseball glove": "야구 글러브",
229
+ "skateboard": "스케이트보드",
230
+ "surfboard": "서핑보드",
231
+ "tennis racket": "테니스 라켓",
232
+ "bottle": "병",
233
+ "wine glass": "와인잔",
234
+ "cup": "컵",
235
+ "fork": "포크",
236
+ "knife": "칼",
237
+ "spoon": "숟가락",
238
+ "bowl": "그릇",
239
+ "banana": "바나나",
240
+ "apple": "사과",
241
+ "sandwich": "샌드위치",
242
+ "orange": "오렌지",
243
+ "broccoli": "브로콜리",
244
+ "carrot": "당근",
245
+ "hot dog": "핫도그",
246
+ "pizza": "피자",
247
+ "donut": "도넛",
248
+ "cake": "케이크",
249
+ "chair": "의자",
250
+ "couch": "소파",
251
+ "potted plant": "화분",
252
+ "bed": "침대",
253
+ "dining table": "식탁",
254
+ "toilet": "화장실",
255
+ "tv": "텔레비전",
256
+ "laptop": "노트북",
257
+ "mouse": "마우스",
258
+ "remote": "리모컨",
259
+ "keyboard": "키보드",
260
+ "cell phone": "휴대폰",
261
+ "microwave": "전자레인지",
262
+ "oven": "오븐",
263
+ "toaster": "토스터",
264
+ "sink": "싱크대",
265
+ "refrigerator": "냉장고",
266
+ "book": "책",
267
+ "clock": "시계",
268
+ "vase": "꽃병",
269
+ "scissors": "가위",
270
+ "teddy bear": "장난감 곰",
271
+ "hair drier": "헤어드라이어",
272
+ "toothbrush": "칫솔"
273
+ };
274
+
275
+ // Initialize COCO-SSD model
276
+ async function initializeModel() {
277
+ loadingText.textContent = 'Loading COCO-SSD model...';
278
+ loadingSubtext.textContent = 'This lightweight model works offline';
279
+ modelProgress.style.width = '30%';
280
+
281
+ try {
282
+ // Load TensorFlow.js backend
283
+ await tf.setBackend('webgl');
284
+ modelProgress.style.width = '60%';
285
+
286
+ // Load COCO-SSD model
287
+ objectDetector = await cocoSsd.load({
288
+ base: 'lite_mobilenet_v2'
289
+ });
290
+
291
+ modelProgress.style.width = '100%';
292
+
293
+ // Add slight delay to show 100% progress before hiding
294
+ await new Promise(resolve => setTimeout(resolve, 300));
295
+
296
+ loadingOverlay.classList.add('hidden');
297
+ return true;
298
+ } catch (error) {
299
+ console.error("Failed to load COCO-SSD:", error);
300
+ showError("Failed to load object detection model", error);
301
+ return false;
302
+ }
303
+ }
304
+
305
+ // Initialize camera with user permission
306
+ async function initCamera() {
307
+ try {
308
+ if (currentStream) {
309
+ currentStream.getTracks().forEach(track => track.stop());
310
+ }
311
+
312
+ const stream = await navigator.mediaDevices.getUserMedia({
313
+ video: {
314
+ facingMode: facingMode,
315
+ width: { ideal: 640 },
316
+ height: { ideal: 480 }
317
+ },
318
+ audio: false
319
+ });
320
+
321
+ currentStream = stream;
322
+ videoElement.srcObject = stream;
323
+
324
+ return new Promise((resolve) => {
325
+ videoElement.onloadedmetadata = () => {
326
+ processedElement.width = videoElement.videoWidth;
327
+ processedElement.height = videoElement.videoHeight;
328
+ permissionOverlay.classList.add('hidden');
329
+ toggleDetectionButton.classList.remove('hidden');
330
+ switchCameraButton.classList.remove('hidden');
331
+ statusIndicator.classList.remove('hidden');
332
+ resolve(true);
333
+ };
334
+ });
335
+ } catch (err) {
336
+ console.error("Camera error:", err);
337
+ permissionOverlay.classList.remove('hidden');
338
+ return false;
339
+ }
340
+ }
341
+
342
+ // Process each video frame
343
+ async function processFrame(timestamp) {
344
+ if (!objectDetector || !isDetecting) {
345
+ requestAnimationFrame(processFrame);
346
+ return;
347
+ }
348
+
349
+ // Throttle processing
350
+ if (timestamp - lastDetectionTime < detectionInterval) {
351
+ requestAnimationFrame(processFrame);
352
+ return;
353
+ }
354
+
355
+ lastDetectionTime = timestamp;
356
+
357
+ if (videoElement.readyState < videoElement.HAVE_ENOUGH_DATA) {
358
+ requestAnimationFrame(processFrame);
359
+ return;
360
+ }
361
+
362
+ try {
363
+ statusText.textContent = "Detecting objects...";
364
+
365
+ const detections = await objectDetector.detect(videoElement);
366
+ drawDetections(detections);
367
+
368
+ statusText.textContent = `Detected ${detections.length} objects`;
369
+ } catch (error) {
370
+ console.error("Detection error:", error);
371
+ statusText.textContent = "Detection error";
372
+ }
373
+
374
+ requestAnimationFrame(processFrame);
375
+ }
376
+
377
+ // Draw bounding boxes and labels
378
+ function drawDetections(detections) {
379
+ // Clear previous detections
380
+ detectionsContainer.innerHTML = '';
381
+
382
+ if (!detections || detections.length === 0) {
383
+ return;
384
+ }
385
+
386
+ for (const detection of detections) {
387
+ if (!detection.bbox || !detection.class) continue;
388
+
389
+ const [x, y, width, height] = detection.bbox;
390
+ const score = detection.score;
391
+ const label = detection.class;
392
+ const koreanLabel = koreanLabels[label.toLowerCase()] || label;
393
+
394
+ // Only show if confidence is high enough
395
+ if (score < 0.5) continue;
396
+
397
+ // Create detection box element
398
+ const boxElement = document.createElement('div');
399
+ boxElement.className = 'detection-box';
400
+ boxElement.style.left = `${x}px`;
401
+ boxElement.style.top = `${y}px`;
402
+ boxElement.style.width = `${width}px`;
403
+ boxElement.style.height = `${height}px`;
404
+
405
+ // Create label element
406
+ const labelElement = document.createElement('div');
407
+ labelElement.className = 'detection-label';
408
+ labelElement.textContent = `${koreanLabel} (${Math.round(score * 100)}%)`;
409
+ labelElement.style.left = `${x}px`;
410
+ labelElement.style.top = `${y}px`;
411
+
412
+ // Add to container
413
+ detectionsContainer.appendChild(boxElement);
414
+ detectionsContainer.appendChild(labelElement);
415
+ }
416
+ }
417
+
418
+ // Toggle detection on/off
419
+ function toggleDetection() {
420
+ isDetecting = !isDetecting;
421
+ if (isDetecting) {
422
+ toggleDetectionButton.innerHTML = '<span class="material-icons mr-2">visibility_off</span> Pause Detection';
423
+ statusText.textContent = "Detection resumed";
424
+ } else {
425
+ toggleDetectionButton.innerHTML = '<span class="material-icons mr-2">visibility</span> Resume Detection';
426
+ statusText.textContent = "Detection paused";
427
+ detectionsContainer.innerHTML = '';
428
+ }
429
+ }
430
+
431
+ // Switch between front and back camera
432
+ async function switchCamera() {
433
+ facingMode = facingMode === "user" ? "environment" : "user";
434
+ statusText.textContent = "Switching camera...";
435
+ await initCamera();
436
+ statusText.textContent = "Camera switched";
437
+ }
438
+
439
+ // Show error message
440
+ function showError(message, error) {
441
+ loadingOverlay.classList.add('hidden');
442
+ errorOverlay.classList.remove('hidden');
443
+ errorMessage.textContent = message;
444
+ if (error) {
445
+ console.error(error);
446
+ }
447
+ }
448
+
449
+ // Start everything
450
+ async function startApp() {
451
+ loadingOverlay.classList.remove('hidden');
452
+ errorOverlay.classList.add('hidden');
453
+
454
+ const modelLoaded = await initializeModel();
455
+ if (!modelLoaded) return;
456
+
457
+ // Check camera permissions
458
+ const cameraPermission = await initCamera();
459
+ if (!cameraPermission) return;
460
+
461
+ // Start processing frames
462
+ requestAnimationFrame(processFrame);
463
+ }
464
+
465
+ // Event listeners
466
+ retryButton.addEventListener('click', startApp);
467
+ startCameraButton.addEventListener('click', initCamera);
468
+ toggleDetectionButton.addEventListener('click', toggleDetection);
469
+ switchCameraButton.addEventListener('click', switchCamera);
470
+
471
+ // Start the app
472
+ startApp();
473
+ });
474
+ </script>
475
+ </body>
476
+ </html>