midokhaled927 commited on
Commit
3c5bdfe
·
verified ·
1 Parent(s): 3c58916

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +463 -55
app/app.py CHANGED
@@ -4,26 +4,21 @@ from fastapi.staticfiles import StaticFiles
4
  import numpy as np
5
  import sqlite3
6
  import json
7
- import cv2
8
  from pathlib import Path
9
  import hashlib
10
  import time
11
-
12
- from services.detection import FaceDetector
13
- from services.embedding import FaceEmbedding
14
 
15
  app = FastAPI(title="AEFRS Ultimate - Face Recognition System")
16
 
17
- # تهيئة الخدمات
18
- detector = FaceDetector()
19
- embedder = FaceEmbedding()
20
-
21
- # قاعدة البيانات
22
  DB_PATH = "database/identities.db"
23
- VECTOR_INDEX_PATH = "database/vector_index/"
24
 
25
- # إنشاء الجداول
26
  def init_database():
 
 
27
  conn = sqlite3.connect(DB_PATH)
28
  cursor = conn.cursor()
29
  cursor.execute("""
@@ -31,8 +26,7 @@ def init_database():
31
  id TEXT PRIMARY KEY,
32
  name TEXT NOT NULL,
33
  embedding TEXT NOT NULL,
34
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
35
- metadata TEXT
36
  )
37
  """)
38
  conn.commit()
@@ -40,37 +34,38 @@ def init_database():
40
 
41
  init_database()
42
 
43
- # API للتسجيل
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  @app.post("/api/enroll")
45
- async def enroll_face(
46
- name: str,
47
- file: UploadFile = File(...)
48
- ):
49
- """تسجيل وجه جديد في النظام"""
50
  try:
51
- # قراءة الصورة
52
  contents = await file.read()
53
- nparr = np.frombuffer(contents, np.uint8)
54
- image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
55
-
56
- # كشف الوجه
57
- faces = detector.detect_faces(image)
58
- if not faces:
59
- raise HTTPException(status_code=400, detail="No face detected")
60
 
61
  # استخراج المتجه
62
- face_roi = image[faces[0]['bbox'][1]:faces[0]['bbox'][3],
63
- faces[0]['bbox'][0]:faces[0]['bbox'][2]]
64
- embedding = embedder.get_embedding(face_roi)
65
 
66
- # حفظ في قاعدة البيانات
67
  identity_id = hashlib.md5(f"{name}_{time.time()}".encode()).hexdigest()[:16]
68
 
 
69
  conn = sqlite3.connect(DB_PATH)
70
  cursor = conn.cursor()
71
  cursor.execute(
72
- "INSERT INTO identities (id, name, embedding, metadata) VALUES (?, ?, ?, ?)",
73
- (identity_id, name, json.dumps(embedding.tolist()), json.dumps({"timestamp": time.time()}))
74
  )
75
  conn.commit()
76
  conn.close()
