kamcio1989 commited on
Commit
9ed72dc
·
verified ·
1 Parent(s): bd606f5

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +698 -0
app.py ADDED
@@ -0,0 +1,698 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ I'll create a comprehensive face recognition platform using transformers.js that can detect faces, extract facial
2
+ features, and identify people from a database of known faces.
3
+
4
+ === index.html ===
5
+ <!DOCTYPE html>
6
+ <html lang="en">
7
+
8
+ <head>
9
+ <meta charset="UTF-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <title>Face Recognition Platform</title>
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+ <link rel="stylesheet" href="styles.css">
14
+ </head>
15
+
16
+ <body class="bg-gradient-to-br from-gray-900 to-gray-800 text-white min-h-screen">
17
+ <!-- Header -->
18
+ <header class="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700">
19
+ <div class="container mx-auto px-4 py-4">
20
+ <div class="flex items-center justify-between">
21
+ <div class="flex items-center space-x-3">
22
+ <svg class="w-8 h-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
23
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
24
+ d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z">
25
+ </path>
26
+ </svg>
27
+ <h1 class="text-2xl font-bold">Face Recognition Platform</h1>
28
+ </div>
29
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
30
+ class="text-blue-400 hover:text-blue-300 transition-colors">
31
+ Built with anycoder
32
+ </a>
33
+ </div>
34
+ </div>
35
+ </header>
36
+
37
+ <!-- Main Content -->
38
+ <main class="container mx-auto px-4 py-8">
39
+ <!-- Model Loading Status -->
40
+ <div id="loadingStatus" class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 mb-6 border border-gray-700">
41
+ <div class="flex items-center space-x-3">
42
+ <div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
43
+ <span id="loadingText">Loading AI models...</span>
44
+ <div class="flex-1 bg-gray-700 rounded-full h-2">
45
+ <div id="progressBar" class="bg-blue-500 h-2 rounded-full transition-all duration-300" style="width: 0%">
46
+ </div>
47
+ </div>
48
+ <span id="progressText" class="text-sm text-gray-400">0%</span>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Tab Navigation -->
53
+ <div class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-1 mb-6 border border-gray-700">
54
+ <div class="flex space-x-1">
55
+ <button onclick="switchTab('recognize')" id="recognizeTab" class="tab-btn flex-1 py-2 px-4 rounded-md bg-blue-600 text-white transition-all">
56
+ Recognize Faces
57
+ </button>
58
+ <button onclick="switchTab('manage')" id="manageTab" class="tab-btn flex-1 py-2 px-4 rounded-md text-gray-300 hover:bg-gray-700 transition-all">
59
+ Manage Database
60
+ </button>
61
+ </div>
62
+ </div>
63
+
64
+ <!-- Recognize Tab -->
65
+ <div id="recognizePanel" class="tab-panel">
66
+ <div class="grid md:grid-cols-2 gap-6">
67
+ <!-- Upload Section -->
68
+ <div class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-gray-700">
69
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
70
+ <svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
72
+ 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"></path>
73
+ </svg>
74
+ Upload Image
75
+ </h2>
76
+ <div
77
+ class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center hover:border-blue-500 transition-colors cursor-pointer"
78
+ onclick="document.getElementById('imageInput').click()">
79
+ <svg class="w-12 h-12 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
81
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z">
82
+ </path>
83
+ </svg>
84
+ <p class="text-gray-400">Click to upload or drag and drop</p>
85
+ <p class="text-sm text-gray-500 mt-2">PNG, JPG, GIF up to 10MB</p>
86
+ <input type="file" id="imageInput" accept="image/*" class="hidden" onchange="handleImageUpload(event)">
87
+ </div>
88
+
89
+ <!-- Preview -->
90
+ <div id="imagePreview" class="mt-4 hidden">
91
+ <img id="previewImg" class="w-full rounded-lg">
92
+ <button onclick="recognizeFaces()" class="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors">
93
+ Recognize Faces
94
+ </button>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Results Section -->
99
+ <div class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-gray-700">
100
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
101
+ <svg class="w-5 h-5 mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
103
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
104
+ </svg>
105
+ Recognition Results
106
+ </h2>
107
+ <div id="resultsContainer" class="space-y-3">
108
+ <div class="text-center text-gray-400 py-8">
109
+ <svg class="w-16 h-16 mx-auto mb-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
110
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
111
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
112
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
113
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
114
+ </path>
115
+ </svg>
116
+ <p>Upload an image to recognize faces</p>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Manage Tab -->
124
+ <div id="managePanel" class="tab-panel hidden">
125
+ <div class="grid md:grid-cols-2 gap-6">
126
+ <!-- Add New Person -->
127
+ <div class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-gray-700">
128
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
129
+ <svg class="w-5 h-5 mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
131
+ d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path>
132
+ </svg>
133
+ Add New Person
134
+ </h2>
135
+ <div class="space-y-4">
136
+ <div>
137
+ <label class="block text-sm font-medium text-gray-300 mb-2">Name</label>
138
+ <input type="text" id="personName" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:border-blue-500" placeholder="Enter person's name">
139
+ </div>
140
+ <div>
141
+ <label class="block text-sm font-medium text-gray-300 mb-2">Photo</label>
142
+ <div
143
+ class="border-2 border-dashed border-gray-600 rounded-lg p-4 text-center hover:border-blue-500 transition-colors cursor-pointer"
144
+ onclick="document.getElementById('personPhoto').click()">
145
+ <svg class="w-8 h-8 mx-auto text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
146
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6">
147
+ </path>
148
+ </svg>
149
+ <p class="text-sm text-gray-400">Click to select photo</p>
150
+ <input type="file" id="personPhoto" accept="image/*" class="hidden" onchange="handlePersonPhoto(event)">
151
+ </div>
152
+ <img id="personPhotoPreview" class="w-full rounded-lg mt-2 hidden">
153
+ </div>
154
+ <button onclick="addPerson()" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg transition-colors">
155
+ Add to Database
156
+ </button>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- Database List -->
161
+ <div class="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-gray-700">
162
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
163
+ <svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
164
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
165
+ d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10">
166
+ </path>
167
+ </svg>
168
+ Face Database
169
+ </h2>
170
+ <div id="databaseList" class="space-y-2 max-h-96 overflow-y-auto">
171
+ <div class="text-center text-gray-400 py-8">
172
+ <svg class="w-16 h-16 mx-auto mb-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
174
+ d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4">
175
+ </path>
176
+ </svg>
177
+ <p>No faces in database</p>
178
+ <p class="text-sm text-gray-500 mt-2">Add people to start recognizing faces</p>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </main>
185
+
186
+ <script src="https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.1/dist/transformers.min.js"></script>
187
+ <script src="app.js"></script>
188
+ </body>
189
+
190
+ </html>
191
+
192
+ === app.js ===
193
+ // Global variables
194
+ let faceDetector = null;
195
+ let faceFeatureExtractor = null;
196
+ let faceDatabase = [];
197
+ let currentImage = null;
198
+
199
+ // Initialize application
200
+ document.addEventListener('DOMContentLoaded', async () => {
201
+ await loadModels();
202
+ loadDatabase();
203
+ });
204
+
205
+ // Load AI models using Web Worker
206
+ async function loadModels() {
207
+ const worker = new Worker('worker.js');
208
+
209
+ worker.onmessage = (event) => {
210
+ const { type, data } = event.data;
211
+
212
+ switch(type) {
213
+ case 'progress':
214
+ updateLoadingProgress(data.progress, data.message);
215
+ break;
216
+ case 'modelsLoaded':
217
+ faceDetector = data.faceDetector;
218
+ faceFeatureExtractor = data.faceFeatureExtractor;
219
+ document.getElementById('loadingStatus').classList.add('hidden');
220
+ break;
221
+ case 'error':
222
+ showError(data.message);
223
+ break;
224
+ }
225
+ };
226
+
227
+ worker.postMessage({ type: 'loadModels' });
228
+ }
229
+
230
+ // Update loading progress
231
+ function updateLoadingProgress(progress, message) {
232
+ document.getElementById('progressBar').style.width = `${progress}%`;
233
+ document.getElementById('progressText').textContent = `${progress}%`;
234
+ document.getElementById('loadingText').textContent = message;
235
+ }
236
+
237
+ // Switch between tabs
238
+ function switchTab(tab) {
239
+ const tabs = document.querySelectorAll('.tab-btn');
240
+ const panels = document.querySelectorAll('.tab-panel');
241
+
242
+ tabs.forEach(t => t.classList.remove('bg-blue-600', 'text-white'));
243
+ tabs.forEach(t => t.classList.add('text-gray-300', 'hover:bg-gray-700'));
244
+ panels.forEach(p => p.classList.add('hidden'));
245
+
246
+ if (tab === 'recognize') {
247
+ document.getElementById('recognizeTab').classList.add('bg-blue-600', 'text-white');
248
+ document.getElementById('recognizeTab').classList.remove('text-gray-300', 'hover:bg-gray-700');
249
+ document.getElementById('recognizePanel').classList.remove('hidden');
250
+ } else {
251
+ document.getElementById('manageTab').classList.add('bg-blue-600', 'text-white');
252
+ document.getElementById('manageTab').classList.remove('text-gray-300', 'hover:bg-gray-700');
253
+ document.getElementById('managePanel').classList.remove('hidden');
254
+ }
255
+ }
256
+
257
+ // Handle image upload for recognition
258
+ function handleImageUpload(event) {
259
+ const file = event.target.files[0];
260
+ if (file && file.type.startsWith('image/')) {
261
+ const reader = new FileReader();
262
+ reader.onload = (e) => {
263
+ currentImage = e.target.result;
264
+ document.getElementById('previewImg').src = currentImage;
265
+ document.getElementById('imagePreview').classList.remove('hidden');
266
+ };
267
+ reader.readAsDataURL(file);
268
+ }
269
+ }
270
+
271
+ // Handle person photo upload
272
+ function handlePersonPhoto(event) {
273
+ const file = event.target.files[0];
274
+ if (file && file.type.startsWith('image/')) {
275
+ const reader = new FileReader();
276
+ reader.onload = (e) => {
277
+ document.getElementById('personPhotoPreview').src = e.target.result;
278
+ document.getElementById('personPhotoPreview').classList.remove('hidden');
279
+ };
280
+ reader.readAsDataURL(file);
281
+ }
282
+ }
283
+
284
+ // Recognize faces in the uploaded image
285
+ async function recognizeFaces() {
286
+ if (!currentImage || !faceDetector || !faceFeatureExtractor) {
287
+ showError('Please upload an image and wait for models to load');
288
+ return;
289
+ }
290
+
291
+ const resultsContainer = document.getElementById('resultsContainer');
292
+ resultsContainer.innerHTML = '<div class="text-center py-4">
293
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
294
+ <p class="mt-2 text-gray-400">Processing image...</p>
295
+ </div>';
296
+
297
+ try {
298
+ // Create image element from source
299
+ const img = new Image();
300
+ img.src = currentImage;
301
+ await new Promise(resolve => img.onload = resolve);
302
+
303
+ // Detect faces
304
+ const detections = await faceDetector(img);
305
+
306
+ if (detections.length === 0) {
307
+ resultsContainer.innerHTML = '<div class="text-center py-8 text-gray-400">
308
+ <p>No faces detected in the image</p>
309
+ </div>';
310
+ return;
311
+ }
312
+
313
+ // Extract features and match with database
314
+ const results = [];
315
+ for (const detection of detections) {
316
+ const face = await extractFaceFeatures(img, detection);
317
+ const matches = await matchFace(face);
318
+
319
+ if (matches.length > 0) {
320
+ results.push({
321
+ ...detection,
322
+ match: matches[0],
323
+ confidence: matches[0].similarity
324
+ });
325
+ } else {
326
+ results.push({
327
+ ...detection,
328
+ match: null,
329
+ confidence: 0
330
+ });
331
+ }
332
+ }
333
+
334
+ displayResults(results);
335
+
336
+ } catch (error) {
337
+ console.error('Error recognizing faces:', error);
338
+ showError('Failed to recognize faces. Please try again.');
339
+ }
340
+ }
341
+
342
+ // Extract face features
343
+ async function extractFaceFeatures(image, detection) {
344
+ // Create canvas to crop face
345
+ const canvas = document.createElement('canvas');
346
+ const ctx = canvas.getContext('2d');
347
+
348
+ // Get face bounding box
349
+ const box = detection.box;
350
+ canvas.width = box[2] - box[0];
351
+ canvas.height = box[3] - box[1];
352
+
353
+ // Draw cropped face
354
+ ctx.drawImage(image, box[0], box[1], canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
355
+
356
+ // Extract features using the feature extractor
357
+ const features = await faceFeatureExtractor(canvas.toDataURL());
358
+ return features;
359
+ }
360
+
361
+ // Match face with database
362
+ async function matchFace(faceFeatures) {
363
+ if (!faceDatabase || faceDatabase.length === 0) {
364
+ return [];
365
+ }
366
+
367
+ const matches = [];
368
+
369
+ for (const person of faceDatabase) {
370
+ // Calculate cosine similarity
371
+ const similarity = cosineSimilarity(faceFeatures, person.features);
372
+
373
+ if (similarity > 0.6) { // Threshold for matching
374
+ matches.push({
375
+ name: person.name,
376
+ similarity: similarity
377
+ });
378
+ }
379
+ }
380
+
381
+ // Sort by similarity
382
+ matches.sort((a, b) => b.similarity - a.similarity);
383
+
384
+ return matches;
385
+ }
386
+
387
+ // Calculate cosine similarity between two feature vectors
388
+ function cosineSimilarity(vecA, vecB) {
389
+ if (!vecA || !vecB || vecA.length !== vecB.length) return 0;
390
+
391
+ let dotProduct = 0;
392
+ let normA = 0;
393
+ let normB = 0;
394
+
395
+ for (let i = 0; i < vecA.length; i++) { dotProduct +=vecA[i] * vecB[i]; normA +=vecA[i] * vecA[i]; normB +=vecB[i] *
396
+ vecB[i]; } normA=Math.sqrt(normA); normB=Math.sqrt(normB); if (normA===0 || normB===0) return 0; return dotProduct /
397
+ (normA * normB); } // Display recognition results function displayResults(results) { const
398
+ resultsContainer=document.getElementById('resultsContainer'); if (results.length===0) {
399
+ resultsContainer.innerHTML='<div class="text-center py-8 text-gray-400"><p>No faces detected</p></div>' ; return; }
400
+ let html='' ; results.forEach((result, index)=> {
401
+ const confidence = Math.round(result.confidence * 100);
402
+ const statusColor = result.match ? 'text-green-400' : 'text-yellow-400';
403
+ const statusText = result.match ? `Recognized as ${result.match.name}` : 'Unknown person';
404
+
405
+ html += `
406
+ <div class="bg-gray-700/50 rounded-lg p-4 border border-gray-600">
407
+ <div class="flex items-center justify-between">
408
+ <div class="flex items-center space-x-3">
409
+ <div class="bg-gray-600 rounded-full p-2">
410
+ <svg class="w-6 h-6 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
411
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
412
+ d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
413
+ </svg>
414
+ </div>
415
+ <div>
416
+ <p class="font-medium ${statusColor}">${statusText}</p>
417
+ <p class="text-sm text-gray-400">Confidence: ${confidence}%</p>
418
+ </div>
419
+ </div>
420
+ <div class="text-sm text-gray-500">
421
+ Face ${index + 1}
422
+ </div>
423
+ </div>
424
+ </div>
425
+ `;
426
+ });
427
+
428
+ resultsContainer.innerHTML = html;
429
+ }
430
+
431
+ // Add new person to database
432
+ async function addPerson() {
433
+ const name = document.getElementById('personName').value.trim();
434
+ const photoPreview = document.getElementById('personPhotoPreview');
435
+
436
+ if (!name) {
437
+ showError('Please enter a name');
438
+ return;
439
+ }
440
+
441
+ if (photoPreview.classList.contains('hidden')) {
442
+ showError('Please select a photo');
443
+ return;
444
+ }
445
+
446
+ if (!faceFeatureExtractor) {
447
+ showError('Feature extractor not loaded');
448
+ return;
449
+ }
450
+
451
+ try {
452
+ // Extract features from the photo
453
+ const img = new Image();
454
+ img.src = photoPreview.src;
455
+ await new Promise(resolve => img.onload = resolve);
456
+
457
+ // Detect face first
458
+ const detections = await faceDetector(img);
459
+
460
+ if (detections.length === 0) {
461
+ showError('No face detected in the photo');
462
+ return;
463
+ }
464
+
465
+ // Extract features
466
+ const features = await extractFaceFeatures(img, detections[0]);
467
+
468
+ // Add to database
469
+ const person = {
470
+ id: Date.now(),
471
+ name: name,
472
+ features: features,
473
+ photo: photoPreview.src
474
+ };
475
+
476
+ faceDatabase.push(person);
477
+ saveDatabase();
478
+ updateDatabaseList();
479
+
480
+ // Clear form
481
+ document.getElementById('personName').value = '';
482
+ photoPreview.classList.add('hidden');
483
+ document.getElementById('personPhoto').value = '';
484
+
485
+ showSuccess(`${name} added to database successfully!`);
486
+
487
+ } catch (error) {
488
+ console.error('Error adding person:', error);
489
+ showError('Failed to add person. Please try again.');
490
+ }
491
+ }
492
+
493
+ // Load database from localStorage
494
+ function loadDatabase() {
495
+ const saved = localStorage.getItem('faceDatabase');
496
+ if (saved) {
497
+ faceDatabase = JSON.parse(saved);
498
+ updateDatabaseList();
499
+ }
500
+ }
501
+
502
+ // Save database to localStorage
503
+ function saveDatabase() {
504
+ localStorage.setItem('faceDatabase', JSON.stringify(faceDatabase));
505
+ }
506
+
507
+ // Update database list display
508
+ function updateDatabaseList() {
509
+ const listContainer = document.getElementById('databaseList');
510
+
511
+ if (faceDatabase.length === 0) {
512
+ listContainer.innerHTML = `
513
+ <div class="text-center text-gray-400 py-8">
514
+ <svg class="w-16 h-16 mx-auto mb-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
515
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
516
+ d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4">
517
+ </path>
518
+ </svg>
519
+ <p>No faces in database</p>
520
+ <p class="text-sm text-gray-500 mt-2">Add people to start recognizing faces</p>
521
+ </div>
522
+ `;
523
+ return;
524
+ }
525
+
526
+ let html = '';
527
+ faceDatabase.forEach(person => {
528
+ html += `
529
+ <div class="bg-gray-700/50 rounded-lg p-3 border border-gray-600 flex items-center justify-between">
530
+ <div class="flex items-center space-x-3">
531
+ <img src="${person.photo}" alt="${person.name}" class="w-12 h-12 rounded-full object-cover">
532
+ <div>
533
+ <p class="font-medium">${person.name}</p>
534
+ <p class="text-sm text-gray-400">ID: ${person.id}</p>
535
+ </div>
536
+ </div>
537
+ <button onclick="removePerson(${person.id})" class="text-red-400 hover:text-red-300 transition-colors">
538
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
539
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
540
+ </svg>
541
+ </button>
542
+ </div>
543
+ `;
544
+ });
545
+
546
+ listContainer.innerHTML = html;
547
+ }
548
+
549
+ // Remove person from database
550
+ function removePerson(id) {
551
+ if (confirm('Are you sure you want to remove this person from the database?')) {
552
+ faceDatabase = faceDatabase.filter(person => person.id !== id);
553
+ saveDatabase();
554
+ updateDatabaseList();
555
+ showSuccess('Person removed from database');
556
+ }
557
+ }
558
+
559
+ // Show error message
560
+ function showError(message) {
561
+ const toast = document.createElement('div');
562
+ toast.className = 'fixed top-4 right-4 bg-red-600 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-pulse';
563
+ toast.innerHTML = `
564
+ <div class="flex items-center space-x-2">
565
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
566
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
567
+ d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
568
+ </svg>
569
+ <span>${message}</span>
570
+ </div>
571
+ `;
572
+ document.body.appendChild(toast);
573
+ setTimeout(() => toast.remove(), 3000);
574
+ }
575
+
576
+ // Show success message
577
+ function showSuccess(message) {
578
+ const toast = document.createElement('div');
579
+ toast.className = 'fixed top-4 right-4 bg-green-600 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-pulse';
580
+ toast.innerHTML = `
581
+ <div class="flex items-center space-x-2">
582
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
583
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
584
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
585
+ </svg>
586
+ <span>${message}</span>
587
+ </div>
588
+ `;
589
+ document.body.appendChild(toast);
590
+ setTimeout(() => toast.remove(), 3000);
591
+ }
592
+
593
+ // Drag and drop functionality
594
+ document.addEventListener('DOMContentLoaded', () => {
595
+ const dropZone = document.querySelector('.border-dashed');
596
+
597
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
598
+ dropZone.addEventListener(eventName, preventDefaults, false);
599
+ });
600
+
601
+ function preventDefaults(e) {
602
+ e.preventDefault();
603
+ e.stopPropagation();
604
+ }
605
+
606
+ ['dragenter', 'dragover'].forEach(eventName => {
607
+ dropZone.addEventListener(eventName, () => {
608
+ dropZone.classList.add('border-blue-500');
609
+ });
610
+ });
611
+
612
+ ['dragleave', 'drop'].forEach(eventName => {
613
+ dropZone.addEventListener(eventName, () => {
614
+ dropZone.classList.remove('border-blue-500');
615
+ });
616
+ });
617
+
618
+ dropZone.addEventListener('drop', handleDrop);
619
+
620
+ function handleDrop(e) {
621
+ const dt = e.dataTransfer;
622
+ const files = dt.files;
623
+
624
+ if (files.length > 0) {
625
+ document.getElementById('imageInput').files = files;
626
+ handleImageUpload({ target: { files: files } });
627
+ }
628
+ }
629
+ });
630
+
631
+ === worker.js ===
632
+ // Web Worker for loading models in the background
633
+ self.importScripts('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.1/dist/transformers.min.js');
634
+
635
+ async function loadModels() {
636
+ try {
637
+ // Load face detection model
638
+ self.postMessage({ type: 'progress', data: { progress: 20, message: 'Loading face detection model...' } });
639
+
640
+ const faceDetector = await pipeline('face-detection', 'Xenova/retinaface-resnet50', {
641
+ progress_callback: (data) => {
642
+ if (data.status === 'downloading') {
643
+ const progress = Math.round(data.progress * 100);
644
+ self.postMessage({
645
+ type: 'progress',
646
+ data: {
647
+ progress: 20 + Math.floor(progress * 0.3),
648
+ message: `Downloading face detection model: ${progress}%`
649
+ }
650
+ });
651
+ }
652
+ }
653
+ });
654
+
655
+ // Load face feature extraction model
656
+ self.postMessage({ type: 'progress', data: { progress: 50, message: 'Loading face recognition model...' } });
657
+
658
+ const faceFeatureExtractor = await pipeline('image-feature-extraction', 'Xenova/face-recognition-model', {
659
+ progress_callback: (data) => {
660
+ if (data.status === 'downloading') {
661
+ const progress = Math.round(data.progress * 100);
662
+ self.postMessage({
663
+ type: 'progress',
664
+ data: {
665
+ progress: 50 + Math.floor(progress * 0.5),
666
+ message: `Downloading face recognition model: ${progress}%`
667
+ }
668
+ });
669
+ }
670
+ }
671
+ });
672
+
673
+ self.postMessage({ type: 'progress', data: { progress: 100, message: 'Models loaded successfully!' } });
674
+
675
+ self.postMessage({
676
+ type: 'modelsLoaded',
677
+ data: {
678
+ faceDetector: faceDetector,
679
+ faceFeatureExtractor: faceFeatureExtractor
680
+ }
681
+ });
682
+
683
+ } catch (error) {
684
+ console.error('Error loading models:', error);
685
+ self.postMessage({ type: 'error', data: { message: 'Failed to load models: ' + error.message } });
686
+ }
687
+ }
688
+
689
+ // Listen for messages from main thread
690
+ self.onmessage = (event) => {
691
+ const { type } = event.data;
692
+
693
+ switch(type) {
694
+ case 'loadModels':
695
+ loadModels();
696
+ break;
697
+ }
698
+ };