@@ -85,28 +80,14 @@ async def enroll_face(
85
  except Exception as e:
86
  raise HTTPException(status_code=500, detail=str(e))
87
 
88
- # API للبحث
89
  @app.post("/api/search")
90
  async def search_face(file: UploadFile = File(...)):
91
  """البحث عن وجه مشابه"""
92
  try:
93
- # قراءة وتحليل الصورة
94
  contents = await file.read()
95
- nparr = np.frombuffer(contents, np.uint8)
96
- image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
97
-
98
- # كشف الوجه
99
- faces = detector.detect_faces(image)
100
- if not faces:
101
- return JSONResponse({
102
- "success": False,
103
- "message": "No face detected"
104
- })
105
 
106
- # استخراج المتجه
107
- face_roi = image[faces[0]['bbox'][1]:faces[0]['bbox'][3],
108
- faces[0]['bbox'][0]:faces[0]['bbox'][2]]
109
- query_embedding = embedder.get_embedding(face_roi)
110
 
111
  # البحث في قاعدة البيانات
112
  conn = sqlite3.connect(DB_PATH)
@@ -125,7 +106,7 @@ async def search_face(file: UploadFile = File(...)):
125
  # ترتيب النتائج
126
  similarities.sort(key=lambda x: x[2], reverse=True)
127
 
128
- if similarities and similarities[0][2] > 0.6: # عتبة 60%
129
  return JSONResponse({
130
  "success": True,
131
  "identity_id": similarities[0][0],
@@ -142,14 +123,441 @@ async def search_face(file: UploadFile = File(...)):
142
  except Exception as e:
143
  raise HTTPException(status_code=500, detail=str(e))
144
 
145
- # تشغيل الواجهة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  @app.get("/", response_class=HTMLResponse)
147
  async def get_html():
148
- with open("static/index.html", "r", encoding="utf-8") as f:
149
- return f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # تثبيت الملفات الثابتة
152
- app.mount("/static", StaticFiles(directory="static"), name="static")
153
 
154
  if __name__ == "__main__":
155
  import uvicorn
 
4
  import numpy as np
5
  import sqlite3
6
  import json
 
7
  from pathlib import Path
8
  import hashlib
9
  import time
10
+ import cv2
11
+ from PIL import Image
12
+ import io
13
 
14
  app = FastAPI(title="AEFRS Ultimate - Face Recognition System")
15
 
16
+ # قاعدة بيانات بسيطة
 
 
 
 
17
  DB_PATH = "database/identities.db"
 
18
 
 
19
  def init_database():
20
+ """تهيئة قاعدة البيانات"""
21
+ Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
22
  conn = sqlite3.connect(DB_PATH)
23
  cursor = conn.cursor()
24
  cursor.execute("""
 
26
  id TEXT PRIMARY KEY,
27
  name TEXT NOT NULL,
28
  embedding TEXT NOT NULL,
29
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 
30
  )
31
  """)
32
  conn.commit()
 
34
 
35
  init_database()
36
 
37
+ def get_face_embedding(image_bytes):
38
+ """استخراج بصمة الوجه (نسخة مبسطة للتجربة)"""
39
+ # هذه دالة مبسطة للتجربة فقط
40
+ # في الإنتاج، استخدم نموذج TFLite الفعلي
41
+
42
+ # تحويل الصورة إلى مصفوفة
43
+ image = Image.open(io.BytesIO(image_bytes))
44
+ image = image.resize((112, 112))
45
+ img_array = np.array(image).flatten()[:512] # خذ أول 512 قيمة
46
+
47
+ # تطبيع المتجه
48
+ embedding = img_array / np.linalg.norm(img_array)
49
+ return embedding
50
+
51
  @app.post("/api/enroll")
52
+ async def enroll_face(name: str, file: UploadFile = File(...)):
53
+ """تسجيل وجه جديد"""
 
 
 
54
  try:
 
55
  contents = await file.read()
 
 
 
 
 
 
 
56
 
57
  # استخراج المتجه
58
+ embedding = get_face_embedding(contents)
 
 
59
 
60
+ # إنشاء ID فريد
61
  identity_id = hashlib.md5(f"{name}_{time.time()}".encode()).hexdigest()[:16]
62
 
63
+ # حفظ في قاعدة البيانات
64
  conn = sqlite3.connect(DB_PATH)
65
  cursor = conn.cursor()
66
  cursor.execute(
67
+ "INSERT INTO identities (id, name, embedding) VALUES (?, ?, ?)",
68
+ (identity_id, name, json.dumps(embedding.tolist()))
69
  )
70
  conn.commit()
71
  conn.close()
 
80
  except Exception as e:
81
  raise HTTPException(status_code=500, detail=str(e))
82
 
 
83
  @app.post("/api/search")
84
  async def search_face(file: UploadFile = File(...)):
85
  """البحث عن وجه مشابه"""
86
  try:
 
87
  contents = await file.read()
 
 
 
 
 
 
 
 
 
 
88
 
89
+ # استخراج المتجه للبحث
90
+ query_embedding = get_face_embedding(contents)
 
 
91
 
92
  # البحث في قاعدة البيانات
93
  conn = sqlite3.connect(DB_PATH)
 
106
  # ترتيب النتائج
107
  similarities.sort(key=lambda x: x[2], reverse=True)
108
 
109
+ if similarities and similarities[0][2] > 0.5: # عتبة 50%
110
  return JSONResponse({
111
  "success": True,
112
  "identity_id": similarities[0][0],
 
123
  except Exception as e:
124
  raise HTTPException(status_code=500, detail=str(e))
125
 
126
+ @app.get("/api/identities")
127
+ async def get_identities():
128
+ """الحصول على جميع الهويات المسجلة"""
129
+ try:
130
+ conn = sqlite3.connect(DB_PATH)
131
+ cursor = conn.cursor()
132
+ cursor.execute("SELECT id, name, created_at FROM identities ORDER BY created_at DESC")
133
+ rows = cursor.fetchall()
134
+ conn.close()
135
+
136
+ identities = [
137
+ {"id": row[0], "name": row[1], "created_at": row[2]}
138
+ for row in rows
139
+ ]
140
+
141
+ return JSONResponse({
142
+ "success": True,
143
+ "data": identities,
144
+ "count": len(identities)
145
+ })
146
+
147
+ except Exception as e:
148
+ raise HTTPException(status_code=500, detail=str(e))
149
+
150
+ @app.get("/healthz")
151
+ async def health_check():
152
+ """فحص صحة النظام"""
153
+ return JSONResponse({
154
+ "status": "healthy",
155
+ "timestamp": time.time()
156
+ })
157
+
158
+ # واجهة HTML
159
  @app.get("/", response_class=HTMLResponse)
160
  async def get_html():
161
+ html_content = """
162
+ <!DOCTYPE html>
163
+ <html lang="ar" dir="rtl">
164
+ <head>
165
+ <meta charset="UTF-8">
166
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
167
+ <title>AEFRS Ultimate - نظام التعرف على الوجوه</title>
168
+ <style>
169
+ * {
170
+ margin: 0;
171
+ padding: 0;
172
+ box-sizing: border-box;
173
+ }
174
+
175
+ body {
176
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
177
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
178
+ min-height: 100vh;
179
+ padding: 20px;
180
+ }
181
+
182
+ .container {
183
+ max-width: 800px;
184
+ margin: 0 auto;
185
+ background: white;
186
+ border-radius: 20px;
187
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
188
+ overflow: hidden;
189
+ }
190
+
191
+ .header {
192
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
193
+ color: white;
194
+ padding: 30px;
195
+ text-align: center;
196
+ }
197
+
198
+ .header h1 {
199
+ font-size: 2em;
200
+ margin-bottom: 10px;
201
+ }
202
+
203
+ .tabs {
204
+ display: flex;
205
+ background: #f5f5f5;
206
+ border-bottom: 2px solid #e0e0e0;
207
+ }
208
+
209
+ .tab-btn {
210
+ flex: 1;
211
+ padding: 15px;
212
+ background: none;
213
+ border: none;
214
+ cursor: pointer;
215
+ font-size: 16px;
216
+ font-weight: 600;
217
+ transition: all 0.3s;
218
+ }
219
+
220
+ .tab-btn:hover {
221
+ background: #e0e0e0;
222
+ }
223
+
224
+ .tab-btn.active {
225
+ background: white;
226
+ color: #667eea;
227
+ border-bottom: 3px solid #667eea;
228
+ }
229
+
230
+ .tab-content {
231
+ display: none;
232
+ padding: 30px;
233
+ animation: fadeIn 0.5s;
234
+ }
235
+
236
+ .tab-content.active {
237
+ display: block;
238
+ }
239
+
240
+ @keyframes fadeIn {
241
+ from { opacity: 0; transform: translateY(10px); }
242
+ to { opacity: 1; transform: translateY(0); }
243
+ }
244
+
245
+ .form-group {
246
+ margin-bottom: 20px;
247
+ }
248
+
249
+ label {
250
+ display: block;
251
+ margin-bottom: 10px;
252
+ font-weight: 600;
253
+ color: #333;
254
+ }
255
+
256
+ input[type="text"], input[type="file"] {
257
+ width: 100%;
258
+ padding: 12px;
259
+ border: 2px solid #e0e0e0;
260
+ border-radius: 10px;
261
+ font-size: 16px;
262
+ transition: border-color 0.3s;
263
+ }
264
+
265
+ input[type="text"]:focus {
266
+ outline: none;
267
+ border-color: #667eea;
268
+ }
269
+
270
+ .image-preview {
271
+ margin-top: 10px;
272
+ text-align: center;
273
+ }
274
+
275
+ .image-preview img {
276
+ max-width: 200px;
277
+ border-radius: 10px;
278
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
279
+ }
280
+
281
+ .btn-primary {
282
+ width: 100%;
283
+ padding: 12px;
284
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
285
+ color: white;
286
+ border: none;
287
+ border-radius: 10px;
288
+ font-size: 16px;
289
+ font-weight: 600;
290
+ cursor: pointer;
291
+ transition: transform 0.2s;
292
+ }
293
+
294
+ .btn-primary:hover {
295
+ transform: translateY(-2px);
296
+ }
297
+
298
+ .btn-secondary {
299
+ padding: 10px 20px;
300
+ background: #f5f5f5;
301
+ border: 2px solid #e0e0e0;
302
+ border-radius: 10px;
303
+ cursor: pointer;
304
+ margin-bottom: 20px;
305
+ }
306
+
307
+ .result-message {
308
+ margin-top: 20px;
309
+ padding: 15px;
310
+ border-radius: 10px;
311
+ background: #f5f5f5;
312
+ }
313
+
314
+ .alert-success {
315
+ background: #d4edda;
316
+ color: #155724;
317
+ border: 1px solid #c3e6cb;
318
+ }
319
+
320
+ .alert-error {
321
+ background: #f8d7da;
322
+ color: #721c24;
323
+ border: 1px solid #f5c6cb;
324
+ }
325
+
326
+ .alert-info {
327
+ background: #d1ecf1;
328
+ color: #0c5460;
329
+ border: 1px solid #bee5eb;
330
+ }
331
+
332
+ .loading {
333
+ text-align: center;
334
+ padding: 20px;
335
+ color: #667eea;
336
+ }
337
+
338
+ .database-list {
339
+ margin-top: 20px;
340
+ }
341
+
342
+ .identity-card {
343
+ background: #f9f9f9;
344
+ padding: 15px;
345
+ margin-bottom: 10px;
346
+ border-radius: 10px;
347
+ border-right: 4px solid #667eea;
348
+ }
349
+
350
+ .identity-card h3 {
351
+ color: #333;
352
+ margin-bottom: 5px;
353
+ }
354
+
355
+ .identity-card p {
356
+ color: #666;
357
+ font-size: 14px;
358
+ }
359
+ </style>
360
+ </head>
361
+ <body>
362
+ <div class="container">
363
+ <div class="header">
364
+ <h1>🔒 AEFRS Ultimate</h1>
365
+ <p>نظام متقدم للتعرف على الوجوه</p>
366
+ </div>
367
+
368
+ <div class="tabs">
369
+ <button class="tab-btn active" onclick="showTab('enroll')">📝 تسجيل وجه جديد</button>
370
+ <button class="tab-btn" onclick="showTab('search')">🔍 بحث عن وجه</button>
371
+ <button class="tab-btn" onclick="showTab('database')">🗄️ قاعدة البيانات</button>
372
+ </div>
373
+
374
+ <div id="enroll" class="tab-content active">
375
+ <div class="form-group">
376
+ <label>📸 صورة الوجه</label>
377
+ <input type="file" id="enrollImage" accept="image/*">
378
+ <div class="image-preview" id="enrollPreview"></div>
379
+ </div>
380
+ <div class="form-group">
381
+ <label>👤 اسم الشخص</label>
382
+ <input type="text" id="personName" placeholder="أدخل الاسم الكامل">
383
+ </div>
384
+ <button class="btn-primary" onclick="enrollFace()">✨ تسجيل الوجه</button>
385
+ <div id="enrollResult"></div>
386
+ </div>
387
+
388
+ <div id="search" class="tab-content">
389
+ <div class="form-group">
390
+ <label>🔍 صورة للبحث</label>
391
+ <input type="file" id="searchImage" accept="image/*">
392
+ <div class="image-preview" id="searchPreview"></div>
393
+ </div>
394
+ <button class="btn-primary" onclick="searchFace()">🔎 بحث</button>
395
+ <div id="searchResult"></div>
396
+ </div>
397
+
398
+ <div id="database" class="tab-content">
399
+ <button class="btn-secondary" onclick="loadDatabase()">🔄 تحديث</button>
400
+ <div id="databaseList"></div>
401
+ </div>
402
+ </div>
403
+
404
+ <script>
405
+ // معاينة الصورة
406
+ document.getElementById('enrollImage')?.addEventListener('change', function(e) {
407
+ previewImage(e, 'enrollPreview');
408
+ });
409
+ document.getElementById('searchImage')?.addEventListener('change', function(e) {
410
+ previewImage(e, 'searchPreview');
411
+ });
412
+
413
+ function previewImage(event, previewId) {
414
+ const file = event.target.files[0];
415
+ if (file) {
416
+ const reader = new FileReader();
417
+ reader.onload = function(e) {
418
+ const preview = document.getElementById(previewId);
419
+ preview.innerHTML = `<img src="${e.target.result}" alt="Preview">`;
420
+ };
421
+ reader.readAsDataURL(file);
422
+ }
423
+ }
424
+
425
+ async function enrollFace() {
426
+ const imageFile = document.getElementById('enrollImage').files[0];
427
+ const personName = document.getElementById('personName').value;
428
+
429
+ if (!imageFile || !personName) {
430
+ showResult('enrollResult', '❌ الرجاء اختيار صورة وإدخال الاسم', 'error');
431
+ return;
432
+ }
433
+
434
+ const formData = new FormData();
435
+ formData.append('file', imageFile);
436
+
437
+ showLoading('enrollResult', 'جاري تسجيل الوجه...');
438
+
439
+ try {
440
+ const response = await fetch(`/api/enroll?name=${encodeURIComponent(personName)}`, {
441
+ method: 'POST',
442
+ body: formData
443
+ });
444
+
445
+ const result = await response.json();
446
+
447
+ if (result.success) {
448
+ showResult('enrollResult', `✅ ${result.message}<br>الرقم: ${result.identity_id}`, 'success');
449
+ document.getElementById('personName').value = '';
450
+ document.getElementById('enrollImage').value = '';
451
+ document.getElementById('enrollPreview').innerHTML = '';
452
+ } else {
453
+ showResult('enrollResult', `❌ ${result.message}`, 'error');
454
+ }
455
+ } catch (error) {
456
+ showResult('enrollResult', '❌ حدث خطأ في الاتصال', 'error');
457
+ }
458
+ }
459
+
460
+ async function searchFace() {
461
+ const imageFile = document.getElementById('searchImage').files[0];
462
+
463
+ if (!imageFile) {
464
+ showResult('searchResult', '❌ الرجاء اختيار صورة للبحث', 'error');
465
+ return;
466
+ }
467
+
468
+ const formData = new FormData();
469
+ formData.append('file', imageFile);
470
+
471
+ showLoading('searchResult', 'جاري البحث...');
472
+
473
+ try {
474
+ const response = await fetch('/api/search', {
475
+ method: 'POST',
476
+ body: formData
477
+ });
478
+
479
+ const result = await response.json();
480
+
481
+ if (result.success) {
482
+ const similarityPercent = (result.similarity * 100).toFixed(2);
483
+ showResult('searchResult',
484
+ `✅ تم العثور على تطابق!<br>👤 الاسم: ${result.name}<br>📊 نسبة التشابه: ${similarityPercent}%<br>🆔 الرقم: ${result.identity_id}`,
485
+ 'success');
486
+ } else {
487
+ showResult('searchResult', `❌ ${result.message}`, 'error');
488
+ }
489
+ } catch (error) {
490
+ showResult('searchResult', '❌ حدث خطأ في الاتصال', 'error');
491
+ }
492
+ }
493
+
494
+ async function loadDatabase() {
495
+ showLoading('databaseList', 'جاري تحميل البيانات...');
496
+
497
+ try {
498
+ const response = await fetch('/api/identities');
499
+ const result = await response.json();
500
+
501
+ if (result.success && result.data.length > 0) {
502
+ let html = '';
503
+ result.data.forEach(person => {
504
+ html += `
505
+ <div class="identity-card">
506
+ <h3>${person.name}</h3>
507
+ <p>🆔 ${person.id}</p>
508
+ <p>📅 ${new Date(person.created_at).toLocaleDateString('ar-EG')}</p>
509
+ </div>
510
+ `;
511
+ });
512
+ document.getElementById('databaseList').innerHTML = html;
513
+ } else {
514
+ showResult('databaseList', '📭 لا يوجد أشخاص مسجلين', 'info');
515
+ }
516
+ } catch (error) {
517
+ showResult('databaseList', '❌ خطأ في تحميل البيانات', 'error');
518
+ }
519
+ }
520
+
521
+ function showResult(elementId, message, type) {
522
+ const element = document.getElementById(elementId);
523
+ element.innerHTML = `<div class="result-message alert-${type}">${message}</div>`;
524
+ setTimeout(() => {
525
+ if (element.innerHTML.includes('alert')) {
526
+ element.innerHTML = '';
527
+ }
528
+ }, 5000);
529
+ }
530
+
531
+ function showLoading(elementId, message) {
532
+ document.getElementById(elementId).innerHTML = `<div class="loading">⏳ ${message}</div>`;
533
+ }
534
+
535
+ function showTab(tabName) {
536
+ document.querySelectorAll('.tab-content').forEach(tab => {
537
+ tab.classList.remove('active');
538
+ });
539
+ document.querySelectorAll('.tab-btn').forEach(btn => {
540
+ btn.classList.remove('active');
541
+ });
542
+
543
+ document.getElementById(tabName).classList.add('active');
544
+ event.target.classList.add('active');
545
+
546
+ if (tabName === 'database') {
547
+ loadDatabase();
548
+ }
549
+ }
550
+
551
+ // تحميل قاعدة البيانات عند بدء التشغيل
552
+ loadDatabase();
553
+ </script>
554
+ </body>
555
+ </html>
556
+ """
557
+ return HTMLResponse(content=html_content)
558
 
559
+ # تثبيت الملفات الثابتة (اختياري)
560
+ app.mount("/static", StaticFiles(directory="static", html=True), name="static")
561
 
562
  if __name__ == "__main__":
563
  import uvicorn